home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
drdobbs
/
1991
/
04
/
oliver
/
fracdraw.c
< prev
next >
Wrap
C/C++ Source or Header
|
1991-01-23
|
30KB
|
715 lines
/*****************************************************************
FRACDRAW.C -- Drawing with fractals
Copyright 1990 by Dick Oliver, R1 Box 5140, Morrisville, VT 05661
A program for interactive fractal drawing.
You may copy, modify, and recompile this source code as you please,
as long as you do not sell it or any product produced from any part of it.
The author makes no claims as to readability or suitability for a
particular task, but I'll be happy to give advice and assistance.
*****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <graph.h>
#include <math.h>
#include <ctype.h>
#include <bios.h>
#include "maple.h" /* Include file for initial template definition */
/* Numerical constants */
#define PI 3.141592
#define TWOPI 6.283853
#define HALFPI 1.570796
#define ALMOSTZERO 0.00002
#define MAXSIZE 0.998
#define MAXINT 32767
/* Keyboard constants */
#define ENTER 13
#define BACKSPACE 8
#define ESC 27
#define END -'O'
#define HOME -'G'
#define INSERT -'R'
#define DELETE -'S'
#define TAB 9
#define UNTAB -15
#define UP -'H'
#define DN -'P'
#define LT -'K'
#define RT -'M'
#define NULLKEY '^'
/* Generic getch() replacement */
#define geta if ((a = getch()) == 0) a = -getch();\
else if (a > 0) a = toupper(a)
/* Main menu */
#define MENUMSG "ACTION KEY\n"\
" Draw D\n"\
" Paint P\n"\
" Both B\n"\
" Next Part Tab\n"\
" NextPoint ~\n"\
" Insert Ins\n"\
" Delete Del\n"\
" Grow *\n"\
" Shrink /\n"\
" Spin + -\n"\
" Skew ; \'\n"\
" Squish [\n"\
" Stretch ]\n"\
" Quit ESC\n\n\n"\
" DRAWING\n WITH\n FRACTALS\n\n"\
" (C) 1990 by\n Dick Oliver"
#define MENUKEYS {'D', 'P', 'B', TAB, '`', INSERT, DELETE, \
'*', '/', '+', ';', '[', ']', ESC}
#define MENUWD 15 /* width of menu in characters */
#define NMENUITEMS 14 /* number of menu items */
#define HAND 64 /* size of handle in pixels */
#define MAXPTS 19 /* max. no. of points on seed */
#define MAXTRANS 19 /* max. no. of parts of template */
/* template variables:
spininc is the amount to rotate (in radians) each time spin is picked
sizeinc is the amount to grow or shrink
ra, rb, rc, rd, rmx, and rmy are the reverse of the initial tranformation
fa, fb, fc, and fd are the tranformations computed from
sizex, sizey, spinx, and spiny
movex and movey are the translation part of the transformations
asprat is the aspect ratio (always 1 for VGA)
fx and fy are used for various temporary storage purposes
x and y are the points on the seed polygon */
float sizeinc = 0.16, spininc = PI / 16,
ra, rb, rc, rd, rmx, rmy,
fa[MAXTRANS + 1], fb[MAXTRANS + 1], fc[MAXTRANS + 1], fd[MAXTRANS + 1],
sizex[MAXTRANS + 1] = {1, SIZEX}, sizey[MAXTRANS + 1] = {1, SIZEY},
spinx[MAXTRANS + 1] = {0, SPINX}, spiny[MAXTRANS + 1] = {0, SPINY},
movex[MAXTRANS + 1] = {CENTERX, MOVEX},
movey[MAXTRANS + 1] = {CENTERY, MOVEY},
asprat, fx, fy, x[MAXPTS] = {SEEDX}, y[MAXPTS] = {SEEDY};
/* menu vars */
char a, menukeys[] = MENUKEYS;
/* xtop, etc. are the points on the handle
drawclr is the text and handle color
xx, yy, midx, and midy are used for various pixel-shuffling operations
menuitem is the current menu choice
hand is the size of the handle in pixels
sk is used to keep track of when to re-sketch the template
xo and yo are the current template corners for quick erasing
thispt & thistran are the current point/part
npts is the number of point, ntrans is the number of parts,
level is the number of levels of detail to draw or paint
color determines the color to make each part at each level
*/
int xtop, ytop, xctr, yctr, xlft, ylft, xrgt, yrgt,
drawclr, i, j, xx, yy, midx, midy, menuitem = 0, hand = HAND, sk,
xo[MAXTRANS + 1][MAXPTS], yo[MAXTRANS + 1][MAXPTS], moveinc = 16,
thispt = 0, thistran = 0, npts = NPOINTS, ntrans = NTRANS,
level = NLEVELS - 1, color[MAXTRANS][NLEVELS] = COLOR;
/* ptmode means we're in "point" mode rather than "part" mode*/
enum {OFF, ON} ptmode = OFF;
/* standard Microsoft video variables */
struct videoconfig vc;
long palette[16] = PALETTE;
/* these function prototypes are needed to avoid confusion about parameter
types (most of the functions aren't prototyped) */
void draw(float a, float b, float c, float d, float mx, float my, int iter);
void warp(float spinxinc, float spinyinc, float sizexinc, float sizeyinc);
main()
{
hello(); /* initialize everything */
while(1) /* the main event-processing loop */
{
geta;
switch(a) /* what shall we do now? */
{ case BACKSPACE:
case ' ':
/* move ">" to new menu item */
_settextposition(menuitem + 2, 1);
_outtext(" ");
if (a == ' ')
{ if (++menuitem == NMENUITEMS) menuitem = 0;
}
else if (--menuitem < 0) menuitem = NMENUITEMS - 1;
_settextposition(menuitem + 2, 1);
_outtext(">");
break;
case ENTER: /* pick a menu item */
ungetch(menukeys[menuitem]);
break;
default:
sk = 0;
switch(a)
{ case LT:
case RT:
case DN:
case UP:
/* move a point or part of the template */
xx = 0, yy = 0;
switch (a)
{ case LT: xx = -moveinc; break;
case RT: xx = moveinc; break;
case UP: yy = -moveinc; break;
case DN: yy = moveinc; break;
}
if (!ptmode && (thistran == 0))
*movex += xx, *movey += yy;
else
{ if (ptmode)
{ x[thispt] += xx * ra + yy * rb;
y[thispt] += xx * rc + yy * rd;
}
else movex[thistran] += xx * ra + yy * rb,
movey[thistran] += xx * rc + yy * rd;
}
break;
case '/': /* Shrink */
if (ptmode)
{ fx = 1 / (sizeinc + 1);
warp(0.0, 0.0, fx, fx);
}
else
{ if ((sizex[thistran] /= sizeinc + 1) == 0)
sizex[thistran] = ALMOSTZERO;
if ((sizey[thistran] /= sizeinc + 1) == 0)
sizey[thistran] = ALMOSTZERO;
computef(thistran);
}
break;
case '*': /* Grow */
if (ptmode) warp(0.0, 0.0,sizeinc+1, sizeinc+1);
else
{ if (((sizex[thistran] *= sizeinc + 1)
> MAXSIZE)
&& (thistran > 0))
sizex[thistran] = MAXSIZE;
if (((sizey[thistran] *= sizeinc + 1)
> MAXSIZE)
&& (thistran > 0))
sizey[thistran] = MAXSIZE;
computef(thistran);
}
break;
case '[': /* Squish x-axis */
if (ptmode) warp(0.0, 0.0, 1/(sizeinc + 1), 1.0);
else
{ if ((sizex[thistran] /= (sizeinc + 1)) == 0)
sizex[thistran] = ALMOSTZERO;
computef(thistran);
}
break;
case ']': /* Stretch x-axis */
if (ptmode) warp(0.0, 0.0, sizeinc + 1, 1.0);
else
{ if (((sizex[thistran] *= sizeinc + 1)
> MAXSIZE)
&& (thistran > 0))
sizex[thistran] = MAXSIZE;
computef(thistran);
}
break;
case '-': /* Spin counter-clockwise */
if (ptmode) warp(-spininc, -spininc, 1.0, 1.0);
else
{ if ((spinx[thistran] -= spininc) < 0)
spinx[thistran] += TWOPI;
if ((spiny[thistran] -= spininc) < 0)
spiny[thistran] += TWOPI;
computef(thistran);
}
break;
case '+': /* Spin clockwise */
if (ptmode) warp(spininc, spininc, 1.0, 1.0);
else
{ if ((spinx[thistran] += spininc) >= TWOPI)
spinx[thistran] -= TWOPI;
if ((spiny[thistran] += spininc) >= TWOPI)
spiny[thistran] -= TWOPI;
computef(thistran);
}
break;
case ';': /* Skew x-axis counter-clockwise */
if (ptmode) warp(spininc, 0.0, 1.0, 1.0);
else
{ if ((spinx[thistran] += spininc) >= TWOPI)
spinx[thistran] -= TWOPI;
computef(thistran);
}
break;
case '\'': /* Skew x-axis clockwise */
if (ptmode) warp(-spininc, 0.0, 1.0, 1.0);
else
{ if ((spinx[thistran] -= spininc) < 0)
spinx[thistran] += TWOPI;
computef(thistran);
}
break;
case '`': /* NextPoint */
if (ptmode) ++thispt;
else ptmode = ON, thistran = 0;
if (thispt >= npts) thispt = 0;
break;
default:
switch(a)
{ case TAB: /* Next part */
if (ptmode)
{ ptmode = OFF;
midpoint();
}
else
{ if (++thistran > ntrans) thistran = 0;
}
break;
case 'D':
case 'P':
case 'B': /* Draw and/or Paint */
_clearscreen(_GCLEARSCREEN);
_setcliprgn(0, 0,
vc.numxpixels - 1,
vc.numypixels - 1);
_setcolor(**color);
if ((a == 'D') || (a == 'B'))
draw(*fa, *fb, *fc, *fd,
*movex, *movey, level);
if ((a == 'P') || (a == 'B')) paint();
printf("\7");
getch();
_clearscreen(_GCLEARSCREEN);
printmenu();
break;
case ESC: /* Quit */
_setvideomode(_DEFAULTMODE);
printf("Seeyalater!");
exit(0);
case INSERT: /* Insert a point or part */
if (ptmode)
{ if (npts < MAXPTS)
{ erase();
++npts;
for (i = npts - 1; i > thispt; i--)
x[i] = x[i - 1],
y[i] = y[i - 1];
if (thispt > 0)
xx = x[thispt - 1],
yy = y[thispt - 1];
else xx = x[npts - 1],
yy = y[npts - 1];
if ((xx == x[thispt]) &&
(yy == y[thispt]))
x[thispt] += moveinc,
y[thispt] += moveinc;
else x[thispt] =
(xx + x[thispt]) / 2,
y[thispt] =
(yy + y[thispt]) / 2;
}
else printf("\7");
}
else
{ if ((ntrans < MAXTRANS) && (ntrans > 0))
{ ++ntrans;
for (i = ntrans; i > thistran; i--)
{ if (i > 1)
{ movex[i] = movex[i - 1];
movey[i] = movey[i - 1];
spinx[i] = spinx[i - 1];
spiny[i] = spiny[i - 1];
sizex[i] = sizex[i - 1];
sizey[i] = sizey[i - 1];
for (j = 0; j < NLEVELS;
j++)
color[i - 1][j] =
color[i - 2][j];
fa[i] = fa[i - 1];
fb[i] = fb[i - 1];
fc[i] = fc[i - 1];
fd[i] = fd[i - 1];
}
else
{ spinx[1] = 0;
spiny[1] = 0;
sizex[1] = sizey[1];
computef(1);
}
}
if (thistran == 0) thistran = 1,i = 1;
if (thistran > 1) j = thistran - 1;
else j = ntrans;
if ((movex[i] == movex[j]) &&
(movey[i] == movey[j]))
movex[i] += moveinc,
movey[i] += moveinc;
else movex[i] =
(movex[i] + movex[j]) / 2,
movey[i] =
(movey[i] + movey[j]) / 2;
}
else
{ if (ntrans == 0) thistran = ++ntrans;
else printf("\7");
}
}
break;
case DELETE: /* Delete a point or part */
erase();
if (ptmode)
{ if (npts > 1)
{ if (thispt == --npts) --thispt;
else for (i = thispt; i < npts; i++)
x[i] = x[i + 1],
y[i] = y[i + 1];
}
else printf("\7");
}
else
{ if (ntrans > 0)
{ --ntrans;
}
else printf("\7");
if (ntrans > 0)
{ if (thistran == 0) thistran = 1;
else
for (i = thistran;
i <= ntrans; i++)
{ movex[i] = movex[i + 1];
movey[i] = movey[i + 1];
spinx[i] = spinx[i + 1];
spiny[i] = spiny[i + 1];
sizex[i] = sizex[i + 1];
sizey[i] = sizey[i + 1];
for (j = 0; j < NLEVELS;
j++)
color[i - 1][j] =
color[i][j];
fa[i] = fa[i + 1];
fb[i] = fb[i + 1];
fc[i] = fc[i + 1];
fd[i] = fd[i + 1];
}
}
if (thistran > ntrans) --thistran;
}
}
sk = 1;
}
erase();
sketch(sk);
}
}
}
/* midpoint() -- find the center of the seed */
midpoint()
{ int xx, yy;
midx = 0, midy = 0;
for (i = 0; i < npts; i++) midx += x[i], midy += y[i];
midx /= npts, midy /= npts;
for (i = 0; i < npts; i++) x[i] -= midx, y[i] -= midy;
for (i = 1; i <= ntrans; i++)
{ xx = midx * fa[i] + midy * fb[i];
yy = midx * fc[i] + midy * fd[i];
movex[i] -= midx - xx;
movey[i] -= midy - yy;
}
xx = midx * *fa + midy * *fb,
yy = midx * *fc + midy * *fd;
*movex += xx,
*movey += yy;
}
/* compute the affine transformations expressed by the template */
computef(int i)
{ fa[i] = sizex[i] * cos(spinx[i]);
fb[i] = -sizey[i] * sin(spiny[i]);
fc[i] = sizex[i] * sin(spinx[i]);
fd[i] = sizey[i] * cos(spiny[i]);
if (i == 0)
{ if ((fx = *fa * *fd - *fb * *fc) == 0) fx = 0.001;
ra = *fd / fx;
rb = - *fb / fx;
rc = - *fc / fx;
rd = *fa / fx;
}
}
/* warp the seed shape (used to skew, squish, and stretch) */
void warp(float spinxinc, float spinyinc, float sizexinc, float sizeyinc)
{ float a, b, c, d, dsizex, dsizey, dspinx, dspiny;
dspinx = spinxinc + *spinx;
dspiny = spinyinc + *spiny;
dsizex = sizexinc * *sizex;
dsizey = sizeyinc * *sizey;
a = cos(dspinx) * dsizex;
b = -sin(dspiny) * dsizey;
c = sin(dspinx) * dsizex;
d = cos(dspiny) * dsizey;
for (i = 0; i < MAXPTS; i++)
{ fx = x[i] * a + y[i] * b;
fy = x[i] * c + y[i] * d;
x[i] = fx * ra + fy * rb;
y[i] = fx * rc + fy * rd;
}
}
/* sketch() -- sketch the template and handle
Note that the handle shows not only which part you are on,
but also the relative size and orientation of both axes.
*/
sketch(int all)
{ int i, j, x1, y1, inc, tran0 = 0;
float x2, y2, a, b, c, d, mx, my;
inc = hand;
if (ptmode)
{ tran0 = 1;
inc *= *sizey / 2;
fx = x[thispt], fy = y[thispt];
x1 = fx * *fa + fy * *fb + *movex;
y1 = fx * *fc + fy * *fd + *movey;
xctr = x1, yctr = (y1 + inc) * asprat;
xtop = x1, ytop = (y1 - inc) * asprat;
y1 *= asprat;
xlft = x1 - inc, ylft = y1;
xrgt = x1 + inc, yrgt = y1;
}
else
{ if (thistran == 0) x1 = 0, y1 = 0, tran0 = 1;
else x1 = movex[thistran], y1 = movey[thistran];
if (tran0) fx = x1, fy = y1 - inc;
else fx = x1 - inc * fb[thistran],
fy = y1 - inc * fd[thistran];
xtop = fx * *fa + fy * *fb + *movex;
ytop = (fx * *fc + fy * *fd + *movey) * asprat;
xctr = x1 * *fa + y1 * *fb + *movex;
yctr = (x1 * *fc + y1 * *fd + *movey) * asprat;
inc /= 2;
if (tran0) fx = x1 - inc, fy = y1;
else fx = x1 - inc * fa[thistran],
fy = y1 - inc * fc[thistran];
xlft = fx * *fa + fy * *fb + *movex;
ylft = (fx * *fc + fy * *fd + *movey) * asprat;
if (tran0) fx = x1 + inc, fy = y1;
else fx = x1 + inc * fa[thistran],
fy = y1 + inc * fc[thistran];
xrgt = fx * *fa + fy * *fb + *movex;
yrgt = (fx * *fc + fy * *fd + *movey) * asprat;
}
_setcolor(**color);
for (j = 0; j < npts; j++)
{ x1 = x[j] * *fa + y[j] * *fb + *movex;
y1 = (x[j] * *fc + y[j] * *fd + *movey) * asprat;
(*xo)[j] = x1, (*yo)[j] = y1;
if (j == 0) _moveto(x1, y1);
else _lineto(x1, y1);
}
_lineto(**xo, **yo);
for (i = 1; i <= ntrans; i++)
{ if ((thistran == 0) || (i == thistran) || (all))
{ _setcolor(color[i - 1][level]);
a = fa[i] * *fa + fc[i] * *fb;
b = fb[i] * *fa + fd[i] * *fb;
c = fa[i] * *fc + fc[i] * *fd;
d = fb[i] * *fc + fd[i] * *fd;
mx = movex[i] * *fa + movey[i] * *fb + *movex;
my = movex[i] * *fc + movey[i] * *fd + *movey;
for (j = 0; j < npts; j++)
{ x1 = a * x[j] + b * y[j] + mx;
y1 = (c * x[j] + d * y[j] + my) * asprat;
if (j == 0) _moveto(x1, y1);
else _lineto(x1, y1);
xo[i][j] = x1, yo[i][j] = y1;
}
_lineto(*(xo[i]), *(yo[i]));
}
}
_setcolor(drawclr);
_moveto(xtop, ytop);
_lineto(xctr, yctr);
_moveto(xlft, ylft);
_lineto(xrgt, yrgt);
}
/* erase the template */
erase()
{ _setcolor(0);
_moveto(**xo, **yo);
for (i = 1; i < npts; i++) _lineto((*xo)[i], (*yo)[i]);
_lineto(**xo, **yo);
for (i = 1; i <= ntrans; i++)
{ if ((thistran == 0) || (i == thistran))
{ _moveto(*(xo[i]), *(yo[i]));
for (j = 0; j < npts; j++) _lineto(xo[i][j], yo[i][j]);
_lineto(*(xo[i]), *(yo[i]));
}
}
_moveto(xtop, ytop);
_lineto(xctr, yctr);
_moveto(xlft, ylft);
_lineto(xrgt, yrgt);
}
/* This function uses the "Chaos Game", or "random iteration" algorithm
to paint the "infinite-level" fractal on the screen.
*/
paint()
{ int i, j, p[MAXTRANS], tc, tp, ci[NLEVELS], cc = 0, mx, my;
unsigned long ct = COUNT;
float x1 = 0.0, y1 = 0.0, x2, y2 = 0, sx[MAXTRANS], sy[MAXTRANS];
mx = *movex, my = *movey;
/* First, we need to compute the relative area of each part of the
template. This is done by comparing the size of the determinants
of the matrix (a,b,c,d). These weights are then used to decide
how often to visit each part--big parts get more visits than small
ones, giving the overall fractal an even density.
*/
for (i = 1; i <= ntrans; i++)
y2 += (sx[i - 1] =
fabs(fa[i] * fd[i] - fb[i] * fc[i]));
if (y2 == 0) y2 = 0.01;
x2 = MAXINT / y2;
j = 0;
for (i = 0; i < ntrans; i++)
{ if ((xx = sx[i] * x2) == 0) xx = 1;
p[i] = (j += xx);
}
/* We skip the first eight points on our journey, because it may take
that long to settle down from wherever we started onto the fractal.
*/
for (j = 0; j < 8; j++)
{ i = rand() % ntrans + 1;
x2 = x1 * fa[i] + y1 * fb[i] + movex[i];
y2 = x1 * fc[i] + y1 * fd[i] + movey[i];
x1 = x2, y1 = y2;
ci[cc] = i;
if (++cc == level) cc = 0;
}
/* Now we put it on the screen. The cc, tc, and ci variables are used
to determine coloring. At each iteration, we choose a level at
random out of all the levels we will be coloring. We then find the
color for that level based on which part was "visited" that many
iterations ago. How does this work? Each iteration of the orbit
goes from a point on the "whole" to a point on a "part". Therefore,
if we were in part #3 one iteration ago, we are now in a level 1
reflection of part #3 within the current part. If we were at part #5
two cycles ago, we are now painting a point within a level 2
reflection of part #5, and so on. */
while(!kbhit() && (--ct != 0))
{ j = rand();
for (i = 0; i < ntrans; i++) if (j < p[i]) break;
i++;
x2 = x1 * fa[i] + y1 * fb[i] + movex[i];
y2 = x1 * fc[i] + y1 * fd[i] + movey[i];
x1 = x2, y1 = y2;
ci[cc] = i - 1;
j = rand() % level;
if ((i = cc - j) < 0) i += level;
if ((tc = color[ci[i]][j + 1]) > 0)
{ _setcolor(tc);
_setpixel((int) (x2 * *fa + y2 * *fb + mx),
(int) ((x2 * *fc + y2 * *fd + my) * asprat));
}
if (++cc == level) cc = 0;
}
}
/* This function uses the "successive approximation" algorithm to draw
the fractal.
*/
void draw(float a, float b, float c, float d, float mx, float my, int iter)
{ int i;
/* When we start drawing, iter is the number of levels to draw.
Each time we go down one level, we decrement iter.
*/
iter--;
/* If user hits ESC, pass that keypress up through the recursive
calls until we get back to the main procedure.
*/
if (kbhit() && (getch() == ESC))
{ ungetch(ESC);
return;
}
/* Draw a reflection of the seed polygon using the current
transformation */
for (i = 0; i < npts; i++)
{ fx = x[i] * a + y[i] * b + mx;
fy = x[i] * c + y[i] * d + my;
if (i == 0)
{ xx = fx, yy = fy;
_moveto((int) fx, (int) (fy * asprat));
}
else _lineto((int) fx, (int) (fy * asprat));
}
_lineto((int) xx, (int) (yy * asprat));
/* If iter has counted all the way down to zero, don't draw the next
deepest level, but back out one level instead */
if (iter < 0) return;
{ /* Call draw recursively for each transformation, drawing the
next deepest level of each part */
for (i = 1; i <= ntrans; i++)
{ _setcolor(color[i - 1][level - iter]);
draw(fa[i] * a + fc[i] * b,
fb[i] * a + fd[i] * b,
fa[i] * c + fc[i] * d,
fb[i] * c + fd[i] * d,
a * movex[i] + b * movey[i] + mx,
c * movex[i] + d * movey[i] + my,
iter);
}
}
}
/* display the menu */
printmenu()
{ _settextwindow(1, vc.numtextcols - MENUWD,
vc.numtextrows, vc.numtextcols);
_clearscreen(_GWINDOW);
_outtext(MENUMSG);
_settextposition(menuitem + 2, 1);
_outtext(">");
_setcliprgn(0, 0, vc.numxpixels - (MENUWD + 1) *
(vc.numxpixels / vc.numtextcols) - 1,
vc.numypixels - 1);
}
/* initialize everything */
hello()
{ if (!_setvideomode(_VRES16COLOR))
{ printf("Can't set video mode. VGA required.");
}
_getvideoconfig(&vc);
_remapallpalette(palette);
_wrapon(_GWRAPOFF);
_clearscreen(_GCLEARSCREEN);
printmenu();
asprat = (float) (4 * vc.numypixels) / (float) (3 * vc.numxpixels);
drawclr = vc.numcolors - 1;
for (i = 0; i <= ntrans; i++) computef(i);
sketch(0);
}