|
|
1.1 root 1:
2: /*
3: Graph3DDoc.m
4:
5: The Graph3DDoc represents an open 3D Graph document. Graph3DDoc receives
6: messages from the user interface objects and uses three Expression objects
7: to do calculations, and a RotatorCamera and a PointMesh to display the
8: results.
9:
10: The Graph3DDoc class has one slightly odd external dependency. It requires
11: that the delegate of NXApp be able to provide it with a NXStringTable
12: (via the stringTable method) that it can use to look up strings that
13: are presented to the user. In this application, the delegate of NXApp is
14: always an instance of the GraphApp class.
15:
16: If this were a larger program, the NXStringTable needed by this document
17: class would probably be in its own nib section, which would be loaded once
18: the first time a Graph3DDoc is created, and then shared.
19:
20: You may freely copy, distribute, and reuse the code in this example.
21: NeXT disclaims any warranty of any kind, expressed or implied, as to its
22: fitness for any particular use.
23: */
24:
25: #import "Graph.h"
26:
27: /* declare methods static to this class */
28: @interface Graph3DDoc(Private)
29: - _updateShape;
30: - _updateGraph:(BOOL)finalChange;
31: - (void)_equationChanged;
32: - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)expr enableVariables:(BOOL *)enableVars;
33: - _uOrVRangeSliderChanged:sender varName:(int)var;
34: - _uOrVRangeTextChanged:sender varName:(int)var;
35: - _write:(const char *)filename;
36: - _read:(const char *)filename;
37: - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val;
38: - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain;
39: - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes;
40: - (void)_setUVRangeOf:(int)var;
41: - (void)_setVariable:(int)index toValue:(float)val;
42: - (void)_setUpLighting;
43: - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel;
44:
45: @end
46:
47: @implementation Graph3DDoc
48:
49: /* variable names must be between A and H */
50: #define MIN_CONST 'A'
51: #define MAX_CONST 'H'
52:
53: /* indices into instance variable arrays */
54: #define X 0
55: #define Y 1
56: #define Z 2
57: #define U 0
58: #define V 1
59:
60: /* default resolution of a new graph */
61: #define DEFAULT_RES 12
62:
63: /* name used for the real document inside a doc wrapper */
64: #define DOC_NAME "/Graph3DDoc.xyzgraph"
65:
66: static void writeCellFloat(Cell *obj, NXTypedStream *ts);
67: static NXTypedStream *openDocStream(const char *filename, int mode);
68: static int removeFile(const char *file);
69:
70: - init {
71: /* the default initialization is to just open a new document */
72: return [self initFromFile:NULL];
73: }
74:
75: /*
76: * Opens a document. If file is NULL, we open an untitiled document. We also
77: * create a NXDataLinkManager and hook ourselves up as its delegate for doing
78: * Object Links.
79: */
80: - initFromFile:(const char *)file {
81: int rows, cols;
82: int i;
83: TextField *aCell;
84: int xyz;
85: RtPoint axis;
86: char realPath[MAXPATHLEN+1];
87:
88: [super init];
89:
90: /* load the UI from the nib section and set attributes not available in IB */
91: [NXApp loadNibSection:"Graph3DDoc.nib" owner:self withNames:NO fromZone:[self zone]];
92: [variableTexts getNumRows:&rows numCols:&cols];
93: for (i = rows; i--; ) {
94: aCell = [variableTexts cellAt:i :0];
95: [aCell setFloatingPointFormat:YES left:3 right:2];
96: [aCell setEnabled:NO];
97: }
98: [minUVText[U] setFloatingPointFormat:YES left:3 right:2];
99: [maxUVText[U] setFloatingPointFormat:YES left:3 right:2];
100: [minUVText[V] setFloatingPointFormat:YES left:3 right:2];
101: [maxUVText[V] setFloatingPointFormat:YES left:3 right:2];
102: [variableSliders setAutosizeCells:YES];
103: [resolutionText setEntryType:NX_POSINTTYPE];
104: [camera setFieldOfViewByAngle:90.0];
105: [self _setUpLighting];
106:
107: /* create Expression objects we will use to evaluate expressions */
108: for (xyz = X; xyz <= Z; xyz++) {
109: expr[xyz] = [[Expression allocFromZone:[self zone]] init];
110: [expr[xyz] setDimensions:2]; /* we eval u * v points */
111: }
112:
113: /* create and position the shape that we will use to show our data */
114: [[camera setWorldShape:[[PointMesh alloc] init]] free];
115: axis[1] = axis[2] = 0;
116: axis[0] = 1.0;
117: [[camera worldShape] rotateAngle:-90.0 axis:axis];
118:
119: if (file) {
120: /* read existing document */
121: if ([self _read:file]) {
122: name = NXCopyStringBufferFromZone(file, [self zone]);
123: if (realpath(name, realPath))
124: realName = NXCopyStringBufferFromZone(realPath, [self zone]);
125: [window setTitleAsFilename:name];
126: [self _updateShape];
127: linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self fromFile:file];
128: } else {
129: [self free]; /* couldn't load file */
130: return nil;
131: }
132: } else {
133: /*
134: * Create a new document. We initialize it with a trivial expression
135: * because that was easier than allowing the state where there is no
136: * current expression.
137: */
138: RtPoint from = {12.0, 8.0, 10.0}, to = {0.0, 0.0, 0.0};
139:
140: [camera setEyeAt:from toward:to roll:0.0];
141: /* set initial shape color to non-white on a color display */
142: if ([camera shouldDrawColor])
143: [[camera worldShape] setColor:NXConvertRGBToColor(51.0/255.0, 153.0/255.0, 116.0/255.0)];
144:
145: [window setTitleAsFilename:[[[NXApp delegate] stringTable] valueForStringKey:"untitled doc"]];
146: [resolutionSlider setIntValue:DEFAULT_RES];
147: [resolutionText setIntValue:DEFAULT_RES];
148: [equation[X] setStringValue:"u"];
149: [equation[Y] setStringValue:"v"];
150: [equation[Z] setStringValue:"A * (u^2 + v^2) + C"];
151: for (xyz = X; xyz <= Z; xyz++) {
152: [expr[xyz] setResolution:DEFAULT_RES];
153: }
154: [self _equationChanged];
155: [self _setUVRangeOf:U];
156: [self _setUVRangeOf:V];
157: [self _updateShape];
158: linkMgr = [[NXDataLinkManager allocFromZone:[self zone]] initWithDelegate:self];
159: }
160:
161: [[NXApp delegate] showThreeDPanel:self];
162: [window makeKeyAndOrderFront:self];
163: return self;
164: }
165:
166: - free {
167: int xyz;
168:
169: for (xyz = X; xyz <= Z; xyz++)
170: [expr[xyz] free];
171: NXZoneFree([self zone], name);
172: NXZoneFree([self zone], realName);
173: [linkMgr free];
174: return [super free];
175: }
176:
177: - (const char *)filename {
178: return name;
179: }
180:
181: - (const char *)realFilename {
182: return realName;
183: }
184:
185: /*
186: * This method is called whenever our window becomes the app's main window.
187: * When this happens we get the 3D Panel and set its camera to our camera, so
188: * the panel reflects the attributes of our window.
189: */
190: - windowDidBecomeMain:sender {
191: [[[NXApp delegate] threeDPanel] setCamera:camera];
192: return self;
193: }
194:
195: #define BUF_MAX 256
196:
197: /*
198: * This method is called when the user has just finished typing in an
199: * expression. This is where we validate that it is a legal expression.
200: * If we dont like the expression, we return YES, which tells the text object
201: * not to accept the user's entry.
202: */
203: - (BOOL)textWillEnd:textObject {
204: BOOL parseError;
205: BOOL enableVars[MAX_CONST - MIN_CONST + 1];
206: NXStringTable *stringTable;
207: Expression *tempExpr = [[Expression allocFromZone:[self zone]] init];
208: char buffer[BUF_MAX];
209: char *newText;
210: int length;
211:
212: /* get the text out of the text object where the expression was typed */
213: length = [textObject byteLength] + 1;
214: if (length > BUF_MAX)
215: newText = NXZoneMalloc(NXDefaultMallocZone(), sizeof(char) * length);
216: else
217: newText = buffer;
218: [textObject getSubstring:newText start:0 length:length];
219:
220: if (*newText) {
221: parseError = [self _parseEquationFrom:newText using:tempExpr enableVariables:enableVars];
222: if (!parseError) {
223: /* parse the new equation into the real expressions */
224: [self _equationChanged];
225:
226: /* update the shape object with current results and display it */
227: [self _updateGraph:YES];
228: }
229: } else
230: parseError = YES;
231:
232: if (parseError) {
233: stringTable = [[NXApp delegate] stringTable];
234: NXRunAlertPanel([stringTable valueForStringKey:"parse alert title"],
235: [stringTable valueForStringKey:"3D parse alert message"],
236: [stringTable valueForStringKey:"ok button"],
237: NULL, NULL);
238: }
239: [tempExpr free];
240: if (length > BUF_MAX)
241: NXZoneFree(NXDefaultMallocZone(), newText);
242: return parseError;
243: }
244:
245: /* This method does most of the work of parsing a new expression. */
246: - (void)_equationChanged {
247: BOOL enableVars[MAX_CONST - MIN_CONST + 1];
248: int i;
249: int xyz;
250: float newVarVal;
251:
252: for (i = 0; i <= MAX_CONST - MIN_CONST; i++)
253: enableVars[i] = NO;
254: for (xyz = X; xyz <= Z; xyz++) {
255: (void)[self _parseEquationFrom:[equation[xyz] stringValue] using:expr[xyz] enableVariables:enableVars];
256: }
257:
258: /* turn on the UI for constants used in these expressions */
259: for (i = 0; i <= MAX_CONST - MIN_CONST; i++) {
260: if ([[variableSliders cellAt:i :0] isEnabled])
261: newVarVal = [[variableSliders cellAt:i :0] floatValue];
262: else
263: newVarVal = 1.0;
264: [self _initVariable:i enable:enableVars[i] value:newVarVal];
265: }
266:
267: /* update the range of the independent variables */
268: [self _setUVRangeOf:U];
269: [self _setUVRangeOf:V];
270: }
271:
272: /*
273: * This method is called after the user typed in a new expression. The
274: * expression has already been verified in our textWillEnd: method.
275: */
276: - equationChanged:sender {
277: /* reselect the equation field so the user can type another */
278: [sender selectCell:[sender selectedCell]];
279: return self;
280: }
281:
282: /* called when either the minu, maxu, minv or maxv slider changes */
283: - _uOrVRangeSliderChanged:sender varName:(int)var {
284: TextFieldCell *text;
285: SliderCell *slider;
286: NXEvent *event;
287:
288: /* update the associated text field */
289: slider = [sender selectedCell];
290: if (slider == maxUVSlider[var])
291: text = maxUVText[var];
292: else if (slider == minUVSlider[var])
293: text = minUVText[var];
294: else
295: text = nil;
296: NX_ASSERT(text, "Funny sender of _uOrVRangeSliderChanged: message");
297: [text setFloatValue:[slider floatValue]];
298:
299: /* update the u or v range in the Expression and display the new graph */
300: [self _setUVRangeOf:var];
301: event = [NXApp currentEvent];
302: [self _updateGraph:event->type == NX_LMOUSEUP];
303: return self;
304: }
305:
306: /* called when either the minu or maxu slider changes */
307: - uRangeSliderChanged:sender {
308: return [self _uOrVRangeSliderChanged:sender varName:U];
309: }
310:
311: /* called when either the minv or maxv slider changes */
312: - vRangeSliderChanged:sender {
313: return [self _uOrVRangeSliderChanged:sender varName:V];
314: }
315:
316: /* called when either the minu, maxu, minv or maxv text changes */
317: - _uOrVRangeTextChanged:sender varName:(int)var {
318: TextFieldCell *text;
319: SliderCell *slider;
320: float val;
321:
322: /*
323: * update the associated slider. If the value typed is outside the current
324: * range of the slider, we extend its range.
325: */
326: text = [sender selectedCell];
327: if (text == maxUVText[var])
328: slider = maxUVSlider[var];
329: else if (text == minUVText[var])
330: slider = minUVSlider[var];
331: else
332: slider = nil;
333: NX_ASSERT(slider, "Funny sender of _uOrVRangeTextChanged: message");
334: val = [text floatValue];
335: [self _setSliderCell:slider value:val minLabel:nil maxLabel:nil];
336:
337: /* update the u or v range in the Expression and display the new graph */
338: [self _setUVRangeOf:var];
339: [self _updateGraph:YES];
340: [sender selectText:self];
341: return self;
342: }
343:
344: /* called when either the minu or maxu text changes */
345: - uRangeTextChanged:sender {
346: return [self _uOrVRangeTextChanged:sender varName:U];
347: }
348:
349: /* called when either the minv or maxv text changes */
350: - vRangeTextChanged:sender {
351: return [self _uOrVRangeTextChanged:sender varName:V];
352: }
353:
354: /* called when one of the variables' sliders changes */
355: - variableSliderChanged:sender {
356: int index;
357: float val;
358: NXEvent *event;
359:
360: /* update the associated text field */
361: index = [variableSliders selectedRow];
362: val = [[variableSliders selectedCell] floatValue];
363: [[variableTexts cellAt:index :0] setFloatValue:val];
364: [self _setVariable:index toValue:val];
365: event = [NXApp currentEvent];
366: [self _updateGraph:event->type == NX_LMOUSEUP];
367: return self;
368: }
369:
370: /* called when one of the variables' text fields changes */
371: - variableTextChanged:sender {
372: int index;
373: float val;
374: TextFieldCell *text;
375: SliderCell *slider;
376:
377: /* update the associated slider */
378: text = [variableTexts selectedCell];
379: index = [variableTexts selectedRow];
380: slider = [variableSliders cellAt:index :0];
381: val = [text floatValue];
382: [self _setSliderCell:slider value:val
383: minLabel:[variableMinLabels cellAt:index :0]
384: maxLabel:[variableMaxLabels cellAt:index :0]];
385:
386: [self _setVariable:index toValue:val];
387: [self _updateGraph:YES];
388: [sender selectCell:text];
389: return self;
390: }
391:
392: /* The maximum allowable resolution. ~10K polygons */
393: #define MAX_RES 100
394:
395: /* called when either the resolution slider or text fields changes */
396: - resolutionChanged:sender {
397: Cell *senderCell, *otherCell;
398: int iVal;
399: float fVal;
400: int xyz;
401: NXEvent *event;
402:
403: if ([[sender cellAt:0 :0] isKindOfClassNamed:"SliderCell"]) {
404: senderCell = resolutionSlider;
405: otherCell = resolutionText;
406: } else {
407: senderCell = resolutionText;
408: otherCell = resolutionSlider;
409: }
410:
411: fVal = [senderCell floatValue];
412: iVal = rint(fVal);
413: if (iVal > MAX_RES)
414: iVal = MAX_RES;
415: [otherCell setIntValue:iVal];
416:
417: /* update the Expression's resolution and display the new graph */
418: for (xyz = X; xyz <= Z; xyz++)
419: [expr[xyz] setResolution:iVal];
420: event = [NXApp currentEvent];
421: [self _updateGraph:event->type != NX_LMOUSEDRAGGED];
422: if (senderCell == resolutionText)
423: [sender selectText:self];
424: return self;
425: }
426:
427: /*
428: * Copies the current view of the graph into the Pasteboard as Renderman code.
429: * This is sent from the Copy Graph menu item.
430: */
431: - copyGraph:sender {
432: Pasteboard *pb;
433: const char *types[2];
434: NXDataLink *link;
435:
436: pb = [Pasteboard new];
437: types[0] = N3DRIBPboardType;
438: types[1] = NXDataLinkPboardType;
439: [self _writeGraphToPasteboard:pb types:types num:2];
440: link = [[NXDataLink alloc] initLinkedToSourceSelection:[NXSelection allSelection] managedBy:linkMgr supportingTypes:&N3DRIBPboardType count:1];
441: [link writeToPasteboard:pb];
442: [link free];
443: return self;
444: }
445:
446: /*
447: * Copies the current view of the graph into the Pasteboard as RIB.
448: * This is used by the Copy Graph menu item and to support Object Links.
449: *
450: * Types must contain N3DRIBPboardType.
451: */
452: - _writeGraphToPasteboard:(Pasteboard *)pb types:(const char *const *)types num:(int)numTypes {
453: NXStream *st;
454: char *data;
455: int dataLen, maxDataLen;
456:
457: /* Open a stream on memory where we will collect the PostScript */
458: st = NXOpenMemory(NULL, 0, NX_WRITEONLY);
459:
460: /* Tell the Pasteboard we're going to copy RIB */
461: [pb declareTypes:types num:numTypes owner:nil];
462:
463: /* writes the RIB for the whole graph into the stream */
464: [camera copyRIBCode:st];
465:
466: /* get the buffered up PostScript out of the stream */
467: NXGetMemoryBuffer(st, &data, &dataLen, &maxDataLen);
468:
469: /* put the buffer in the Pasteboard, free the stream (and the buffer) */
470: [pb writeType:N3DRIBPboardType data:data length:dataLen];
471: NXCloseMemory(st, NX_FREEBUFFER);
472: return self;
473: }
474:
475: /*** Object Links support - methods called by the DataLinkManager ***/
476:
477: /* called by the DataLinkManager when new data is needed for a link */
478: - copyToPasteboard:(Pasteboard *)pboard at:(NXSelection *)selection cheapCopyAllowed:(BOOL)flag {
479: NX_ASSERT([selection isEqual:[NXSelection allSelection]] || [selection isEqual:[NXSelection currentSelection]], "Funny selection passed to copyToPasteboard:at:");
480: [self _writeGraphToPasteboard:pboard types:&N3DRIBPboardType num:1];
481: return self;
482: }
483:
484: /* returns the window for the given selection */
485: - windowForSelection:(NXSelection *)selection {
486: return window; /* all our sels are always in our one window */
487: }
488:
489: /*
490: * We support continuously updating links by tracking changes to links to
491: * us from open documents. This is easy for Graph because all links can
492: * only be to the whole graph, so any change in the graph is a change relevant
493: * to any link.
494: */
495: - (BOOL)dataLinkManagerTracksLinksIndividually:(NXDataLinkManager *)sender {
496: return YES;
497: }
498:
499: /*
500: * Sent when we should start tracking a link. We just keep all links we're
501: * tracking in a list, and send them a message whenever the graph is changed.
502: */
503: - dataLinkManager:(NXDataLinkManager *)sender startTrackingLink:(NXDataLink *)link {
504: if (!linksTracked)
505: linksTracked = [[List allocFromZone:[self zone]] init];
506: [linksTracked addObject:link];
507: return self;
508: }
509:
510: /* Sent when we can forget a links we're tracking */
511: - dataLinkManager:(NXDataLinkManager *)sender stopTrackingLink:(NXDataLink *)link {
512: [linksTracked removeObject:link];
513: if ([linksTracked count] == 0) {
514: [linksTracked free];
515: linksTracked = nil;
516: }
517: return self;
518: }
519:
520: /*
521: * Called by other methods of Graph3DDoc whenever we make a change to the
522: * document. We tell the DataLinkManager about the change, and also notify
523: * any links we are tracking.
524: */
525: - docChanged {
526: [linkMgr documentEdited];
527: [linksTracked makeObjectsPerform:@selector(sourceEdited)];
528: [window setDocEdited:YES];
529: return self;
530: }
531:
532: /* called by ThreeDPanel when it changes something about the document */
533: - threeDPanelDidChangeDoc:(ThreeDPanel *)sender {
534: [self docChanged];
535: return self;
536: }
537:
538:
539: /* called when Save, Save As, or Save To is picked from the menu */
540: - save:sender { return [self _doSaveWithNewName:NO retainName:YES]; }
541: - saveAs:sender { return [self _doSaveWithNewName:YES retainName:YES]; }
542: - saveTo:sender { return [self _doSaveWithNewName:YES retainName:NO]; }
543:
544: /*
545: * All varieties of save go through this routine. It covers all the cases
546: * of running the Save Panel and retaining the name chosen.
547: */
548: - _doSaveWithNewName:(BOOL)doNewName retainName:(BOOL)doRetain {
549: SavePanel *savePanel;
550: const char *saveName; /* filename to save into */
551: NXZone *zone;
552: BOOL previouslySaved = (name != NULL);
553: char realPath[MAXPATHLEN+1];
554:
555: /*
556: * If the file is untitled or we are saving under a different name,
557: * run the save panel to get a new name.
558: */
559: zone = [self zone];
560: if (!name || doNewName) {
561: savePanel = [SavePanel new];
562: [savePanel setRequiredFileType:"xyzgraph"];
563: if ([savePanel runModalForDirectory:NULL file:name]) {
564: /* if we want to keep this name, replace any old name */
565: if (doRetain) {
566: NXZoneFree(zone, name);
567: NXZoneFree(zone, realName);
568: realName = NULL;
569: name = NXCopyStringBufferFromZone([savePanel filename], zone);
570: }
571: saveName = [savePanel filename];
572: } else
573: return nil; /* user canceled */
574: } else
575: /* if we didn't run the Save Panel, save using the existing name */
576: saveName = name;
577: [self _write:saveName];
578: if (!doRetain)
579: [linkMgr documentSavedTo:saveName];
580: else if (!previouslySaved || doNewName)
581: [linkMgr documentSavedAs:saveName];
582: else
583: [linkMgr documentSaved];
584: if (doRetain) {
585: [window setDocEdited:NO];
586: [window setTitleAsFilename:name];
587: if (!realName && realpath(name, realPath)) {
588: realName = NXCopyStringBufferFromZone(realPath, [self zone]);
589: }
590: }
591: return self;
592: }
593:
594: - revertToSaved:sender {
595: int response;
596: NXStringTable *stringTable;
597: const char *shortName;
598: Graph3DDoc *newDoc;
599:
600: if ([window isDocEdited] && name) {
601: stringTable = [[NXApp delegate] stringTable];
602: shortName = strrchr(name, '/') + 1;
603: response = NXRunAlertPanel([stringTable valueForStringKey:"revert alert title"],
604: [stringTable valueForStringKey:"revert alert message"],
605: [stringTable valueForStringKey:"revert button"],
606: [stringTable valueForStringKey:"cancel button"],
607: NULL, shortName);
608: if (response == NX_ALERTDEFAULT) {
609: [window setDelegate:nil];
610: [window close];
611: [NXApp delayedFree:self];
612: [linkMgr documentClosed];
613: [linkMgr free];
614: linkMgr = nil;
615: newDoc = [[Graph3DDoc allocFromZone:[self zone]] initFromFile:name];
616: if (!newDoc) {
617: NXRunAlertPanel(
618: [stringTable valueForStringKey:"open alert title"],
619: [stringTable valueForStringKey:"open alert message"],
620: [stringTable valueForStringKey:"ok button"],
621: NULL, NULL, name);
622: }
623: }
624: }
625: return self;
626: }
627:
628: /* Called when the window is closing. We free the Graph3DDoc. */
629: - windowWillClose:sender {
630: int response;
631: NXStringTable *stringTable;
632: const char *shortName;
633:
634: if ([window isDocEdited]) {
635: stringTable = [[NXApp delegate] stringTable];
636: if (name) {
637: shortName = strrchr(name, '/') + 1;
638: } else {
639: shortName = [stringTable valueForStringKey:"untitled doc"];
640: }
641: response = NXRunAlertPanel([stringTable valueForStringKey:"close alert title"],
642: [stringTable valueForStringKey:"close alert message"],
643: [stringTable valueForStringKey:"save button"],
644: [stringTable valueForStringKey:"dont save button"],
645: [stringTable valueForStringKey:"cancel button"],
646: shortName);
647: if (response != NX_ALERTDEFAULT && response != NX_ALERTALTERNATE) {
648: return nil;
649: } else {
650: if (response == NX_ALERTDEFAULT && ![self save:sender])
651: return nil;
652: }
653: }
654: [NXApp delayedFree:self];
655: [linkMgr documentClosed];
656: return self; /* says its OK to close */
657: }
658:
659: /* Called whenever something changes to update the view of the graph. */
660: - _updateGraph:(BOOL)finalChange {
661: [self _updateShape]; /* update the values that we graph */
662: [camera display]; /* draw the new graph */
663: if (finalChange)
664: [self docChanged];
665: return self;
666: }
667:
668: /* Called whenever something changes to update the values that we graph. */
669: - _updateShape {
670: float *vals[3];
671: int numVals[3];
672: float min[3], max[3];
673: int xyz;
674:
675: /* extract the values from the Expressions. This may cause a recalc. */
676: for (xyz = X; xyz <= Z; xyz++) {
677: [expr[xyz] resultsVector:&vals[xyz] numVals:&numVals[xyz]];
678: [expr[xyz] resultsMin:&min[xyz] max:&max[xyz]];
679: }
680:
681: /* set the points of the graph */
682: [[camera worldShape] setPointsX:vals[X] y:vals[Y] z:vals[Z]
683: minX:min[X] minY:min[Y] minZ:min[Z]
684: maxX:max[X] maxY:max[Y] maxZ:max[Z]
685: numU:[expr[X] resolution] numV:[expr[X] resolution]];
686: return self;
687: }
688:
689: /* parses and validates the text for one equation field */
690: - (BOOL)_parseEquationFrom:(const char *)str using:(Expression *)anExpr enableVariables:(BOOL *)enableVars {
691: BOOL parseError;
692: EXPEnumState state; /* used to run through the vars of the expression */
693: const char *var;
694:
695: parseError = ![anExpr parse:str];
696: if (!parseError) {
697: /*
698: * If it parsed successfully, make sure all the variables are single
699: * letters, and are either "u", "v" or between "A" and "H". As we
700: * find suitable variables, we remember then in the enableVars
701: * array, so we can enable their controls later.
702: */
703: state = [anExpr beginVariableEnumeration];
704: while (var = [anExpr nextVariable:state])
705: if (*var && !var[1] && *var >= MIN_CONST && *var <= MAX_CONST)
706: enableVars[*var - MIN_CONST] = YES;
707: else if ((*var != 'u' && *var != 'v') || var[1])
708: parseError = YES;
709: [anExpr endVariableEnumeration:state];
710: }
711: return parseError;
712: }
713:
714: /*
715: * Writes a document to the given file using typedstreams. We're very careful
716: * about catching exceptions here so we can tell the user if his file didn't
717: * get written out successfully.
718: */
719: - _write:(const char *)filename {
720: NXTypedStream *ts;
721: const char *stringVal;
722: int intVal;
723: char charVal;
724: RtPoint fromPoint;
725: RtPoint toPoint;
726: float aRollAngle;
727: RtMatrix shapeXform;
728: int rows, cols;
729: int numVars;
730: int i;
731: NXStringTable *stringTable;
732: volatile BOOL hadAnError = NO;
733: char buffer[MAXPATHLEN+1];
734: int xyz;
735: NXRect winFrame;
736:
737: /* cons up the name of the backup file and remove it */
738: strcpy(buffer, filename);
739: strcat(buffer, "~");
740: removeFile(buffer);
741:
742: /* move the existing file to the backup */
743: rename(filename, buffer);
744:
745: /* create the new document as a file package (doc wrapper) */
746: strcpy(buffer, filename);
747: strcat(buffer, DOC_NAME);
748: mkdir(filename, 0777);
749: ts = NXOpenTypedStreamForFile(buffer, NX_WRITEONLY);
750: if (ts) {
751: NX_DURING
752: intVal = 2; /* version of this write's data */
753: NXWriteType(ts, "i", &intVal);
754: for (xyz = X; xyz <= Z; xyz++) {
755: stringVal = [expr[xyz] text];
756: NXWriteType(ts, "*", &stringVal);
757: }
758: [variableSliders getNumRows:&rows numCols:&cols];
759: numVars = 0;
760: for (i = 0; i < rows; i++) {
761: if ([[variableSliders cellAt:i :0] isEnabled])
762: numVars++;
763: }
764: NXWriteType(ts, "i", &numVars);
765: for (i = 0; i < rows; i++)
766: if ([[variableSliders cellAt:i :0] isEnabled]) {
767: charVal = MIN_CONST + i;
768: NXWriteType(ts, "c", &charVal);
769: writeCellFloat([variableSliders cellAt:i :0], ts);
770: }
771: intVal = [expr[X] resolution];
772: NXWriteType(ts, "i", &intVal);
773: writeCellFloat(minUVSlider[U], ts);
774: writeCellFloat(maxUVSlider[U], ts);
775: writeCellFloat(minUVSlider[V], ts);
776: writeCellFloat(maxUVSlider[V], ts);
777: NXWriteColor(ts, [[camera worldShape] color]);
778: NXWriteColor(ts, [camera backgroundColor]);
779: intVal = [[camera worldShape] surfaceType];
780: NXWriteType(ts, "i", &intVal);
781: [camera getEyeAt:&fromPoint toward:&toPoint roll:&aRollAngle];
782: NXWriteArray(ts, "f", 3, fromPoint);
783: NXWriteArray(ts, "f", 3, toPoint);
784: NXWriteType(ts, "f", &aRollAngle);
785: [[camera worldShape] getTransformMatrix:shapeXform];
786: NXWriteArray(ts, "f", 4, shapeXform[0]);
787: NXWriteArray(ts, "f", 4, shapeXform[1]);
788: NXWriteArray(ts, "f", 4, shapeXform[2]);
789: NXWriteArray(ts, "f", 4, shapeXform[3]);
790: [window getFrame:&winFrame];
791: NXWriteRect(ts, &winFrame);
792: NXCloseTypedStream(ts);
793: NX_HANDLER
794: hadAnError = YES;
795: NX_DURING
796: NXCloseTypedStream(ts);
797: NX_HANDLER
798: /* ignore any error at this point */
799: NX_ENDHANDLER
800: NX_ENDHANDLER
801: } else
802: hadAnError = YES;
803: if (hadAnError) {
804: stringTable = [[NXApp delegate] stringTable];
805: NXRunAlertPanel([stringTable valueForStringKey:"save alert title"],
806: [stringTable valueForStringKey:"save alert message"],
807: [stringTable valueForStringKey:"ok button"],
808: NULL, NULL, filename);
809: return nil;
810: } else
811: return self;
812: }
813:
814: /* reads a document from the given file using typedstreams */
815: - _read:(const char *)filename {
816: NXTypedStream *ts;
817: char *stringVal;
818: int i;
819: char varName;
820: float floatVal1, floatVal2;
821: int intVal;
822: RtPoint fromPoint;
823: RtPoint toPoint;
824: float aRollAngle;
825: RtMatrix shapeXform;
826: int version;
827: int numVars;
828: BOOL parseError;
829: int xyz, uv;
830: NXRect winFrame;
831:
832: ts = openDocStream(filename, NX_READONLY);
833: if (ts) {
834: NX_DURING
835: NXReadType(ts, "i", &version); /* read file version */
836: NX_ASSERT(version <= 2, "Unknown file version in -read");
837: for (xyz = X; xyz <= Z; xyz++) {
838: NXReadType(ts, "*", &stringVal);
839: [equation[xyz] setStringValue:stringVal];
840: parseError = ![expr[xyz] parse:stringVal];
841: free(stringVal);
842: NX_ASSERT(!parseError, "Bad expression stored in data file");
843: if (parseError)
844: NX_VALRETURN(nil);
845: }
846: NXReadType(ts, "i", &numVars);
847: for (i = 0; i < numVars; i++) {
848: NXReadType(ts, "c", &varName);
849: NXReadType(ts, "f", &floatVal1);
850: [self _initVariable:varName - MIN_CONST enable:YES
851: value:floatVal1];
852: }
853: NXReadType(ts, "i", &intVal); /* read resolution */
854: [resolutionText setIntValue:intVal];
855: [resolutionSlider setIntValue:intVal];
856: for (xyz = X; xyz <= Z; xyz++)
857: [expr[xyz] setResolution:intVal];
858: for (uv = U; uv <= V; uv++) {
859: NXReadType(ts, "f", &floatVal1); /* read min */
860: NXReadType(ts, "f", &floatVal2); /* read max */
861: [minUVText[uv] setFloatValue:floatVal1];
862: [self _setSliderCell:minUVSlider[uv] value:floatVal1
863: minLabel:nil maxLabel:nil];
864: [maxUVText[uv] setFloatValue:floatVal2];
865: [self _setSliderCell:maxUVSlider[uv] value:floatVal2
866: minLabel:nil maxLabel:nil];
867: [self _setUVRangeOf:uv];
868: }
869: [[camera worldShape] setColor:NXReadColor(ts)];
870: [camera setBackgroundColor:NXReadColor(ts)];
871: NXReadType(ts, "i", &intVal); /* read surface type */
872: [camera setSurfaceTypeForAll:intVal chooseHider:YES];
873: NXReadArray(ts, "f", 3, fromPoint);
874: NXReadArray(ts, "f", 3, toPoint);
875: NXReadType(ts, "f", &aRollAngle);
876: [camera setEyeAt:fromPoint toward:toPoint roll:aRollAngle];
877: NXReadArray(ts, "f", 4, shapeXform[0]);
878: NXReadArray(ts, "f", 4, shapeXform[1]);
879: NXReadArray(ts, "f", 4, shapeXform[2]);
880: NXReadArray(ts, "f", 4, shapeXform[3]);
881: [[camera worldShape] setTransformMatrix:shapeXform];
882: NXReadRect(ts, &winFrame);
883: [window placeWindow:&winFrame];
884: NXCloseTypedStream(ts);
885: /* must use NX_VALRETURN to return from inside a NX_DURING */
886: NX_VALRETURN(self);
887: NX_HANDLER
888: NX_DURING
889: NXCloseTypedStream(ts);
890: NX_HANDLER
891: /* ignore any error at this point */
892: NX_ENDHANDLER
893: return nil;
894: NX_ENDHANDLER
895: } else
896: return nil;
897: }
898:
899: /*
900: * Inits a variable to either be on or off. Used when we discover a new
901: * set of variables after a parse, or to set up the variables from a document
902: * that we read from a file.
903: */
904: - (void)_initVariable:(int)index enable:(BOOL)flag value:(float)val {
905: TextFieldCell *text;
906: SliderCell *slider;
907:
908: /* set the initial value in the Expression */
909: if (flag)
910: [self _setVariable:index toValue:val];
911:
912: /* if the enabled status is changing... */
913: if (flag != [[variableSliders cellAt:index :0] isEnabled])
914: if (flag) {
915:
916: /* enable all controls associated with the variable */
917: [[variableLabels cellAt:index :0] setTextGray:NX_BLACK];
918: slider = [variableSliders cellAt:index :0];
919: [slider setEnabled:YES];
920: [self _setSliderCell:slider value:val
921: minLabel:[variableMinLabels cellAt:index :0]
922: maxLabel:[variableMaxLabels cellAt:index :0]];
923: text = [variableTexts cellAt:index :0];
924: [text setEnabled:YES];
925: [text setFloatValue:val];
926: } else if (!flag) {
927:
928: /* disable all controls associated with the variable */
929: [[variableLabels cellAt:index :0] setTextGray:NX_DKGRAY];
930: slider = [variableSliders cellAt:index :0];
931: [slider setFloatValue:[slider minValue]];
932: [slider setEnabled:NO];
933: text = [variableTexts cellAt:index :0];
934: [text setStringValue:NULL];
935: [text setEnabled:NO];
936: }
937: }
938:
939: /*
940: * sets the range of the u or v variable in the Expression.
941: * It ensures that the min value isn't greater than the max value.
942: */
943: - (void)_setUVRangeOf:(int)var {
944: int xyz;
945: float min = [minUVSlider[var] floatValue];
946: float max = [maxUVSlider[var] floatValue];
947: char *varName = (var == U) ? "u" : "v";
948:
949: for (xyz = X; xyz <= Z; xyz++) {
950: [expr[xyz] setVar:varName min:MIN(min, max) max:MAX(min, max)];
951: /* set up two dimensions of evaluation */
952: [expr[xyz] setVar:varName dimension:var];
953: }
954: }
955:
956: /* sets one of the expressions' variables to a value */
957: - (void)_setVariable:(int)index toValue:(float)val {
958: char varName[2];
959: int xyz;
960:
961: varName[0] = MIN_CONST + index;
962: varName[1] = '\0';
963: for (xyz = X; xyz <= Z; xyz++)
964: [expr[xyz] setVar:varName value:val];
965: }
966:
967: /* sets a slider to a value and adjust min/max */
968: - (void)_setSliderCell:(SliderCell *)slider value:(float)val minLabel:(Cell *)minLabel maxLabel:(Cell *)maxLabel {
969: if (val < [slider minValue]) {
970: [window disableDisplay]; /* so slider won't flash to new location */
971: [slider setMinValue:val];
972: [window reenableDisplay];
973: [minLabel setIntValue:floor(val)];
974: } else if (val > [slider maxValue]) {
975: [window disableDisplay]; /* so slider won't flash to new location */
976: [slider setMaxValue:val];
977: [window reenableDisplay];
978: [maxLabel setIntValue:ceil(val)];
979: }
980: [slider setFloatValue:val];
981: }
982:
983: /* sets up some default lights for the camera */
984: - (void)_setUpLighting {
985: RtPoint distantlight_from, distantlight2_from, pointlight_from;
986: RtPoint distantlight_to;
987: RtFloat intensity;
988: N3DLight *pointLight, *distantLight, *distantLight2, *ambientLight;
989:
990: if ([[camera lightList] count] == 4)
991: return; /* we've already added lights to this camera */
992:
993: pointlight_from[0] = 12.0;
994: pointlight_from[1] = -7.0;
995: pointlight_from[2] = 8.5;
996: distantlight_from[0] = 12.0;
997: distantlight_from[1] = -7.0;
998: distantlight_from[2] = 8.5;
999: distantlight2_from[0] = 3.0;
1000: distantlight2_from[1] = 2.0;
1001: distantlight2_from[2] = 8.5;
1002:
1003: distantlight_to[0] = 0.0;
1004: distantlight_to[1] = 0.0;
1005: distantlight_to[2] = 0.0;
1006:
1007: intensity = 60.0;
1008: pointLight = [[N3DLight allocFromZone:[camera zone]] init];
1009: [pointLight makePointFrom:pointlight_from intensity:intensity];
1010: [camera addLight:pointLight];
1011:
1012: intensity = 0.6;
1013: distantLight = [[N3DLight allocFromZone:[camera zone]] init];
1014: [distantLight makeDistantFrom:distantlight_from to:distantlight_to intensity:intensity];
1015: [camera addLight:distantLight];
1016:
1017: intensity = 0.9;
1018: distantLight2 = [[N3DLight allocFromZone:[camera zone]] init];
1019: [distantLight2 makeDistantFrom:distantlight2_from to:distantlight_to intensity:intensity];
1020: [camera addLight:distantLight2];
1021:
1022: intensity = 0.15;
1023: ambientLight = [[N3DLight allocFromZone:[camera zone]] init];
1024: [ambientLight makeAmbientWithIntensity:intensity];
1025: [camera addLight:ambientLight];
1026: }
1027:
1028: /*
1029: * These methods are called as the IB file is unarchived. We override them to
1030: * store these outlets in arrays, since IB doesnt know how to declare arrays
1031: * of outlets.
1032: */
1033: - setEquationX:obj { equation[X] = obj; return self; }
1034: - setEquationY:obj { equation[Y] = obj; return self; }
1035: - setEquationZ:obj { equation[Z] = obj; return self; }
1036: - setMinUSlider:obj { minUVSlider[U] = obj; return self; }
1037: - setMinVSlider:obj { minUVSlider[V] = obj; return self; }
1038: - setMaxUSlider:obj { maxUVSlider[U] = obj; return self; }
1039: - setMaxVSlider:obj { maxUVSlider[V] = obj; return self; }
1040: - setMinUText:obj { minUVText[U] = obj; return self; }
1041: - setMinVText:obj { minUVText[V] = obj; return self; }
1042: - setMaxUText:obj { maxUVText[U] = obj; return self; }
1043: - setMaxVText:obj { maxUVText[V] = obj; return self; }
1044:
1045: @end
1046:
1047: /* little utility proc to write out the float value of a control */
1048: static void writeCellFloat(Cell *obj, NXTypedStream *ts) {
1049: float val;
1050:
1051: val = [obj floatValue];
1052: NXWriteType(ts, "f", &val);
1053: }
1054:
1055: /* Opens a stream on the document regardless of whether its a doc wrapper. */
1056: static NXTypedStream *openDocStream(const char *filename, int mode) {
1057: NXTypedStream *ts = NULL;
1058: struct stat statInfo;
1059: char buffer[MAXPATHLEN+1];
1060:
1061: if (stat(filename, &statInfo) == 0) {
1062: if (statInfo.st_mode & S_IFDIR) {
1063: strcpy(buffer, filename);
1064: strcat(buffer, DOC_NAME);
1065: ts = NXOpenTypedStreamForFile(buffer, mode);
1066: } else {
1067: ts = NXOpenTypedStreamForFile(filename, mode);
1068: }
1069: }
1070: return ts;
1071: }
1072:
1073: /* removes a directory, removing anything inside it. Does not recurse */
1074: static int removeFile(const char *file) {
1075: DIR *dirp;
1076: struct stat st;
1077: struct direct *dp;
1078: char *leaf = NULL;
1079: char path[MAXPATHLEN+1];
1080:
1081: if (!stat(file, &st)) {
1082: if ((st.st_mode & S_IFMT) == S_IFDIR) {
1083: dirp = opendir(file);
1084: for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
1085: if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) {
1086: if (!leaf) {
1087: strcpy(path, file);
1088: strcat(path, "/");
1089: leaf = path + strlen(path);
1090: }
1091: strcpy(leaf, dp->d_name);
1092: if (unlink(path)) {
1093: closedir(dirp);
1094: return -1;
1095: }
1096: }
1097: }
1098: return rmdir(file);
1099: } else {
1100: return unlink(file);
1101: }
1102: }
1103:
1104: return -1;
1105: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.