Sindbad~EG File Manager
/* graph a relation f(x,y)=0
M. Beeson, for Mathpert
11.15.90 file created
10.2.97 code last modified
6.14.98 last modified
4.19.00 eliminated call to user_interrupt
2.24.24 changed include cgraph. to svgGraph.h
*/
#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include "globals.h"
#include "graphstr.h"
#include "svgGraph.h" /* already calls #include terms.h and meta.h*/
#include "grapher.h"
#include "deval.h"
#include "heap.h"
/*____________________________________________________________________________*/
static double verysmall,epsilon; /* used to compensate for roundoff error */
#define NEARCHANGE1(x,y) ((x < verysmall && y > epsilon) || (y < verysmall && x > epsilon))
#define NEARCHANGE2(x,y) NEARCHANGE1(-x,-y)
#define SIGNCHANGE(x,y) (x != BADVAL && y != BADVAL && (x > 0 ? (y <= 0 ? 1 : NEARCHANGE1(x,y)) : y > 0 ? 1 : NEARCHANGE2(x,y)))
#define MAXABSFUNCTOR(a,b) ((a) > 0 ? ((b) <= 0 ? (a) : (a) > (b) ? (a) : (b)) : (b) > 0 ? (b) : (a) < (b) ? -(a) : - (b))
/*_______________________________________________________________*/
static int promising(double a,double b,double c,double d,double e);
int nearzero(term b, double *xp, double deltax, double scale);
/* First version of graphrelation simply computed values of f at every
pixel and looked for sign changes in both directions, coloring two pixels
when it found a sign change. To do this we need two arrays to keep two
columns of values of f. This is too slow.
This version divides the screen into 9 by 9 rectangles, and computes them
by columns, computing values only at the corners and middle unless the rectangle
is promising. Then it computes a double array of values and plots the
corresponding points.
*/
void graphrelation(term f, /* relation to be graphed is f =0 or f > 0 */
/* f can be an AND of mathematical terms,
in which case the relation is the conjunction
of inequalities ARG(i,f) > 0 or >= 0 */
term boundary, /* zero on the boundary */
double *xp, double *yp, /* pointers to values of x,y */
unsigned color, /* for the boundary line */
unsigned fillcolor, /* for the interior */
int shadeflag /* nonzero to shade the interior */
)
/* graph f(x,y)=0 or f(x,y) > 0 over rectangle specified by viewport and world
coordinates */
{ int nx1,nx2,ny1,ny2; /* pixel coordinates of the viewport */
int bigstep;
svgDevice *device = get_device();
int i,j; /* loop variables */
double e;
double xpixel = device -> xpixel;
double ypixel = device -> ypixel;
double square[32][32]; /* for promising rectangles */
double *col0; /* dynamic array for one column of numbers */
double *col1; /* alternate with col0 to save copying */
double *col,*othercol; /* will be col0 and col1 alternately */
int u,v;
double savex,savey;
unsigned changeflag;
unsigned printerbackground = 0x00ffffffL; /* white */
int fillflag = shadeflag;
int printerfill = 0 ; // no printers fillflag && (device->adapter < 0);
int andflag = FUNCTOR(f) == AND;
verysmall = fabs(device->xmax - device->xmin) * VERYSMALL;
epsilon = 1000.0 * verysmall;
bigstep = 5; // was 9
assert(bigstep <= 29);
/* don't ever make it bigger or bounds on 'square' will be exceeded */
nx1 = device->pxmin;
nx2 = device->pxmax;
ny1 = device->pymin;
ny2 = device->pymax;
assert(ny1 < ny2);
/* pixel coordinates are smaller at the top */
/* so ny1 goes with ymax, and ny2 goes with ymin */
col0 = (double *) calloc(ny2-ny1+bigstep+1,sizeof(double));
if(col0==NULL)
nospace();
col1 = (double *) calloc(ny2-ny1+bigstep+1,sizeof(double));
if(col1==NULL)
nospace();
col0 -= ny1-bigstep; /* so legal indices are col[ny1-bigstep]...col[ny2] */
col1 -= ny1-bigstep;
*xp = device->xmin + 0.5*device->xpixel;
/* world coordinates begin half a pixel left of the first pixel */
for(i=nx1; i<=nx2; i+=bigstep)
{ *yp = device->ymin + 0.5 * device->ypixel;
if( i & 1 ) /* i is odd */
{ col = col1;
othercol = col0;
}
else /* i is even */
{ col = col0;
othercol = col1;
}
deval(f,col+ny2); /* outside loop so don't have to check j > ny2 in loop */
*yp += bigstep * ypixel; /* set correct initial value of y */
for(j=ny2-bigstep; j>ny1-bigstep; j-=bigstep) /* compute the i-th column */
{ deval(f,col + j);
savex = *xp;
savey = *yp;
*xp -= 0.5 * bigstep * xpixel;
*yp -= 0.5 * bigstep * ypixel;
deval(f,&e);
*xp = savex;
*yp = savey;
if(e==BADVAL)
{ *yp += bigstep *ypixel;
continue;
}
if(i > nx1 && promising(col[j],col[j+bigstep],othercol[j+bigstep],othercol[j],e))
/* then fill in the 'square' array with every-pixel
values for the (bigstep+1) by (bigstep+3)-pixel rectangle
with (*xp,*yp) near the upper right. Precisely,
the left side is bigstep+1 pixels to the left of *xp
so u = bigstep+1 corresponds to x-coordinate *xp
and u = bigstep+2 is one pixel to the right of *xp
*/
{ if(printerfill)
{ /* it's a printer. Printers have too few colors to make
a solid fill color. The fill color is a hatch pattern.
Therefore, around the edges instead of filling in
pixel by pixel INSIDE the region, we shade the whole
rectangle and then color the OUTSIDE pixels one by
one to the background color. */
svg_filledrect(i-bigstep+1,j+1,i+2,j+bigstep,fillcolor);
}
*xp -= (bigstep+1) * xpixel; /* one to left of little square */
for(u=0;u <= bigstep+2;u++)
{ *yp = savey - (bigstep+1) *ypixel; /* one below little square */
for(v=0; v <= bigstep+2;v++)
{ deval(f,&square[u][v]);
if(square[u][v] == BADVAL)
{ double saveit = *xp;
*xp += epsilon;
/* Give it a second try. If this edge of
the square corresponds to a singularity
such as we have in 1/(x+1) <= 6 we can
avoid leaving a blank column. */
deval(f,&square[u][v]);
*xp = saveit;
if(square[u][v] == BADVAL)
continue; /* without coloring anything */
}
changeflag = SIGNCHANGE(square[u][v],square[u][v-1]);
if(v>0 && changeflag)
{ /* crossing a boundary vertically */
/* determine by evaluating 'boundary' whether we
have to color the boundary */
if(!nearzero(boundary,yp,-ypixel,MAXABSFUNCTOR(square[u][v],square[u][v-1])))
{ /* don't color the boundary */
/* No need to check fillflag because for
inequalities it is always 1 */
if(square[u][v] > 0)
draw_pixel(i-bigstep-1+u,j+bigstep+1-v,fillcolor);
if(square[u][v] < 0)
draw_pixel(i-bigstep-1+u,j+bigstep+2-v,fillcolor);
if(andflag)
changeflag = SIGNCHANGE(square[u-1][v],square[u][v]);
else
changeflag = SIGNCHANGE(square[u-1][v],square[u][v]) ? (unsigned)(-1) : 0;
if(u>0 && changeflag)
{ /* crossing both horizontally and vertically */
if(square[u-1][v] > 0)
draw_pixel(i-bigstep+u-2,j+bigstep+1-v,fillcolor);
if(square[u][v] > 0)
draw_pixel(i-bigstep+u-1,j+bigstep+1-v,fillcolor);
if(nearzero(boundary,yp,ypixel,MAXABSFUNCTOR(square[u-1][v],square[u][v])))
/* color the boundary after all */
{ draw_pixel(i-bigstep-1 + u,j+bigstep+1-v,color);
draw_pixel(i-bigstep-1 + u,j+bigstep+2-v,color);
#if 0 /* draws better without these two lines, I don't know why. */
if(u>0 && SIGNCHANGE(square[u-1][v],square[u][v]))
draw_pixel(i-bigstep+u-2,j+bigstep+1-v,color);
#endif
}
}
}
else /* draw the boundary in color */
{ draw_pixel(i-bigstep-1 + u,j+bigstep+1-v,color);
draw_pixel(i-bigstep-1 + u,j+bigstep+2-v,color);
#if 0 /* draws better without these two lines, I don't know why. */
if(u>0 && SIGNCHANGE(square[u-1][v],square[u][v]))
draw_pixel(i-bigstep+u-2,j+bigstep+1-v,color);
#endif
}
}
else if(u>0 && (changeflag = SIGNCHANGE(square[u-1][v],square[u][v]))!=0)
/* = instead of == is not a mistake here! */
/* The somewhat convoluted syntax uses this principle:
instead of saying if(a=b) we say if((a=b)!=0)
in order to avoid a warning message.
*/
{ /* crossing a boundary horizontally and not vertically */
/* Do we have to color the boundary? */
if(!nearzero(boundary,xp,-xpixel,MAXABSFUNCTOR(square[u-1][v],square[u][v])))
{ /* don't color */
if(square[u][v] > 0)
draw_pixel(i-bigstep+u-1,j+bigstep+1-v,fillcolor);
if(square[u-1][v] > 0)
draw_pixel(i-bigstep+u-2,j+bigstep+1-v,fillcolor);
}
else
{ draw_pixel(i-bigstep+u-1,j+bigstep+1-v,color);
#if 0
/* It draws better without this line, even though it seems like it should be there. */
draw_pixel(i-bigstep+u,j+bigstep+1-v,color);
#endif
}
}
else if(v>0 && fillflag)
{ if(square[u][v] > 0 && !printerfill)
draw_pixel(i-bigstep+u-1,j+bigstep+1-v,fillcolor);
else if(printerfill && square[u][v] <= 0)
draw_pixel(i-bigstep+u-1,j+bigstep+1-v,printerbackground);
}
*yp += ypixel;
}
*xp += xpixel;
}
*xp = savex;
*yp = savey;
}
else if(fillflag && e != BADVAL && e > 0)
/* this entire large rectangle should be shaded */
// PixelRect(i-bigstep+1,j+1,i+2,j+bigstep,fillcolor);
svg_filledrect(i-bigstep,j,i,j+bigstep,fillcolor);
*yp += bigstep *ypixel;
}
*xp += bigstep * xpixel;
}
free(col0 + ny1-bigstep);
free(col1 + ny1-bigstep);
}
/*______________________________________________________________________*/
static int promising(double a,double b,double c, double d,double e)
/* a,b,c,d, are function values of f at corners of a rectangle,
in clockwise order, and e is the value in the middle.
Is there any chance of a zero of f inside the
rectangle? If so return 1, else return 0. */
{ double laplacian;
if(SIGNCHANGE(a,b))
return 1;
if(SIGNCHANGE(b,c))
return 1;
if(SIGNCHANGE(c,d))
return 1;
if(SIGNCHANGE(d,e))
return 1;
/* Now they all have the same sign */
/* Compute the Laplacian; does it has the same sign as f? */
laplacian = a+b+c+d -4.0*e;
if(! SIGNCHANGE(a,laplacian))
/* Laplacian has opposite sign, e.g. positive f is concave down,
so very unlikely to find a zero here */
return 0;
/* But if positive f is concave up, check size of f */
if( fabs(a+b+c+d+e) <= 2.0 * fabs(laplacian))
return 1; /* it's a possibility */
#if 0 /* this was intended to fix some gaps near double zeroes, but it
doesn't help. */
else if(fabs(a+b+c+d) <= 0.1 && fabs(laplacian) <= fabs(a+b+c+d))
/* laplacian is small and function is small, maybe we're near a double zero. */
return 1;
#endif
return 0; /* function is large compared to laplacian, very little chance of zero. */
}
/*_________________________________________________________________________*/
int nearzero(term b, double *xp, double deltax, double scale)
/* xp is a value pointer of an atom in b. Evaluate (using deval)
b and then (temporarily) change *xp by deltax. Return 1 if somewhere between
these values, b is zero or close enough to count as zero. The last
parameter, scale, is provided to help decide what's close. This represents
the max of the endpoint absolute values of the function that computes the
interior of the region being graphed, over the interval in question; that
function changes sign, so 'scale' should be small. If b takes a value on that
order of magnitude, it should count as zero. */
{ double z,w,savexp,mid;
deval(b,&z);
if(z == BADVAL)
return 0;
if(z < 10*scale)
return 1;
if(z > 50*scale)
return 0;
savexp = *xp;
*xp += deltax;
deval(b,&w);
*xp = savexp;
if(w == BADVAL)
return 0;
if(w < 10* scale)
return 1;
*xp += 0.5 * deltax;
deval(b,&mid);
*xp = savexp;
if(mid == BADVAL)
return 0;
if(mid < 10* scale)
return 1;
return 0;
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists