| 1 | #ifndef lint |
| 2 | static char sccsid[] = "@(#)crtplot.c 4.1 (Berkeley) %G%"; |
| 3 | #endif |
| 4 | |
| 5 | /* |
| 6 | This plotting routine interprets plot commands and outputs them onto |
| 7 | intelligent terminals (ie, terminals with clear screen and cursor |
| 8 | addressability. It uses the curses library. It should be compiled |
| 9 | as follows: |
| 10 | cc crtdriver.c crtplot.c -lcurses -ltermcap -lm |
| 11 | Note: This requires as slightly modified driver from the standard driver |
| 12 | because some function names conflicted with the curses library. |
| 13 | (That's what you get when you have a flat name space!) |
| 14 | */ |
| 15 | |
| 16 | |
| 17 | #include <curses.h> |
| 18 | #include <math.h> |
| 19 | #include <signal.h> |
| 20 | |
| 21 | |
| 22 | /* These map from plot routine coordinates to screen coordinates. */ |
| 23 | #define scaleX(x) (int) ((x-lowX)*rangeX + 0.5) |
| 24 | #define scaleY(y) (int) (LINES-0.5 - ((y-lowY)*rangeY)) |
| 25 | |
| 26 | #define plot_movech(y, x, ch) { plot_move(x, y); plot_addch(ch); } |
| 27 | |
| 28 | |
| 29 | static double lowX, rangeX; /* min and range of x */ |
| 30 | static double lowY, rangeY; /* min and range of y */ |
| 31 | static int lastX, lastY; /* last point plotted */ |
| 32 | |
| 33 | |
| 34 | char *getenv(); |
| 35 | extern char _putchar(); |
| 36 | |
| 37 | /* This routine just moves the cursor. */ |
| 38 | screen_move(y, x) |
| 39 | int x,y; |
| 40 | { |
| 41 | /* must check for automatic wrap at last col */ |
| 42 | if (!AM || (y < LINES -1) || (x < COLS -1)) { |
| 43 | mvcur(lastY, lastX, y, x); |
| 44 | lastY = y; |
| 45 | lastX = x; |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | |
| 50 | /* This routine assumes the cursor is positioned correctly. */ |
| 51 | plot_addch(ch) |
| 52 | char ch; |
| 53 | { |
| 54 | putchar(ch); |
| 55 | if (++lastX >= COLS) { |
| 56 | if (AM) { |
| 57 | lastX = 0; |
| 58 | lastY++; |
| 59 | } else { |
| 60 | lastX = COLS - 1; |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | |
| 66 | |
| 67 | |
| 68 | /* See the curses manual for what is been done and why. */ |
| 69 | openpl() |
| 70 | { |
| 71 | char *sp; |
| 72 | int closepl(); |
| 73 | |
| 74 | gettmode(); |
| 75 | if (sp=getenv("TERM")) |
| 76 | setterm(sp); |
| 77 | signal(SIGINT, closepl); |
| 78 | |
| 79 | } |
| 80 | |
| 81 | |
| 82 | |
| 83 | |
| 84 | closepl() |
| 85 | { |
| 86 | signal(SIGINT, SIG_IGN); |
| 87 | /* Leave cursor at top of screen. */ |
| 88 | mvcur(LINES-1, COLS-1, 0, 0); |
| 89 | endwin(); |
| 90 | exit(0); |
| 91 | } |
| 92 | |
| 93 | |
| 94 | |
| 95 | plot_move(x,y) |
| 96 | int x, y; |
| 97 | { |
| 98 | screen_move(scaleY(y), scaleX(x)); |
| 99 | } |
| 100 | |
| 101 | |
| 102 | |
| 103 | line(x0, y0, x1, y1) |
| 104 | int x0, y0, x1, y1; |
| 105 | { |
| 106 | plot_movech(y0, x0, '*'); |
| 107 | dda_line('*', scaleX(x0), scaleY(y0), scaleX(x1), scaleY(y1)); |
| 108 | } |
| 109 | |
| 110 | label(str) |
| 111 | char *str; |
| 112 | { |
| 113 | reg i, length; |
| 114 | int strlen(); |
| 115 | |
| 116 | if ( (length=strlen(str)) > (COLS-lastX) ) |
| 117 | length = COLS - lastX; |
| 118 | for (i=0; i<length; ++i) |
| 119 | plot_addch(str[i]); |
| 120 | } |
| 121 | |
| 122 | plot_erase() |
| 123 | { |
| 124 | /* |
| 125 | Some of these functions probably belong in openpl(). However, if the |
| 126 | input is being typed in, putting them in openpl would not work |
| 127 | since "noecho", etc would prevent (sort of) input. Notice that |
| 128 | the driver calls openpl before doing anything. This is actually |
| 129 | wrong, but it is what whoever originally wrote the driver decided |
| 130 | to do. (openpl() in libplot does nothing -- that is the main problem!) |
| 131 | */ |
| 132 | _puts(TI); |
| 133 | _puts(VS); |
| 134 | |
| 135 | noecho(); |
| 136 | nonl(); |
| 137 | tputs(CL, LINES, _putchar); |
| 138 | mvcur(0, COLS-1, LINES-1, 0); |
| 139 | lastX = 0; |
| 140 | lastY = LINES-1; |
| 141 | } |
| 142 | |
| 143 | |
| 144 | point(x, y) |
| 145 | int x,y; |
| 146 | { |
| 147 | plot_movech(y, x, '*'); |
| 148 | } |
| 149 | |
| 150 | |
| 151 | cont(x, y) |
| 152 | int x,y; |
| 153 | { |
| 154 | dda_line('*', lastX-1, lastY, scaleX(x), scaleY(y)); |
| 155 | } |
| 156 | |
| 157 | |
| 158 | space(x0, y0, x1, y1) |
| 159 | int x0, y0, x1, y1; |
| 160 | { |
| 161 | lowX = (double) x0; |
| 162 | lowY = (double) y0; |
| 163 | rangeX = COLS/(double) (x1 - x0); |
| 164 | rangeY = LINES/(double) (y1 - y0); |
| 165 | } |
| 166 | |
| 167 | |
| 168 | linemod(string) |
| 169 | char *string; |
| 170 | { |
| 171 | } |
| 172 | |
| 173 | |
| 174 | |
| 175 | /* See Neuman & Sproul for explanation and rationale. */ |
| 176 | /* Does not plot first point -- assumed that it is already plotted */ |
| 177 | dda_line(ch, x0, y0, x1, y1) |
| 178 | char ch; |
| 179 | int x0, y0, x1, y1; /* already transformed to screen coords */ |
| 180 | { |
| 181 | int length, i; |
| 182 | double deltaX, deltaY; |
| 183 | double x, y; |
| 184 | double floor(); |
| 185 | int abs(); |
| 186 | |
| 187 | length = abs(x1 - x0); |
| 188 | if (abs(y1 -y0) > length) |
| 189 | length = abs(y1 - y0); |
| 190 | |
| 191 | if (length == 0) |
| 192 | return; |
| 193 | |
| 194 | deltaX = (double) (x1 - x0)/(double) length; |
| 195 | deltaY = (double) (y1 - y0)/(double) length; |
| 196 | |
| 197 | x = (double) x0 + 0.5; |
| 198 | y = (double) y0 + 0.5; |
| 199 | |
| 200 | for (i=0; i < length; ++i) |
| 201 | { |
| 202 | x += deltaX; |
| 203 | y += deltaY; |
| 204 | screen_move((int) floor(y), (int) floor(x)); |
| 205 | plot_addch(ch); |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | |
| 210 | circle (xc,yc,r) |
| 211 | int xc,yc,r; |
| 212 | { |
| 213 | arc(xc,yc, xc+r,yc, xc-r,yc); |
| 214 | arc(xc,yc, xc-r,yc, xc+r,yc); |
| 215 | } |
| 216 | |
| 217 | |
| 218 | /* should include test for equality? */ |
| 219 | #define side(x,y) (a*(x)+b*(y)+c > 0.0 ? 1 : -1) |
| 220 | |
| 221 | arc(xc,yc,xbeg,ybeg,xend,yend) |
| 222 | int xc,yc,xbeg,ybeg,xend,yend; |
| 223 | { |
| 224 | double r, radius, costheta, sintheta; |
| 225 | double a, b, c, x, y, tempX; |
| 226 | int right_side; |
| 227 | |
| 228 | xbeg -= xc; ybeg -= yc; |
| 229 | xend -= xc; yend -= yc; |
| 230 | |
| 231 | /* probably should check that arc is truely circular */ |
| 232 | /* Note: r is in screen coordinates. */ |
| 233 | r = sqrt( rangeX*rangeX*xbeg*xbeg + rangeY*rangeY*ybeg*ybeg); |
| 234 | |
| 235 | /* |
| 236 | This method is reasonably efficient, clean, and clever. |
| 237 | The easy part is generating the next point on the arc. This is |
| 238 | done by rotating the points by the angle theta. Theta is chosen |
| 239 | so that no rotation will cause more than one pixel of a move. |
| 240 | This corresponds to a triangle having 'x side' of r and 'y side' of 1. |
| 241 | The rotation is done (way) below inside the loop. |
| 242 | */ |
| 243 | if (r <= 1.0) { |
| 244 | /* radius is mapped to length < 1*/ |
| 245 | point(xc,yc); |
| 246 | return; |
| 247 | } |
| 248 | |
| 249 | radius = sqrt(r*r + 1.0); |
| 250 | sintheta = 1.0/radius; |
| 251 | costheta = r/radius; |
| 252 | |
| 253 | /* |
| 254 | The hard part of drawing an arc is figuring out when to stop. |
| 255 | This method works by drawing the line from the beginning point |
| 256 | to the ending point. This splits the plane in half, with the |
| 257 | arc that we wish to draw on one side of the line. If we evaluate |
| 258 | side(x,y) = a*x + b*y + c, then all of the points on one side of the |
| 259 | line will result in side being positive, and all the points on the |
| 260 | other side of the line will result in side being negative. |
| 261 | |
| 262 | We want to draw the arc in a counter-clockwise direction, so we |
| 263 | must find out what the sign of "side" is for a point which is to the |
| 264 | "right" of a line drawn from "beg" to "end". A point which must lie |
| 265 | on the right is [xbeg + (yend-ybeg), ybeg - (xend-xbeg)]. (This |
| 266 | point is perpendicular to the line at "beg"). |
| 267 | |
| 268 | Thus, we compute "side" of the above point, and then compare the |
| 269 | sign of side for each new point with the sign of the above point. |
| 270 | When they are different, we terminate the loop. |
| 271 | */ |
| 272 | |
| 273 | a = (double) (yend - ybeg); |
| 274 | b = (double) (xend - xbeg); |
| 275 | c = (double) (yend*xbeg - xend*ybeg); |
| 276 | right_side = side(xbeg + (yend-ybeg), |
| 277 | ybeg - (xend-xbeg) ); |
| 278 | |
| 279 | x = xbeg; |
| 280 | y = ybeg; |
| 281 | plot_move(xbeg+xc, ybeg+yc); |
| 282 | do { |
| 283 | dda_line('*',lastX-1, lastY, scaleX(xc + x), scaleY(yc + y )); |
| 284 | /* |
| 285 | screen_move( scaleY(yc + y), scaleX(xc + x) ); |
| 286 | plot_addch('*'); |
| 287 | */ |
| 288 | tempX = x; |
| 289 | x = x*costheta - y*sintheta; |
| 290 | y = tempX*sintheta + y*costheta; |
| 291 | } while( side(x,y) == right_side ); |
| 292 | } |