home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-01-11 | 34.6 KB | 1,106 lines |
-
- /*
- Graph3DDoc.m
-
- The Graph3DDoc represents an open 3D Graph document. Graph3DDoc receives
- messages from the user interface objects and uses three Expression objects
- to do calculations, and a RotatorCamera and a PointMesh to display the
- results.
-
- The Graph3DDoc class has one slightly odd external dependency. It requires
- that the delegate of NXApp be able to provide it with a NXStringTable
- (via the stringTable method) that it can use to look up strings that
- are presented to the user. In this application, the delegate of NXApp is
- always an instance of the GraphApp class.
-
- If this were a larger program, the NXStringTable needed by this document
- class would probably be in its own nib section, which would be loaded once
- the first time a Graph3DDoc is created, and then shared.
-
- You may freely copy, distribute, and reuse the code in this example.
- NeXT disclaims any warranty of any kind, expressed or implied, as to its
- fitness for any particular use.
- */
-
- #import "Graph.h"
-
- /* declare methods static to this class */
- @interface Graph3DDoc(Private)
- - _updateShape;
- - _updateGraph:(BOOL)finalChange;
- - (void)_equationChanged;
- - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)expr enableVariables:(BOOL *)enableVars;
- - _uOrVRangeSliderChanged:sender varName:(int)var;
- - _uOrVRangeTextChanged:sender varName:(int)var;
- - _write:(const char *)filename;
- - _read:(const char *)filename;
- - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val;
- - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain;
- - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes;
- - (void)_setUVRangeOf:(int)var;
- - (void)_setVariable:(int)index toValue:(float)val;
- - (void)_setUpLighting;
- - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel;
-
- @end
-
- @implementation Graph3DDoc
-
- /* variable names must be between A and H */
- #define MIN_CONST 'A'
- #define MAX_CONST 'H'
-
- /* indices into instance variable arrays */
- #define X 0
- #define Y 1
- #define Z 2
- #define U 0
- #define V 1
-
- /* default resolution of a new graph */
- #define DEFAULT_RES 12
-
- /* name used for the real document inside a doc wrapper */
- #define DOC_NAME "/Graph3DDoc.xyzgraph"
-
- static void writeCellFloat(Cell *obj, NXTypedStream *ts);
- static NXTypedStream *openDocStream(const char *filename, int mode);
- static int removeFile(const char *file);
-
- - init {
- /* the default initialization is to just open a new document */
- return [self initFromFile:NULL];
- }
-
- /*
- * Opens a document. If file is NULL, we open an untitiled document. We also
- * create a NXDataLinkManager and hook ourselves up as its delegate for doing
- * Object Links.
- */
- - initFromFile:(const char *)file {
- int rows, cols;
- int i;
- TextField *aCell;
- int xyz;
- RtPoint axis;
- char realPath[MAXPATHLEN+1];
-
- [super init];
-
- /* load the UI from the nib section and set attributes not available in IB */
- [NXApp loadNibSection:"Graph3DDoc.nib" owner:self withNames:NO fromZone:[self zone]];
- [variableTexts getNumRows:&rows numCols:&cols];
- for (i = rows; i--; ) {
- aCell = [variableTexts cellAt:i :0];
- [aCell setFloatingPointFormat:YES left:3 right:2];
- [aCell setEnabled:NO];
- }
- [minUVText[U] setFloatingPointFormat:YES left:3 right:2];
- [maxUVText[U] setFloatingPointFormat:YES left:3 right:2];
- [minUVText[V] setFloatingPointFormat:YES left:3 right:2];
- [maxUVText[V] setFloatingPointFormat:YES left:3 right:2];
- [variableSliders setAutosizeCells:YES];
- [resolutionText setEntryType:NX_POSINTTYPE];
- [camera setFieldOfViewByAngle:90.0];
- [self _setUpLighting];
-
- /* create Expression objects we will use to evaluate expressions */
- for (xyz = X; xyz <= Z; xyz++) {
- expr[xyz] = [[Expression allocFromZone:[self zone]] init];
- [expr[xyz] setDimensions:2]; /* we eval u * v points */
- }
-
- /* create and position the shape that we will use to show our data */
- [[camera setWorldShape:[[PointMesh alloc] init]] free];
- axis[1] = axis[2] = 0;
- axis[0] = 1.0;
- [[camera worldShape] rotateAngle:-90.0 axis:axis];
-
- if (file) {
- /* read existing document */
- if ([self _read:file]) {
- name = NXCopyStringBufferFromZone(file, [self zone]);
- if (realpath(name, realPath))
- realName = NXCopyStringBufferFromZone(realPath, [self zone]);
- [window setTitleAsFilename:name];
- [self _updateShape];
- linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self fromFile:file];
- } else {
- [self free]; /* couldn't load file */
- return nil;
- }
- } else {
- /*
- * Create a new document. We initialize it with a trivial expression
- * because that was easier than allowing the state where there is no
- * current expression.
- */
- RtPoint from = {12.0, 8.0, 10.0}, to = {0.0, 0.0, 0.0};
-
- [camera setEyeAt:from toward:to roll:0.0];
- /* set initial shape color to non-white on a color display */
- if ([camera shouldDrawColor])
- [[camera worldShape] setColor:NXConvertRGBToColor(51.0/255.0, 153.0/255.0, 116.0/255.0)];
-
- [window setTitleAsFilename:[[[NXApp delegate] stringTable] valueForStringKey:"untitled doc"]];
- [resolutionSlider setIntValue:DEFAULT_RES];
- [resolutionText setIntValue:DEFAULT_RES];
- [equation[X] setStringValue:"u"];
- [equation[Y] setStringValue:"v"];
- [equation[Z] setStringValue:"A * (u^2 + v^2) + C"];
- for (xyz = X; xyz <= Z; xyz++) {
- [expr[xyz] setResolution:DEFAULT_RES];
- }
- [self _equationChanged];
- [self _setUVRangeOf:U];
- [self _setUVRangeOf:V];
- [self _updateShape];
- linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self];
- }
-
- [[NXApp delegate] showThreeDPanel:self];
- [window makeKeyAndOrderFront:self];
- return self;
- }
-
- - free {
- int xyz;
-
- for (xyz = X; xyz <= Z; xyz++)
- [expr[xyz] free];
- NXZoneFree([self zone], name);
- NXZoneFree([self zone], realName);
- [linkMgr free];
- return [super free];
- }
-
- - (const char *)filename {
- return name;
- }
-
- - (const char *)realFilename {
- return realName;
- }
-
- /*
- * This method is called whenever our window becomes the app's main window.
- * When this happens we get the 3D Panel and set its camera to our camera, so
- * the panel reflects the attributes of our window.
- */
- - windowDidBecomeMain:sender {
- [[[NXApp delegate] threeDPanel] setCamera:camera];
- return self;
- }
-
- #define BUF_MAX 256
-
- /*
- * This method is called when the user has just finished typing in an
- * expression. This is where we validate that it is a legal expression.
- * If we dont like the expression, we return YES, which tells the text object
- * not to accept the user's entry.
- */
- - (BOOL)textWillEnd:textObject {
- BOOL parseError;
- BOOL enableVars[MAX_CONST - MIN_CONST + 1];
- NXStringTable *stringTable;
- Expression *tempExpr = [[Expression allocFromZone:[self zone]] init];
- char buffer[BUF_MAX];
- char *newText;
- int length;
-
- /* get the text out of the text object where the expression was typed */
- length = [textObject byteLength] + 1;
- if (length > BUF_MAX)
- newText = NXZoneMalloc(NXDefaultMallocZone(), sizeof(char) * length);
- else
- newText = buffer;
- [textObject getSubstring:newText start:0 length:length];
-
- if (*newText) {
- parseError = [self _parseEquationFrom:newText using:tempExpr enableVariables:enableVars];
- if (!parseError) {
- /* parse the new equation into the real expressions */
- [self _equationChanged];
-
- /* update the shape object with current results and display it */
- [self _updateGraph:YES];
- }
- } else
- parseError = YES;
-
- if (parseError) {
- stringTable = [[NXApp delegate] stringTable];
- NXRunAlertPanel([stringTable valueForStringKey:"parse alert title"],
- [stringTable valueForStringKey:"3D parse alert message"],
- [stringTable valueForStringKey:"ok button"],
- NULL, NULL);
- }
- [tempExpr free];
- if (length > BUF_MAX)
- NXZoneFree(NXDefaultMallocZone(), newText);
- return parseError;
- }
-
- /* This method does most of the work of parsing a new expression. */
- - (void)_equationChanged {
- BOOL enableVars[MAX_CONST - MIN_CONST + 1];
- int i;
- int xyz;
- float newVarVal;
-
- for (i = 0; i <= MAX_CONST - MIN_CONST; i++)
- enableVars[i] = NO;
- for (xyz = X; xyz <= Z; xyz++) {
- (void)[self _parseEquationFrom:[equation[xyz] stringValue] using:expr[xyz] enableVariables:enableVars];
- }
-
- /* turn on the UI for constants used in these expressions */
- for (i = 0; i <= MAX_CONST - MIN_CONST; i++) {
- if ([[variableSliders cellAt:i :0] isEnabled])
- newVarVal = [[variableSliders cellAt:i :0] floatValue];
- else
- newVarVal = 1.0;
- [self _initVariable:i enable:enableVars[i] value:newVarVal];
- }
-
- /* update the range of the independent variables */
- [self _setUVRangeOf:U];
- [self _setUVRangeOf:V];
- }
-
- /*
- * This method is called after the user typed in a new expression. The
- * expression has already been verified in our textWillEnd: method.
- */
- - equationChanged:sender {
- /* reselect the equation field so the user can type another */
- [sender selectCell:[sender selectedCell]];
- return self;
- }
-
- /* called when either the minu, maxu, minv or maxv slider changes */
- - _uOrVRangeSliderChanged:sender varName:(int)var {
- TextFieldCell *text;
- SliderCell *slider;
- NXEvent *event;
-
- /* update the associated text field */
- slider = [sender selectedCell];
- if (slider == maxUVSlider[var])
- text = maxUVText[var];
- else if (slider == minUVSlider[var])
- text = minUVText[var];
- else
- text = nil;
- NX_ASSERT(text, "Funny sender of _uOrVRangeSliderChanged: message");
- [text setFloatValue:[slider floatValue]];
-
- /* update the u or v range in the Expression and display the new graph */
- [self _setUVRangeOf:var];
- event = [NXApp currentEvent];
- [self _updateGraph:event->type == NX_LMOUSEUP];
- return self;
- }
-
- /* called when either the minu or maxu slider changes */
- - uRangeSliderChanged:sender {
- return [self _uOrVRangeSliderChanged:sender varName:U];
- }
-
- /* called when either the minv or maxv slider changes */
- - vRangeSliderChanged:sender {
- return [self _uOrVRangeSliderChanged:sender varName:V];
- }
-
- /* called when either the minu, maxu, minv or maxv text changes */
- - _uOrVRangeTextChanged:sender varName:(int)var {
- TextFieldCell *text;
- SliderCell *slider;
- float val;
-
- /*
- * update the associated slider. If the value typed is outside the current
- * range of the slider, we extend its range.
- */
- text = [sender selectedCell];
- if (text == maxUVText[var])
- slider = maxUVSlider[var];
- else if (text == minUVText[var])
- slider = minUVSlider[var];
- else
- slider = nil;
- NX_ASSERT(slider, "Funny sender of _uOrVRangeTextChanged: message");
- val = [text floatValue];
- [self _setSliderCell:slider value:val minLabel:nil maxLabel:nil];
-
- /* update the u or v range in the Expression and display the new graph */
- [self _setUVRangeOf:var];
- [self _updateGraph:YES];
- [sender selectText:self];
- return self;
- }
-
- /* called when either the minu or maxu text changes */
- - uRangeTextChanged:sender {
- return [self _uOrVRangeTextChanged:sender varName:U];
- }
-
- /* called when either the minv or maxv text changes */
- - vRangeTextChanged:sender {
- return [self _uOrVRangeTextChanged:sender varName:V];
- }
-
- /* called when one of the variables' sliders changes */
- - variableSliderChanged:sender {
- int index;
- float val;
- NXEvent *event;
-
- /* update the associated text field */
- index = [variableSliders selectedRow];
- val = [[variableSliders selectedCell] floatValue];
- [[variableTexts cellAt:index :0] setFloatValue:val];
- [self _setVariable:index toValue:val];
- event = [NXApp currentEvent];
- [self _updateGraph:event->type == NX_LMOUSEUP];
- return self;
- }
-
- /* called when one of the variables' text fields changes */
- - variableTextChanged:sender {
- int index;
- float val;
- TextFieldCell *text;
- SliderCell *slider;
-
- /* update the associated slider */
- text = [variableTexts selectedCell];
- index = [variableTexts selectedRow];
- slider = [variableSliders cellAt:index :0];
- val = [text floatValue];
- [self _setSliderCell:slider value:val
- minLabel:[variableMinLabels cellAt:index :0]
- maxLabel:[variableMaxLabels cellAt:index :0]];
-
- [self _setVariable:index toValue:val];
- [self _updateGraph:YES];
- [sender selectCell:text];
- return self;
- }
-
- /* The maximum allowable resolution. ~10K polygons */
- #define MAX_RES 100
-
- /* called when either the resolution slider or text fields changes */
- - resolutionChanged:sender {
- Cell *senderCell, *otherCell;
- int iVal;
- float fVal;
- int xyz;
- NXEvent *event;
-
- if ([[sender cellAt:0 :0] isKindOfClassNamed:"SliderCell"]) {
- senderCell = resolutionSlider;
- otherCell = resolutionText;
- } else {
- senderCell = resolutionText;
- otherCell = resolutionSlider;
- }
-
- fVal = [senderCell floatValue];
- iVal = rint(fVal);
- if (iVal > MAX_RES)
- iVal = MAX_RES;
- [otherCell setIntValue:iVal];
-
- /* update the Expression's resolution and display the new graph */
- for (xyz = X; xyz <= Z; xyz++)
- [expr[xyz] setResolution:iVal];
- event = [NXApp currentEvent];
- [self _updateGraph:event->type != NX_LMOUSEDRAGGED];
- if (senderCell == resolutionText)
- [sender selectText:self];
- return self;
- }
-
- /*
- * Copies the current view of the graph into the Pasteboard as Renderman code.
- * This is sent from the Copy Graph menu item.
- */
- - copyGraph:sender {
- Pasteboard *pb;
- const char *types[2];
- NXDataLink *link;
-
- pb = [Pasteboard new];
- types[0] = N3DRIBPboardType;
- types[1] = NXDataLinkPboardType;
- [self _writeGraphToPasteboard:pb types:types num:2];
- link = [[NXDataLink alloc] initLinkedToSourceSelection:[NXSelection allSelection] managedBy:linkMgr supportingTypes:&N3DRIBPboardType count:1];
- [link writeToPasteboard:pb];
- [link free];
- return self;
- }
-
- /*
- * Copies the current view of the graph into the Pasteboard as RIB.
- * This is used by the Copy Graph menu item and to support Object Links.
- *
- * Types must contain N3DRIBPboardType.
- */
- - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes {
- NXStream *st;
- char *data;
- int dataLen, maxDataLen;
-
- /* Open a stream on memory where we will collect the PostScript */
- st = NXOpenMemory(NULL, 0, NX_WRITEONLY);
-
- /* Tell the Pasteboard we're going to copy RIB */
- [pb declareTypes:types num:numTypes owner:nil];
-
- /* writes the RIB for the whole graph into the stream */
- [camera copyRIBCode:st];
-
- /* get the buffered up PostScript out of the stream */
- NXGetMemoryBuffer(st, &data, &dataLen, &maxDataLen);
-
- /* put the buffer in the Pasteboard, free the stream (and the buffer) */
- [pb writeType:N3DRIBPboardType data:data length:dataLen];
- NXCloseMemory(st, NX_FREEBUFFER);
- return self;
- }
-
- /*** Object Links support - methods called by the DataLinkManager ***/
-
- /* called by the DataLinkManager when new data is needed for a link */
- - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)flag {
- NX_ASSERT([selection isEqual:[NXSelection allSelection]] || [selection isEqual:[NXSelection currentSelection]], "Funny selection passed to copyToPasteboard:at:");
- [self _writeGraphToPasteboard:pboard types:&N3DRIBPboardType num:1];
- return self;
- }
-
- /* returns the window for the given selection */
- - windowForSelection:(NXSelection *)selection {
- return window; /* all our sels are always in our one window */
- }
-
- /*
- * We support continuously updating links by tracking changes to links to
- * us from open documents. This is easy for Graph because all links can
- * only be to the whole graph, so any change in the graph is a change relevant
- * to any link.
- */
- - (BOOL)dataLinkManagerTracksLinksIndividually:(NXDataLinkManager *)sender {
- return YES;
- }
-
- /*
- * Sent when we should start tracking a link. We just keep all links we're
- * tracking in a list, and send them a message whenever the graph is changed.
- */
- - dataLinkManager:(NXDataLinkManager *)sender startTrackingLink:(NXDataLink *)link {
- if (!linksTracked)
- linksTracked = [[List allocFromZone:[self zone]] init];
- [linksTracked addObject:link];
- return self;
- }
-
- /* Sent when we can forget a links we're tracking */
- - dataLinkManager:(NXDataLinkManager *)sender stopTrackingLink:(NXDataLink *)link {
- [linksTracked removeObject:link];
- if ([linksTracked count] == 0) {
- [linksTracked free];
- linksTracked = nil;
- }
- return self;
- }
-
- /*
- * Called by other methods of Graph3DDoc whenever we make a change to the
- * document. We tell the DataLinkManager about the change, and also notify
- * any links we are tracking.
- */
- - docChanged {
- [linkMgr documentEdited];
- [linksTracked makeObjectsPerform:@selector(sourceEdited)];
- [window setDocEdited:YES];
- return self;
- }
-
- /* called by ThreeDPanel when it changes something about the document */
- - threeDPanelDidChangeDoc:(ThreeDPanel *)sender {
- [self docChanged];
- return self;
- }
-
-
- /* called when Save, Save As, or Save To is picked from the menu */
- - save:sender { return [self _doSaveWithNewName:NO retainName:YES]; }
- - saveAs:sender { return [self _doSaveWithNewName:YES retainName:YES]; }
- - saveTo:sender { return [self _doSaveWithNewName:YES retainName:NO]; }
-
- /*
- * All varieties of save go through this routine. It covers all the cases
- * of running the Save Panel and retaining the name chosen.
- */
- - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain {
- SavePanel *savePanel;
- const char *saveName; /* filename to save into */
- NXZone *zone;
- BOOL previouslySaved = (name != NULL);
- char realPath[MAXPATHLEN+1];
-
- /*
- * If the file is untitled or we are saving under a different name,
- * run the save panel to get a new name.
- */
- zone = [self zone];
- if (!name || doNewName) {
- savePanel = [SavePanel new];
- [savePanel setRequiredFileType:"xyzgraph"];
- if ([savePanel runModalForDirectory:NULL file:name]) {
- /* if we want to keep this name, replace any old name */
- if (doRetain) {
- NXZoneFree(zone, name);
- NXZoneFree(zone, realName);
- realName = NULL;
- name = NXCopyStringBufferFromZone([savePanel filename], zone);
- }
- saveName = [savePanel filename];
- } else
- return nil; /* user canceled */
- } else
- /* if we didn't run the Save Panel, save using the existing name */
- saveName = name;
- [self _write:saveName];
- if (!doRetain)
- [linkMgr documentSavedTo:saveName];
- else if (!previouslySaved || doNewName)
- [linkMgr documentSavedAs:saveName];
- else
- [linkMgr documentSaved];
- if (doRetain) {
- [window setDocEdited:NO];
- [window setTitleAsFilename:name];
- if (!realName && realpath(name, realPath)) {
- realName = NXCopyStringBufferFromZone(realPath, [self zone]);
- }
- }
- return self;
- }
-
- - revertToSaved:sender {
- int response;
- NXStringTable *stringTable;
- const char *shortName;
- Graph3DDoc *newDoc;
-
- if ([window isDocEdited] && name) {
- stringTable = [[NXApp delegate] stringTable];
- shortName = strrchr(name, '/') + 1;
- response = NXRunAlertPanel([stringTable valueForStringKey:"revert alert title"],
- [stringTable valueForStringKey:"revert alert message"],
- [stringTable valueForStringKey:"revert button"],
- [stringTable valueForStringKey:"cancel button"],
- NULL, shortName);
- if (response == NX_ALERTDEFAULT) {
- [window setDelegate:nil];
- [window close];
- [NXApp delayedFree:self];
- [linkMgr documentClosed];
- [linkMgr free];
- linkMgr = nil;
- newDoc = [[Graph3DDoc allocFromZone:[self zone]] initFromFile:name];
- if (!newDoc) {
- NXRunAlertPanel(
- [stringTable valueForStringKey:"open alert title"],
- [stringTable valueForStringKey:"open alert message"],
- [stringTable valueForStringKey:"ok button"],
- NULL, NULL, name);
- }
- }
- }
- return self;
- }
-
- /* Called when the window is closing. We free the Graph3DDoc. */
- - windowWillClose:sender {
- int response;
- NXStringTable *stringTable;
- const char *shortName;
-
- if ([window isDocEdited]) {
- stringTable = [[NXApp delegate] stringTable];
- if (name) {
- shortName = strrchr(name, '/') + 1;
- } else {
- shortName = [stringTable valueForStringKey:"untitled doc"];
- }
- response = NXRunAlertPanel([stringTable valueForStringKey:"close alert title"],
- [stringTable valueForStringKey:"close alert message"],
- [stringTable valueForStringKey:"save button"],
- [stringTable valueForStringKey:"dont save button"],
- [stringTable valueForStringKey:"cancel button"],
- shortName);
- if (response != NX_ALERTDEFAULT && response != NX_ALERTALTERNATE) {
- return nil;
- } else {
- if (response == NX_ALERTDEFAULT && ![self save:sender])
- return nil;
- }
- }
- [NXApp delayedFree:self];
- [linkMgr documentClosed];
- return self; /* says its OK to close */
- }
-
- /* Called whenever something changes to update the view of the graph. */
- - _updateGraph:(BOOL)finalChange {
- [self _updateShape]; /* update the values that we graph */
- [camera display]; /* draw the new graph */
- if (finalChange)
- [self docChanged];
- return self;
- }
-
- /* Called whenever something changes to update the values that we graph. */
- - _updateShape {
- float *vals[3];
- int numVals[3];
- float min[3], max[3];
- int xyz;
-
- /* extract the values from the Expressions. This may cause a recalc. */
- for (xyz = X; xyz <= Z; xyz++) {
- [expr[xyz] resultsVector:&vals[xyz] numVals:&numVals[xyz]];
- [expr[xyz] resultsMin:&min[xyz] max:&max[xyz]];
- }
-
- /* set the points of the graph */
- [[camera worldShape] setPointsX:vals[X] y:vals[Y] z:vals[Z]
- minX:min[X] minY:min[Y] minZ:min[Z]
- maxX:max[X] maxY:max[Y] maxZ:max[Z]
- numU:[expr[X] resolution] numV:[expr[X] resolution]];
- return self;
- }
-
- /* parses and validates the text for one equation field */
- - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)anExpr enableVariables:(BOOL *)enableVars {
- BOOL parseError;
- EXPEnumState state; /* used to run through the vars of the expression */
- const char *var;
-
- parseError = ![anExpr parse:str];
- if (!parseError) {
- /*
- * If it parsed successfully, make sure all the variables are single
- * letters, and are either "u", "v" or between "A" and "H". As we
- * find suitable variables, we remember then in the enableVars
- * array, so we can enable their controls later.
- */
- state = [anExpr beginVariableEnumeration];
- while (var = [anExpr nextVariable:state])
- if (*var && !var[1] && *var >= MIN_CONST && *var <= MAX_CONST)
- enableVars[*var - MIN_CONST] = YES;
- else if ((*var != 'u' && *var != 'v') || var[1])
- parseError = YES;
- [anExpr endVariableEnumeration:state];
- }
- return parseError;
- }
-
- /*
- * Writes a document to the given file using typedstreams. We're very careful
- * about catching exceptions here so we can tell the user if his file didn't
- * get written out successfully.
- */
- - _write:(const char *)filename {
- NXTypedStream *ts;
- const char *stringVal;
- int intVal;
- char charVal;
- RtPoint fromPoint;
- RtPoint toPoint;
- float aRollAngle;
- RtMatrix shapeXform;
- int rows, cols;
- int numVars;
- int i;
- NXStringTable *stringTable;
- volatile BOOL hadAnError = NO;
- char buffer[MAXPATHLEN+1];
- int xyz;
- NXRect winFrame;
-
- /* cons up the name of the backup file and remove it */
- strcpy(buffer, filename);
- strcat(buffer, "~");
- removeFile(buffer);
-
- /* move the existing file to the backup */
- rename(filename, buffer);
-
- /* create the new document as a file package (doc wrapper) */
- strcpy(buffer, filename);
- strcat(buffer, DOC_NAME);
- mkdir(filename, 0777);
- ts = NXOpenTypedStreamForFile(buffer, NX_WRITEONLY);
- if (ts) {
- NX_DURING
- intVal = 2; /* version of this write's data */
- NXWriteType(ts, "i", &intVal);
- for (xyz = X; xyz <= Z; xyz++) {
- stringVal = [expr[xyz] text];
- NXWriteType(ts, "*", &stringVal);
- }
- [variableSliders getNumRows:&rows numCols:&cols];
- numVars = 0;
- for (i = 0; i < rows; i++) {
- if ([[variableSliders cellAt:i :0] isEnabled])
- numVars++;
- }
- NXWriteType(ts, "i", &numVars);
- for (i = 0; i < rows; i++)
- if ([[variableSliders cellAt:i :0] isEnabled]) {
- charVal = MIN_CONST + i;
- NXWriteType(ts, "c", &charVal);
- writeCellFloat([variableSliders cellAt:i :0], ts);
- }
- intVal = [expr[X] resolution];
- NXWriteType(ts, "i", &intVal);
- writeCellFloat(minUVSlider[U], ts);
- writeCellFloat(maxUVSlider[U], ts);
- writeCellFloat(minUVSlider[V], ts);
- writeCellFloat(maxUVSlider[V], ts);
- NXWriteColor(ts, [[camera worldShape] color]);
- NXWriteColor(ts, [camera backgroundColor]);
- intVal = [[camera worldShape] surfaceType];
- NXWriteType(ts, "i", &intVal);
- [camera getEyeAt:&fromPoint toward:&toPoint roll:&aRollAngle];
- NXWriteArray(ts, "f", 3, fromPoint);
- NXWriteArray(ts, "f", 3, toPoint);
- NXWriteType(ts, "f", &aRollAngle);
- [[camera worldShape] getTransformMatrix:shapeXform];
- NXWriteArray(ts, "f", 4, shapeXform[0]);
- NXWriteArray(ts, "f", 4, shapeXform[1]);
- NXWriteArray(ts, "f", 4, shapeXform[2]);
- NXWriteArray(ts, "f", 4, shapeXform[3]);
- [window getFrame:&winFrame];
- NXWriteRect(ts, &winFrame);
- NXCloseTypedStream(ts);
- NX_HANDLER
- hadAnError = YES;
- NX_DURING
- NXCloseTypedStream(ts);
- NX_HANDLER
- /* ignore any error at this point */
- NX_ENDHANDLER
- NX_ENDHANDLER
- } else
- hadAnError = YES;
- if (hadAnError) {
- stringTable = [[NXApp delegate] stringTable];
- NXRunAlertPanel([stringTable valueForStringKey:"save alert title"],
- [stringTable valueForStringKey:"save alert message"],
- [stringTable valueForStringKey:"ok button"],
- NULL, NULL, filename);
- return nil;
- } else
- return self;
- }
-
- /* reads a document from the given file using typedstreams */
- - _read:(const char *)filename {
- NXTypedStream *ts;
- char *stringVal;
- int i;
- char varName;
- float floatVal1, floatVal2;
- int intVal;
- RtPoint fromPoint;
- RtPoint toPoint;
- float aRollAngle;
- RtMatrix shapeXform;
- int version;
- int numVars;
- BOOL parseError;
- int xyz, uv;
- NXRect winFrame;
-
- ts = openDocStream(filename, NX_READONLY);
- if (ts) {
- NX_DURING
- NXReadType(ts, "i", &version); /* read file version */
- NX_ASSERT(version <= 2, "Unknown file version in -read");
- for (xyz = X; xyz <= Z; xyz++) {
- NXReadType(ts, "*", &stringVal);
- [equation[xyz] setStringValue:stringVal];
- parseError = ![expr[xyz] parse:stringVal];
- free(stringVal);
- NX_ASSERT(!parseError, "Bad expression stored in data file");
- if (parseError)
- NX_VALRETURN(nil);
- }
- NXReadType(ts, "i", &numVars);
- for (i = 0; i < numVars; i++) {
- NXReadType(ts, "c", &varName);
- NXReadType(ts, "f", &floatVal1);
- [self _initVariable:varName - MIN_CONST enable:YES
- value:floatVal1];
- }
- NXReadType(ts, "i", &intVal); /* read resolution */
- [resolutionText setIntValue:intVal];
- [resolutionSlider setIntValue:intVal];
- for (xyz = X; xyz <= Z; xyz++)
- [expr[xyz] setResolution:intVal];
- for (uv = U; uv <= V; uv++) {
- NXReadType(ts, "f", &floatVal1); /* read min */
- NXReadType(ts, "f", &floatVal2); /* read max */
- [minUVText[uv] setFloatValue:floatVal1];
- [self _setSliderCell:minUVSlider[uv] value:floatVal1
- minLabel:nil maxLabel:nil];
- [maxUVText[uv] setFloatValue:floatVal2];
- [self _setSliderCell:maxUVSlider[uv] value:floatVal2
- minLabel:nil maxLabel:nil];
- [self _setUVRangeOf:uv];
- }
- [[camera worldShape] setColor:NXReadColor(ts)];
- [camera setBackgroundColor:NXReadColor(ts)];
- NXReadType(ts, "i", &intVal); /* read surface type */
- [camera setSurfaceTypeForAll:intVal chooseHider:YES];
- NXReadArray(ts, "f", 3, fromPoint);
- NXReadArray(ts, "f", 3, toPoint);
- NXReadType(ts, "f", &aRollAngle);
- [camera setEyeAt:fromPoint toward:toPoint roll:aRollAngle];
- NXReadArray(ts, "f", 4, shapeXform[0]);
- NXReadArray(ts, "f", 4, shapeXform[1]);
- NXReadArray(ts, "f", 4, shapeXform[2]);
- NXReadArray(ts, "f", 4, shapeXform[3]);
- [[camera worldShape] setTransformMatrix:shapeXform];
- NXReadRect(ts, &winFrame);
- [window placeWindow:&winFrame];
- NXCloseTypedStream(ts);
- /* must use NX_VALRETURN to return from inside a NX_DURING */
- NX_VALRETURN(self);
- NX_HANDLER
- NX_DURING
- NXCloseTypedStream(ts);
- NX_HANDLER
- /* ignore any error at this point */
- NX_ENDHANDLER
- return nil;
- NX_ENDHANDLER
- } else
- return nil;
- }
-
- /*
- * Inits a variable to either be on or off. Used when we discover a new
- * set of variables after a parse, or to set up the variables from a document
- * that we read from a file.
- */
- - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val {
- TextFieldCell *text;
- SliderCell *slider;
-
- /* set the initial value in the Expression */
- if (flag)
- [self _setVariable:index toValue:val];
-
- /* if the enabled status is changing... */
- if (flag != [[variableSliders cellAt:index :0] isEnabled])
- if (flag) {
-
- /* enable all controls associated with the variable */
- [[variableLabels cellAt:index :0] setTextGray:NX_BLACK];
- slider = [variableSliders cellAt:index :0];
- [slider setEnabled:YES];
- [self _setSliderCell:slider value:val
- minLabel:[variableMinLabels cellAt:index :0]
- maxLabel:[variableMaxLabels cellAt:index :0]];
- text = [variableTexts cellAt:index :0];
- [text setEnabled:YES];
- [text setFloatValue:val];
- } else if (!flag) {
-
- /* disable all controls associated with the variable */
- [[variableLabels cellAt:index :0] setTextGray:NX_DKGRAY];
- slider = [variableSliders cellAt:index :0];
- [slider setFloatValue:[slider minValue]];
- [slider setEnabled:NO];
- text = [variableTexts cellAt:index :0];
- [text setStringValue:NULL];
- [text setEnabled:NO];
- }
- }
-
- /*
- * sets the range of the u or v variable in the Expression.
- * It ensures that the min value isn't greater than the max value.
- */
- - (void)_setUVRangeOf:(int)var {
- int xyz;
- float min = [minUVSlider[var] floatValue];
- float max = [maxUVSlider[var] floatValue];
- char *varName = (var == U) ? "u" : "v";
-
- for (xyz = X; xyz <= Z; xyz++) {
- [expr[xyz] setVar:varName min:MIN(min, max) max:MAX(min, max)];
- /* set up two dimensions of evaluation */
- [expr[xyz] setVar:varName dimension:var];
- }
- }
-
- /* sets one of the expressions' variables to a value */
- - (void)_setVariable:(int)index toValue:(float)val {
- char varName[2];
- int xyz;
-
- varName[0] = MIN_CONST + index;
- varName[1] = '\0';
- for (xyz = X; xyz <= Z; xyz++)
- [expr[xyz] setVar:varName value:val];
- }
-
- /* sets a slider to a value and adjust min/max */
- - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel {
- if (val < [slider minValue]) {
- [window disableDisplay]; /* so slider won't flash to new location */
- [slider setMinValue:val];
- [window reenableDisplay];
- [minLabel setIntValue:floor(val)];
- } else if (val > [slider maxValue]) {
- [window disableDisplay]; /* so slider won't flash to new location */
- [slider setMaxValue:val];
- [window reenableDisplay];
- [maxLabel setIntValue:ceil(val)];
- }
- [slider setFloatValue:val];
- }
-
- /* sets up some default lights for the camera */
- - (void)_setUpLighting {
- RtPoint distantlight_from, distantlight2_from, pointlight_from;
- RtPoint distantlight_to;
- RtFloat intensity;
- N3DLight *pointLight, *distantLight, *distantLight2, *ambientLight;
-
- if ([[camera lightList] count] == 4)
- return; /* we've already added lights to this camera */
-
- pointlight_from[0] = 12.0;
- pointlight_from[1] = -7.0;
- pointlight_from[2] = 8.5;
- distantlight_from[0] = 12.0;
- distantlight_from[1] = -7.0;
- distantlight_from[2] = 8.5;
- distantlight2_from[0] = 3.0;
- distantlight2_from[1] = 2.0;
- distantlight2_from[2] = 8.5;
-
- distantlight_to[0] = 0.0;
- distantlight_to[1] = 0.0;
- distantlight_to[2] = 0.0;
-
- intensity = 60.0;
- pointLight = [[N3DLight allocFromZone:[camera zone]] init];
- [pointLight makePointFrom:pointlight_from intensity:intensity];
- [camera addLight:pointLight];
-
- intensity = 0.6;
- distantLight = [[N3DLight allocFromZone:[camera zone]] init];
- [distantLight makeDistantFrom:distantlight_from to:distantlight_to intensity:intensity];
- [camera addLight:distantLight];
-
- intensity = 0.9;
- distantLight2 = [[N3DLight allocFromZone:[camera zone]] init];
- [distantLight2 makeDistantFrom:distantlight2_from to:distantlight_to intensity:intensity];
- [camera addLight:distantLight2];
-
- intensity = 0.15;
- ambientLight = [[N3DLight allocFromZone:[camera zone]] init];
- [ambientLight makeAmbientWithIntensity:intensity];
- [camera addLight:ambientLight];
- }
-
- /*
- * These methods are called as the IB file is unarchived. We override them to
- * store these outlets in arrays, since IB doesnt know how to declare arrays
- * of outlets.
- */
- - setEquationX:obj { equation[X] = obj; return self; }
- - setEquationY:obj { equation[Y] = obj; return self; }
- - setEquationZ:obj { equation[Z] = obj; return self; }
- - setMinUSlider:obj { minUVSlider[U] = obj; return self; }
- - setMinVSlider:obj { minUVSlider[V] = obj; return self; }
- - setMaxUSlider:obj { maxUVSlider[U] = obj; return self; }
- - setMaxVSlider:obj { maxUVSlider[V] = obj; return self; }
- - setMinUText:obj { minUVText[U] = obj; return self; }
- - setMinVText:obj { minUVText[V] = obj; return self; }
- - setMaxUText:obj { maxUVText[U] = obj; return self; }
- - setMaxVText:obj { maxUVText[V] = obj; return self; }
-
- @end
-
- /* little utility proc to write out the float value of a control */
- static void writeCellFloat(Cell *obj, NXTypedStream *ts) {
- float val;
-
- val = [obj floatValue];
- NXWriteType(ts, "f", &val);
- }
-
- /* Opens a stream on the document regardless of whether its a doc wrapper. */
- static NXTypedStream *openDocStream(const char *filename, int mode) {
- NXTypedStream *ts = NULL;
- struct stat statInfo;
- char buffer[MAXPATHLEN+1];
-
- if (stat(filename, &statInfo) == 0) {
- if (statInfo.st_mode & S_IFDIR) {
- strcpy(buffer, filename);
- strcat(buffer, DOC_NAME);
- ts = NXOpenTypedStreamForFile(buffer, mode);
- } else {
- ts = NXOpenTypedStreamForFile(filename, mode);
- }
- }
- return ts;
- }
-
- /* removes a directory, removing anything inside it. Does not recurse */
- static int removeFile(const char *file) {
- DIR *dirp;
- struct stat st;
- struct direct *dp;
- char *leaf = NULL;
- char path[MAXPATHLEN+1];
-
- if (!stat(file, &st)) {
- if ((st.st_mode & S_IFMT) == S_IFDIR) {
- dirp = opendir(file);
- for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
- if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
- if (!leaf) {
- strcpy(path, file);
- strcat(path, "/");
- leaf = path + strlen(path);
- }
- strcpy(leaf, dp->d_name);
- if (unlink(path)) {
- closedir(dirp);
- return -1;
- }
- }
- }
- return rmdir(file);
- } else {
- return unlink(file);
- }
- }
-
- return -1;
- }
-