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.