This is HenonView.m in view mode; [Download] [Up]
//
// HenonView:View
//
// written by Anders Bertelrud
// (c) 1990, 1991 by Anders Bertelrud
//
#import "HenonView.h"
#import <dpsclient/dpsclient.h>
#import <appkit/appkit.h>
#import <objc/NXStringTable.h>
#define MAXREAL 1e+10
//#define MAXINT 65535
@implementation HenonView
////////////////////// Creation & Destruction Methods //////////////////////
- initFrame:(const NXRect *)f
{
self = [super initFrame:f];
// Create, initialize, and configure a new NXImage (for use as a
// mapping cache).
henonCache = [[NXImage alloc] initSize:&f->size];
[henonCache useCacheWithDepth:NX_TwoBitGrayDepth];
useGrid = YES;
continuousPlot = YES;
isRunning = NO;
timerTicking = NO;
// Initialize the default plot values, and init the mapping cache.
[self setPlotValues1:self];
[self initHenonCache];
return self;
}
- free
{
[self stopTimer];
[henonCache free];
[super free];
return nil;
}
//////////////////////////// Rendering Methods /////////////////////////////
- drawSelf:(const NXRect *)rects :(int)rectCount
{
// Since the mapping is cached, all we have to do is to composite
// the cache into the panel.
[henonCache composite:NX_COPY toPoint:&bounds.origin];
return self;
}
/////////////////////////////// File Methods ///////////////////////////////
- saveAsTIFF:sender
{
id savePanel = [SavePanel new];
NXStream *tiffStream;
// This method should probably allow the user to specify compression
// type and (in the case of JPEG) quantization value. This could be
// done using an accessory view.
[savePanel setRequiredFileType:"tiff"];
if ([savePanel runModal])
{
if ((tiffStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL)
{
NXRunAlertPanel([stringTable valueForStringKey:"FS error"],
[stringTable valueForStringKey:"Couldn't write TIFF to"],
[stringTable valueForStringKey:"OK"], NULL, NULL,
[savePanel filename]);
}
else
{
[henonCache writeTIFF:tiffStream];
NXSaveToFile(tiffStream, [savePanel filename]);
NXCloseMemory(tiffStream, NX_FREEBUFFER);
}
}
return self;
}
////////////////////////////// Action Methods //////////////////////////////
- startStop:sender
{
if (!isRunning)
{
// We aren't already plotting a map, so start now.
L = [[lField cell] floatValue];
T = [[tField cell] floatValue];
R = [[rField cell] floatValue];
B = [[bField cell] floatValue];
// Now check values and stuff. It is really very bad design philo-
// sophy to yell at the user using alert panels; this is just a cheap
// demo, however, so we'll leave it at this for now. At least we're
// using string tables to make internationalization easy.
if (L >= R)
{
NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
[stringTable valueForStringKey:"Right must be > left"],
[stringTable valueForStringKey:"OK"], NULL, NULL);
return self;
}
if (B >= T)
{
NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
[stringTable valueForStringKey:"Top must be > bottom"],
[stringTable valueForStringKey:"OK"], NULL, NULL);
return self;
}
orbits = [[orbitField cell] intValue];
if (orbits < 1)
{
NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
[stringTable valueForStringKey:"# orbits must be int >= 1"],
[stringTable valueForStringKey:"OK"], NULL, NULL);
return self;
}
points = [[pointsField cell] intValue];
if (points < 1)
{
NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
[stringTable valueForStringKey:"# pts/orbit must be int >= 1"],
[stringTable valueForStringKey:"OK"], NULL, NULL);
return self;
}
dy0 = [[incYField cell] floatValue];
dx0 = [[incXField cell] floatValue];
y0 = [[startYField cell] floatValue];
x0 = [[startXField cell] floatValue];
phase = [[phaseField cell] floatValue];
if (phase < 0 || phase > 3.141593)
{
NXRunAlertPanel([stringTable valueForStringKey:"Range error"],
[stringTable valueForStringKey:"Phase angle must be 0<=a<=pi"],
[stringTable valueForStringKey:"OK"], NULL, NULL);
return self;
}
// Calculate some things used by the caculation algorithm.
cosa = cos(phase);
sina = sin(phase);
xOld = x0;
yOld = y0;
xScale = bounds.size.width / (R - L);
yScale = bounds.size.height / (T - B);
currentOrbit = 1;
currentPoint = 1;
isRunning = YES;
// Start the plot.
[[currentOrbitField cell] setIntValue:currentOrbit];
[self initHenonCache];
[self display];
// Start the timer.
[self startTimer];
[nextOrbitButton setEnabled:YES];
[startStopButton setTitle:[stringTable valueForStringKey:"Stop"]];
}
else
{
// We're plotting a map, so stop now that the button has been clicked.
isRunning = NO;
[self stopTimer];
[nextOrbitButton setEnabled:NO];
[[currentOrbitField cell] setStringValue:""];
[startStopButton setTitle:[stringTable valueForStringKey:"Plot"]];
}
return self;
}
- gridChecked:sender
{
useGrid = [sender state];
return self;
}
- nextOrbit:sender
{
// This is a quick way to make sure that we'll go into the next orbit
// the next time doHenonCalculation is called.
currentPoint = points + 1;
return self;
}
- setPlotValues1:sender
{
isRunning = YES; // Hack so that startStop: will do the right thing.
[self startStop:self]; // Stop the current plot.
phase = 1.111; [phaseField setFloatValue:phase];
[[phaseSlider cell] setFloatValue:phase];
L = -1.2; [lField setFloatValue:L];
T = 1.2; [tField setFloatValue:T];
R = 1.2; [rField setFloatValue:R];
B = -1.2; [bField setFloatValue:B];
x0 = 0.098; [startXField setFloatValue:x0];
y0 = 0.061; [startYField setFloatValue:y0];
dx0 = 0.04; [incXField setFloatValue:dx0];
dy0 = 0.03; [incYField setFloatValue:dy0];
orbits = 38; [orbitField setIntValue:orbits];
points = 700; [pointsField setIntValue:points];
return self;
}
- setPlotValues2:sender
{
isRunning = YES; // Hack so that startStop: will do the right thing.
[self startStop:self]; // Stop the current plot.
phase = 0.264; [phaseField setFloatValue:phase];
[[phaseSlider cell] setFloatValue:phase];
L = -1.2; [lField setFloatValue:L];
T = 1.2; [tField setFloatValue:T];
R = 1.2; [rField setFloatValue:R];
B = -1.2; [bField setFloatValue:B];
x0 = 0.098; [startXField setFloatValue:x0];
y0 = 0.061; [startYField setFloatValue:y0];
dx0 = 0.04; [incXField setFloatValue:dx0];
dy0 = 0.03; [incYField setFloatValue:dy0];
orbits = 25; [orbitField setIntValue:orbits];
points = 300; [pointsField setIntValue:points];
return self;
}
- setPlotValues3:sender
{
isRunning = YES; // Hack so that startStop: will do the right thing.
[self startStop:self]; // Stop the current plot.
phase = 1.5732; [phaseField setFloatValue:phase];
[[phaseSlider cell] setFloatValue:phase];
L = -3.0; [lField setFloatValue:L];
T = 3.0; [tField setFloatValue:T];
R = 3.0; [rField setFloatValue:R];
B = -3.0; [bField setFloatValue:B];
x0 = 0.098; [startXField setFloatValue:x0];
y0 = 0.061; [startYField setFloatValue:y0];
dx0 = 0.04; [incXField setFloatValue:dx0];
dy0 = 0.03; [incYField setFloatValue:dy0];
orbits = 70; [orbitField setIntValue:orbits];
points = 400; [pointsField setIntValue:points];
return self;
}
////////////////////////////// Timer Methods ///////////////////////////////
// The timed entry function.
void timedEntryFunction (DPSTimedEntry entryID, double currentTime, id self)
{
[self doHenonCalculation];
}
- startTimer
{
[self stopTimer];
timerTicking = YES;
timer = DPSAddTimedEntry((double)0.0,
(DPSTimedEntryProc)&timedEntryFunction, self, NX_BASETHRESHOLD);
return self;
}
- stopTimer
{
if (timerTicking)
{
DPSRemoveTimedEntry(timer);
timerTicking = NO;
}
return self;
}
////////////////////////// Miscellaneous Methods ///////////////////////////
- doHenonCalculation
{
float xNew, yNew;
// If you want to modify the plot algorithm, this is the place to do it.
// This method is called continuously (from the timed entry function), and
// it calculates exactly one point in the HƯnon map. The location of the
// point is based on the location of the previous point as well as on the
// values given in the plot panel.
// If, after calculating the current point, we have calculated all the
// points in the orbit, we update the HƯnon window and start the next
// orbit.
if ([henonCache lockFocus])
{
PSsetgray(NX_BLACK);
if (currentPoint <= points)
{
if (abs(xOld) < MAXREAL && abs(yOld) < MAXREAL)
{
xNew = xOld*cosa - (yOld - xOld*xOld)*sina;
yNew = xOld*sina + (yOld - xOld*xOld)*cosa;
if (abs(xNew - L) < MAXINT/xScale
&& abs(T - yNew) < MAXINT/yScale)
{
float x, y;
x = (xNew - L) * xScale;
y = (T - yNew) * yScale;
if (x >= bounds.origin.x
&& x <= bounds.origin.x + bounds.size.width
&& y >= bounds.origin.y
&& y <= bounds.origin.y + bounds.size.height)
{
PSmoveto(x, y);
PSrlineto(0, 0);
PSstroke();
}
}
xOld = xNew;
yOld = yNew;
}
}
else
{
if (currentOrbit < orbits)
{
// Calculate next starting point.
xOld = x0 + currentOrbit * dx0;
yOld = y0 + currentOrbit * dy0;
// Reset the current point, and update the TextField in
// the HƯnon panel to reflect this change.
currentPoint = 1;
[[currentOrbitField cell] setIntValue:++currentOrbit];
// Update the panel to show the just completed orbit.
[self lockFocus];
[henonCache composite:NX_COPY toPoint:&bounds.origin];
[self unlockFocus];
[window flushWindow];
}
else
{
// We're now finished with the plot, so clean things up.
[self stopTimer];
isRunning = NO;
[startStopButton setTitle:"Plot"];
[nextOrbitButton setEnabled:NO];
[[currentOrbitField cell] setStringValue:""];
}
}
// Set things up so that the next point will be calculated the next
// time the timed entry calles doHenonCalculation.
++currentPoint;
[henonCache unlockFocus];
}
return self;
}
- initHenonCache
{
float bR, bT, bL, bB, bW, bH;
NXPoint origin;
[henonCache lockFocus];
// Figure out shorthands for some edge values.
bL = bounds.origin.x;
bT = bounds.origin.y + bounds.size.height;
bR = bounds.origin.x + bounds.size.width;
bB = bounds.origin.y;
bW = bounds.size.width;
bH = bounds.size.height;
PSsetgray(NX_WHITE);
NXRectFill(&bounds);
origin.x = (0.0 - L) * bW / (R - L);
origin.y = (0.0 - B) * bH / (T - B);
if ((R - L) < R && (T - B) < T)
{
origin.x = bR - 10;
origin.x = bB + 10;
}
// If the user wants a grid, draw one.
if (useGrid)
{
PSmoveto(0, origin.y);
PSlineto(bR, origin.y);
PSmoveto(origin.x, 0);
PSlineto(origin.x, bT);
PSsetgray(NX_DKGRAY);
PSsetlinewidth(0);
PSstroke();
}
[henonCache unlockFocus];
return self;
}
//////////////////////////// Delegation Methods ////////////////////////////
- appDidInit:sender
{
[window makeKeyAndOrderFront:self];
return self;
}
@endThese are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.