This is DrawView.m in view mode; [Download] [Up]
/* DrawView.m part of RatMandel
* draws the Mandelbrot set of rational quadratic functions
* Author: William Gilbert
* Written: May 1991 and last modified Dec 1992
*/
#import "DrawView.h"
#define PRINTED_SIZE 5.6 // size of printed window in inches
#define LINE_WIDTH_FACTOR 1.1 // fudge factor to avoid aliasing
#define INF 10e20
#define A_PLANE 0
#define A_INVERSE_PLANE 1
#define B_PLANE 2
@implementation DrawView
- appDidInit:sender
{
[notesText setOpaque:YES];
[notesText setMonoFont:NO];
[notesText setFont:[Font newFont:"Ohlfs" size:11.0]];
[self presetRing:sender];
[self getParameters:&displayplane xr:&xr yr:&yr xs:&xs ys:&ys
xmin:&xmin ymin:&ymin xmax:&xmax ymax:&ymax
iterates:&iterates cycle:&cycle close:&close];
[[PrintPanel new] setAccessoryView:accessoryBox];
lineCompleted = 0;
return self;
}
- start:sender
{
running = YES;
[self display];
return self;
}
- clearNotes:sender
{
[notesText selectAll:nil];
[notesText delete:nil];
return self;
}
- lastCoords:sender
{
[self setParameters:displayplane xr:xr yr:yr xs:xs ys:ys
xmin:xmin ymin:ymin xmax:xmax ymax:ymax
iterates:iterates cycle:cycle close:close];
[self clearNotes:sender];
[self lockFocus];
PSnewinstance();
[self unlockFocus];
return self;
}
- presetMandelbrot:sender
{
[notesText setText:"\n Mandelbrot Set\n ==============\n The critical point -1 is also fixed.\n F(z) is conjugate to\n z -> z^2 + c\n\n This is not the usual\nview of the Mandelbrot\nset. See Mandelbrot\nPlate 189.\n"];
[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:2.0 ys:0.0
xmin:-2.0 ymin:-3.0 xmax:4.0 ymax:3.0
iterates:50 cycle:1 close:0.01];
[self parametersChanged:sender];
return self;
}
- presetSnowshoe:sender
{
[notesText setText:"\n Snowshoe\n ========\n F(z) is conjugate to\n z -> (1/z^2) + c\n\n See J. Milnor, \"Remarks on Quadratic Rational Maps\" Fig 13.\n\n\n F:1 -> -1\n"];
[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:-2.0 ys:0.0
xmin:-8.2 ymin:-4.7 xmax:1.2 ymax:4.7
iterates:1000 cycle:30 close:0.001];
[self parametersChanged:sender];
return self;
}
- presetSpyglass:sender
{
[notesText setText:"\n Spyglass\n ========\n"];
[self setParameters:A_PLANE xr:-1.0 yr:0.4 xs:2.0 ys:0.0
xmin:-2.0 ymin:-3.0 xmax:4.0 ymax:3.0
iterates:50 cycle:1 close:0.01];
[self parametersChanged:sender];
return self;
}
- presetWink:sender
{
[notesText setText:"\n Wink\n ====\n"];
[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:1.7 ys:0.3
xmin:-2.0 ymin:-3.0 xmax:4.0 ymax:3.0
iterates:200 cycle:1 close:0.01];
[self parametersChanged:sender];
return self;
}
- presetTieAndCane:sender
{
[notesText setText:"\n TieAndCane\n ==========\n"];
[self setParameters:B_PLANE xr:-1.0 yr:0.0 xs:50.0 ys:200.0
xmin:-2.0 ymin:-2.0 xmax:2.0 ymax:2.0
iterates:1000 cycle:1 close:0.01];
[self parametersChanged:sender];
return self;
}
- presetNapoleon:sender
{
[notesText setText:"\n Napoleon\n ========\n"];
[self setParameters:A_PLANE xr:-1.0 yr:0.0 xs:3.0 ys:0.0
xmin:-3.0 ymin:-4.0 xmax:5.0 ymax:4.0
iterates:100 cycle:1 close:0.01];
[self parametersChanged:sender];
return self;
}
- presetSperm:sender
{
[notesText setText:"\n Sperm\n =====\n"];
[self setParameters:A_PLANE xr:0.3 yr:-0.4 xs:1.2 ys:0.3
xmin:-9.0 ymin:-16.0 xmax:9.0 ymax:2.0
iterates:200 cycle:1 close:0.1];
[self parametersChanged:sender];
return self;
}
- presetRing:sender
{
[notesText setText:"\n Ring\n ====\n"];
[self setParameters:B_PLANE xr:0.3 yr:-0.4 xs:1.2 ys:0.3
xmin:-4.6 ymin:-4.6 xmax:2.1 ymax:2.1
iterates:200 cycle:1 close:0.1];
[self parametersChanged:sender];
return self;
}
- presetBloodClot:sender
{
[notesText setText:"\n BloodClot\n =========\n Same as Alien, but\nshowing the a-plane\ninstead of the\n(1/a)-plane.\n"];
[self setParameters:A_PLANE xr:0.0 yr:0.0 xs:0.0 ys:0.0
xmin:-1.25 ymin:0.1 xmax:1.25 ymax:2.6
iterates:500 cycle:1 close:0.15];
[self parametersChanged:sender];
return self;
}
- presetAlien:sender
{
[notesText setText:"\n Alien\n =====\n See Madelbrot Plate X and Milnor Fig 12.\n\n The actual Mandelbrot set is obtained by setting close = 0.001; see Yin p. 146.\n"];
[self setParameters:A_INVERSE_PLANE xr:0.0 yr:0.0 xs:0.0 ys:0.0
xmin:-1.02 ymin:-1.02 xmax:1.02 ymax:1.02
iterates:1000 cycle:1 close:0.1];
[self parametersChanged:sender];
return self;
}
- presetLunar:sender
{
[notesText setText:"\n Lunar\n =====\n Same as Alien with cycle = 10\n"];
[self setParameters:A_INVERSE_PLANE xr:0.0 yr:0.0 xs:0.0 ys:0.0
xmin:-1.02 ymin:-1.02 xmax:1.02 ymax:1.02
iterates:1000 cycle:10 close:0.05];
[self parametersChanged:sender];
return self;
}
- presetScallops:sender
{
[notesText setText:"\n Scallops\n ========\n See J. Milnor, \"Remarks on Quadratic Rational Maps\" Fig 11.\n\n\nF:-1 -> 0 -> inf(fixed)\n"];
[self setParameters:A_INVERSE_PLANE xr:0.0 yr:0.0 xs:2.0 ys:0.0
xmin:-1.02 ymin:-1.02 xmax:1.02 ymax:1.02
iterates:1000 cycle:1 close:0.1];
[self parametersChanged:sender];
return self;
}
- (BOOL) continue
// Ignores all mousedown events until Stop is pressed
{
NXEvent *peekedEvent;
peekedEvent = [NXApp peekAndGetNextEvent:NX_MOUSEDOWNMASK];
if (peekedEvent IS NULL)
return YES;
else if (NXPointInRect(&(peekedEvent->location), &stopRect))
{
running = NO;
return NO;
}
else
return YES;
}
- drawSelf: (NXRect *)rect : (int) count
{
NXRect frameRect;
int p, q, // pixel coords
pmax, qmax, // pixels in screen
pstart; // start of line to draw
float x, y, // actual coords of point
dx, dy, // distance between pixels
oldCol, newCol, // colours of adjacent pixels
printerDot, // distance between printer dots
lineWidth, // width 1 on printer is PRINTER_PIXEL_FACTOR
qHeight; // vertical position of line to be drawn
[self getParameters:&displayplane xr:&xr yr:&yr xs:&xs ys:&ys
xmin:&xmin ymin:&ymin xmax:&xmax ymax:&ymax
iterates:&iterates cycle:&cycle close:&close];
sqclose = SQ(close);
invclose = 1/sqclose;
if (lineCompleted IS 0) NXEraseRect(&bounds);
[self getFrame:&frameRect];
if ((NXDrawingStatus IS NX_DRAWING) AND running)
{
[stopButton getFrame:&stopRect];
pmax = (int) frameRect.size.width;
qmax = (int) frameRect.size.height;
dx = (xmax - xmin)/pmax;
dy = (ymax - ymin)/qmax;
PSsetlinewidth(1.0);
for (q = lineCompleted, y = ymin + lineCompleted*dy;
((q <= qmax) AND running); q++, y += dy)
{
for (p = 0, pstart = 0, x = xmin, oldCol = NX_WHITE;
((p <= pmax) AND [self continue]); p++, x += dx)
{
newCol = [self pixelCol:x :y];
if (newCol IS_NOT oldCol)
{
if (oldCol IS_NOT NX_WHITE)
[self drawline:oldCol :pstart :(p-1) :q];
pstart = p;
oldCol = newCol;
}
}
if (oldCol IS_NOT NX_WHITE)
[self drawline:oldCol :pstart :p :q];
[window flushWindow];
}
if (q < qmax)
{
lineCompleted = q-1;
[startButton setTitle:"Cont"];
}
else
{
lineCompleted = 0;
[startButton setTitle:"Start"];
}
}
else if (NXDrawingStatus IS NX_PRINTING)
{
NXEraseRect(&bounds);
pixelSize = 1 + [[printerPopUpButton target]
indexOfItem:[printerPopUpButton title]];
pmax = (int) ((atoi(NXGetDefaultValue("System", "PrinterResolution")))
*PRINTED_SIZE/pixelSize);
qmax = pmax;
lineWidth = frameRect.size.width/pmax;
printerDot = lineWidth/pixelSize;
dx = (xmax - xmin)/pmax;
dy = (ymax - ymin)/qmax;
if (pixelSize > 1)
PSsetlinewidth(lineWidth*LINE_WIDTH_FACTOR);
else
PSsetlinewidth(0);
for (q = 0, y = ymin; (q <= qmax); q++, y += dy)
{
qHeight = (q + 0.5)*lineWidth;
for (p = 0, pstart = 0, x = xmin, oldCol = NX_WHITE; p <= pmax;
p++, x += dx)
{
newCol = [self pixelCol:x :y];
if (newCol IS_NOT oldCol)
{
if (oldCol IS_NOT NX_WHITE)
[self drawline :oldCol :(pstart*lineWidth)
:(p*lineWidth - printerDot) :qHeight];
pstart = p;
oldCol = newCol;
}
}
if (oldCol IS_NOT NX_WHITE)
[self drawline:oldCol :(pstart*lineWidth) :(p*lineWidth)
:qHeight];
[window flushWindow];
}
}
return self;
}
- (void) drawline:(float) col :(float) p1 :(float) p2 :(float) q
{
PSnewpath();
PSmoveto(p1, q);
PSlineto(p2, q);
PSsetgray(col);
PSstroke();
}
- (float) pixelCol:(float) x :(float) y
{
int power = 0;
BOOL separate = YES; // images of critical points are separate
double x1, y1, x2, y2, xx, yy, xa = 0.0, ya = 0.0, xb = 0.0, yb = 0.0,
x0, y0, norma, norm0, norm1, norm2;
switch (displayplane)
{
case A_PLANE:
xa = x;
ya = y;
xb = xr*xa - yr*ya + xs;
yb = xr*ya + yr*xa + ys;
break;
case A_INVERSE_PLANE:
norma = x*x + y*y;
if (norma > 0.0)
{
xa = x/norma;
ya = -y/norma;
}
xb = xr*xa - yr*ya + xs;
yb = xr*ya + yr*xa + ys;
break;
case B_PLANE:
xb = x;
yb = y;
xa = xr*xb - yr*yb + xs;
ya = xr*yb + yr*xb + ys;
break;
}
norma = xa*xa + ya*ya;
if (norma IS 0.0)
return NX_WHITE;
else
{ // F(z) = (1/a)(z + 1/z + b)
x1 = 1.0;
y1 = 0.0;
x2 = -1.0;
y2 = 0.0;
x0 = x1;
y0 = y1;
norm0 = x0*x0 + y0*y0;
norm1 = x1*x1 + y1*y1;
norm2 = x2*x2 + y2*y2;
while (separate AND (power < iterates))
{
++power;
if (norm1 IS 0.0)
{
x1 = INF;
y1 = INF;
}
else
{
xx = x1 + (x1/norm1) + xb;
yy = y1 - (y1/norm1) + yb;
x1 = ((xx*xa + yy*ya)/norma);
y1 = ((yy*xa - xx*ya)/norma);
}
if (norm2 IS 0.0)
{
x2 = INF;
y2 = INF;
}
else
{
xx = x2 + (x2/norm2) + xb;
yy = y2 - (y2/norm2) + yb;
x2 = ((xx*xa + yy*ya)/norma);
y2 = ((yy*xa - xx*ya)/norma);
}
if ((power MOD cycle) IS 0)
{
x0 = x1;
y0 = y1;
norm0 = x0*x0 + y0*y0;
}
norm1 = x1*x1 + y1*y1;
norm2 = x2*x2 + y2*y2;
if ((SQ(x0 - x2) + SQ(y0 - y2) < sqclose) OR
((norm0 > invclose) AND (norm2 > invclose)))
separate = FALSE;
}
if (power < iterates)
return NX_WHITE;
else
return NX_BLACK;
}
}
- setParameters:(int)plane
xr:(float)paramsxr yr:(float)paramsyr
xs:(float)paramsxs ys:(float)paramsys
xmin:(float)paramsxmin ymin:(float)paramsymin
xmax:(float)paramsxmax ymax:(float)paramsymax
iterates:(int)paramsiterates cycle:(int)paramscycle
close:(float)paramsclose
{
switch (plane)
{
case A_PLANE:
[planePopUpButton setTitle:
" The a-plane section, when b = r a + s, of"];
break;
case A_INVERSE_PLANE:
[planePopUpButton setTitle:
" The (1/a)-plane section, when b = r a + s, of"];
break;
case B_PLANE:
[planePopUpButton setTitle:
" The b-plane section, when a = r b + s, of"];
break;
}
[paramsForm setFloatValue:paramsxr at:0];
[paramsForm setFloatValue:paramsyr at:1];
[paramsForm setFloatValue:paramsxs at:2];
[paramsForm setFloatValue:paramsys at:3];
[paramsForm setFloatValue:paramsxmin at:4];
[paramsForm setFloatValue:paramsymin at:5];
[paramsForm setFloatValue:paramsxmax at:6];
[paramsForm setFloatValue:paramsymax at:7];
[paramsForm setIntValue:paramsiterates at:8];
[paramsForm setIntValue:paramscycle at:9];
[paramsForm setFloatValue:paramsclose at:10];
return self;
}
- getParameters:(int *)plane
xr:(float *)paramsxr yr:(float *)paramsyr
xs:(float *)paramsxs ys:(float *)paramsys
xmin:(float *)paramsxmin ymin:(float *)paramsymin
xmax:(float *)paramsxmax ymax:(float *)paramsymax
iterates:(int *)paramsiterates cycle:(int *)paramscycle
close:(float *)paramsclose
{
if (([paramsForm floatValueAt:0] IS_NOT xr) OR
([paramsForm floatValueAt:1] IS_NOT yr) OR
([paramsForm floatValueAt:2] IS_NOT xs) OR
([paramsForm floatValueAt:3] IS_NOT ys) OR
([paramsForm floatValueAt:4] IS_NOT xmin) OR
([paramsForm floatValueAt:5] IS_NOT ymin) OR
([paramsForm floatValueAt:6] IS_NOT xmax) OR
([paramsForm floatValueAt:7] IS_NOT ymax) OR
([paramsForm floatValueAt:8] IS_NOT iterates) OR
([paramsForm floatValueAt:9] IS_NOT cycle) OR
([paramsForm floatValueAt:10] IS_NOT close))
{
[self parametersChanged:self];
}
*plane = [[planePopUpButton target] indexOfItem:[planePopUpButton title]];
*paramsxr = [paramsForm floatValueAt:0];
*paramsyr = [paramsForm floatValueAt:1];
*paramsxs = [paramsForm floatValueAt:2];
*paramsys = [paramsForm floatValueAt:3];
*paramsxmin = [paramsForm floatValueAt:4];
*paramsymin = [paramsForm floatValueAt:5];
*paramsxmax = [paramsForm floatValueAt:6];
*paramsymax = [paramsForm floatValueAt:7];
*paramsiterates = [paramsForm intValueAt:8];
*paramscycle = [paramsForm intValueAt:9];
*paramsclose = [paramsForm floatValueAt:10];
return self;
}
- parametersChanged:sender
{
lineCompleted = 0;
[startButton setTitle:"Start"];
if (NXDrawingStatus IS NX_DRAWING)
{
[self lockFocus];
PSnewinstance();
[self unlockFocus];
}
return self;
}
- mouseDown:(NXEvent *)theEvent
// Draws a box on the screen to produce new view coordinates
{
NXRect boxRect;
NXPoint centerPoint, currentPoint;
NXEvent *nextEvent;
int oldMask,
boxSides[] = {NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN,
NX_YMIN, NX_XMAX, NX_YMAX, NX_XMIN};
float aspectRatio,
boxGrays[] = {NX_BLACK, NX_BLACK, NX_BLACK, NX_BLACK,
NX_WHITE, NX_WHITE, NX_WHITE, NX_WHITE};
[window makeFirstResponder:self];
oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
aspectRatio = bounds.size.height/bounds.size.width;
centerPoint = theEvent->location;
[self convertPoint:¢erPoint fromView:nil];
NXSetRect(&boxRect,centerPoint.x,centerPoint.y,0.0,0.0);
[self lockFocus];
nextEvent = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
while (nextEvent->type IS NX_MOUSEDRAGGED)
{
currentPoint = nextEvent->location;
[self convertPoint:¤tPoint fromView:nil];
boxRect.size.width = 2*(currentPoint.x - centerPoint.x);
boxRect.size.height = 2*(currentPoint.y - centerPoint.y);
if (boxRect.size.width < 0)
boxRect.size.width = -boxRect.size.width;
if (boxRect.size.height < 0)
boxRect.size.height = -boxRect.size.height;
if (boxRect.size.height > boxRect.size.width*aspectRatio)
boxRect.size.height = boxRect.size.width*aspectRatio;
else
boxRect.size.width = boxRect.size.height/aspectRatio;
boxRect.origin.x = centerPoint.x - .5*boxRect.size.width;
boxRect.origin.y = centerPoint.y - .5*boxRect.size.height;
PSnewinstance();
PSsetinstance(YES);
NXDrawTiledRects(&boxRect, (NXRect *)0, boxSides, boxGrays, 8);
PSsetinstance(NO);
nextEvent = [NXApp getNextEvent:NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK];
}
[self unlockFocus];
[window setEventMask:oldMask];
if ((boxRect.size.width > 0) AND (boxRect.size.height > 0))
{
[paramsForm setFloatValue:xmin + (xmax - xmin)*
(boxRect.origin.x - bounds.origin.x)/bounds.size.width at:4];
[paramsForm setFloatValue:ymin + (ymax - ymin)*
(boxRect.origin.y - bounds.origin.y)/bounds.size.height at:5];
[paramsForm setFloatValue:xmin + (xmax - xmin)*
(boxRect.origin.x + boxRect.size.width - bounds.origin.x)
/bounds.size.width at:6];
[paramsForm setFloatValue:ymin + (ymax - ymin)*
(boxRect.origin.y + boxRect.size.height - bounds.origin.y)
/bounds.size.height at:7];
lineCompleted = 0;
[startButton setTitle:"Start"];
}
else
{
[self lockFocus];
PSnewinstance();
[self unlockFocus];
}
return self;
}
@end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.