001: /*
002: * $RCSfile: LwsObject.java,v $
003: *
004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * - Redistribution of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * - Redistribution in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * Neither the name of Sun Microsystems, Inc. or the names of
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * This software is provided "AS IS," without a warranty of any
023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
034: * POSSIBILITY OF SUCH DAMAGES.
035: *
036: * You acknowledge that this software is not designed, licensed or
037: * intended for use in the design, construction, operation or
038: * maintenance of any nuclear facility.
039: *
040: * $Revision: 1.4 $
041: * $Date: 2007/02/09 17:20:09 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.loaders.lw3d;
046:
047: import java.awt.Component;
048: import java.io.*;
049: import java.util.Vector;
050: import javax.media.j3d.*;
051: import javax.vecmath.*;
052: import java.util.Enumeration;
053: import java.util.StringTokenizer;
054: import java.util.Vector;
055: import com.sun.j3d.utils.geometry.ColorCube;
056: import com.sun.j3d.loaders.ParsingErrorException;
057: import com.sun.j3d.loaders.IncorrectFormatException;
058: import java.net.MalformedURLException;
059:
060: import java.net.*;
061:
062: /**
063: * An LwsObject is passed a handle to the text file that contains the scene
064: * and is responsible for parsing a particular section of that file that
065: * describes a single object. This section defines the type of object
066: * (either a group or some geometry specified by an Object file) and
067: * some keyframe data that describes the an animation of the
068: * orientation/position/scale of the object. For geometry objects, this
069: * class instantiates a J3dLwoParser object to parse the binary data file.
070: * For the keyframe data, the class instantiates an LwsMotion object to
071: * parse and store that data.
072: */
073:
074: class LwsObject extends TextfileParser implements LwsPrimitive {
075:
076: // data from the file
077: String fileName;
078: String objName;
079: LwsMotion motion;
080: int parent;
081: TransformGroup objectTransform;
082: Vector objectBehavior;
083: Vector shapeList = null;
084: boolean hasPivot = false;
085: TransformGroup pivotTransGroup = null;
086:
087: URL urlName;
088: String protocol;
089: int fileType;
090:
091: /**
092: * Constructor: parses object section of this scene file and
093: * creates all appropriate data structures to hold the information
094: * @param st StreamTokenizer for scene file
095: * @param loadObject boolean specifying that object is not a lw3d Null
096: * object
097: * @param firstFrame int holding the first frame of the scene's animation
098: * @param totalFrames int holding the total number of frames in the scene
099: * @param totalTime float holding the total time of the animation
100: * @param loader Lw3dLoader loader object that was created by user
101: * @param debugVals in holding current debug flags
102: */
103: LwsObject(StreamTokenizer st, boolean loadObject, int firstFrame,
104: int totalFrames, float totalTime, Lw3dLoader loader,
105: int debugVals) throws java.io.FileNotFoundException,
106: ParsingErrorException {
107: debugPrinter.setValidOutput(debugVals);
108: parent = -1;
109:
110: fileType = loader.getFileType();
111:
112: try {
113: if (loadObject) {
114: // If this is true, then the object is actually described
115: // in an external geometry file. Get that filename
116: fileName = getString(st);
117: String path = null;
118: switch (loader.getFileType()) {
119: case Lw3dLoader.FILE_TYPE_FILENAME:
120: // No other case is current implemented in this loader
121: path = loader.getBasePath();
122: if (path == null)
123: path = loader.getInternalBasePath();
124: if (path != null) {
125: // It's not sufficient to just use the base path.
126: // Lightwave scene files tend to embed path names
127: // to object files that are only correct if you
128: // start from a certain directory. For example, a
129: // scene file in data/ may point to an object in
130: // data/Objects - but in this case
131: // getInternalBasePath() would be data/, so you'd
132: // look for the object in data/data/Objects...
133: // To attempt to overcome this confusing state of
134: // affairs, let's check path/filename
135: // first, then iterate all the way up the path
136: // until there are no more members of path. This
137: // will ensure that we'll at least pick up data
138: // files if they exist off of one of the parent
139: // directories of wherever the scene file is
140: // stored.
141: // No, I don't really like this solution, but I don't
142: // know of a better general approach for now...
143:
144: fileName = getQualifiedFilename(path, fileName);
145: }
146: break;
147: case Lw3dLoader.FILE_TYPE_URL:
148: path = "";
149: URL pathUrl = loader.getBaseUrl();
150: if (pathUrl != null) {
151: path = pathUrl.toString();
152: // store the protocol
153: protocol = pathUrl.getProtocol();
154: } else {
155: path = loader.getInternalBaseUrl();
156: // store the protocol
157: protocol = (new URL(path)).getProtocol();
158: }
159:
160: urlName = getQualifiedURL(path, fileName);
161: break;
162: }
163: } else
164: // else the object is a lw3d Null object; essentially a group
165: // which contains other objects
166: objName = getString(st);
167: skip(st, "ShowObject", 2);
168: debugOutputLn(LINE_TRACE,
169: "skipped showobject, about to get objectmotion");
170: getAndCheckString(st, "ObjectMotion");
171: debugOutputLn(LINE_TRACE, "got string " + st.sval);
172: // Create an LwsMotion object to parse the animation data
173: motion = new LwsMotion(st, firstFrame, totalFrames,
174: totalTime, debugVals);
175: debugOutputLn(LINE_TRACE, "got motion");
176: boolean hasParent = false; // keeps bones prim from reassigning par
177:
178: // TODO: This isn't the greatest way to stop parsing an object
179: // (keying on the ShowOptions parameter), but it seems to be valid
180: // for the current lw3d format
181: while (!isCurrentToken(st, "ShadowOptions")) {
182: if (!hasParent && isCurrentToken(st, "ParentObject")) {
183: parent = (int) getNumber(st);
184: hasParent = true;
185: } else if (isCurrentToken(st, "PivotPoint")) {
186: // PivotPoint objects are tricky - they make the object
187: // rotate about this new point instead of the default
188: // So setup transform groups such that this happens
189: // correctly.
190: hasPivot = true;
191: float x = (float) getNumber(st);
192: float y = (float) getNumber(st);
193: float z = (float) getNumber(st);
194: Vector3f pivotPoint = new Vector3f(-x, -y, z);
195: Transform3D pivotTransform = new Transform3D();
196: pivotTransform.set(pivotPoint);
197: pivotTransGroup = new TransformGroup(pivotTransform);
198: pivotTransGroup
199: .setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
200: }
201:
202: else if (isCurrentToken(st, "ObjDissolve")) {
203: // Just handle it for now, don't care about value
204: EnvelopeHandler env = new EnvelopeHandler(st,
205: totalFrames, totalTime);
206: }
207: st.nextToken();
208: }
209: getNumber(st); // skip shadow options parameter
210: debugOutputLn(LINE_TRACE, "done with LwsObject constructor");
211: } catch (MalformedURLException e) {
212: throw new FileNotFoundException(e.getMessage());
213: } catch (IOException e) {
214: throw new ParsingErrorException(e.getMessage());
215: } catch (NumberFormatException e) {
216: throw new ParsingErrorException("Expected a number, got "
217: + e.getMessage());
218: }
219: }
220:
221: /**
222: * This method takes the given path and filename and checks whether
223: * that file exists. If not, it chops off the last part of pathname
224: * and recurse to try again. This has the effect of searching all the
225: * way up a given pathname for the existence of the file anywhere
226: * along that path. This is somewhat of a hack to get around the
227: * constraining way that Lightwave uses to define its data object
228: * locations in its scene files.
229: *
230: * If the filename is absolute, it will use the path information from
231: * the filename first, then the path information from the lws file.
232: * If the file can not be found in these locations, the local directory
233: * will be searched.
234: * In addition, it will look for filenames converted to lowercase to
235: * make it easier to use between Windows and Unix.
236: */
237:
238: String getQualifiedFilename(String pathname, String filename)
239: throws java.io.FileNotFoundException {
240:
241: int index;
242: String pathname2 = "";
243:
244: // System.out.println ("pathname:"+pathname+" filename:"+filename);
245:
246: // Do we have an absolute filename ?
247: if (filename.indexOf(File.separator) == 0) {
248: if ((index = filename.lastIndexOf(File.separator)) != -1) {
249: pathname2 = filename.substring(0, index + 1);
250: filename = filename.substring(index + 1);
251: } else {
252: return null; // something out of the ordinary happened
253: }
254: }
255:
256: // See if we can find the file
257: // ---------------------------
258:
259: // Try pathname from absolute filename
260: try {
261: if (new File(pathname2 + filename).exists()) {
262: return (pathname2 + filename);
263: }
264: } catch (NullPointerException ex) {
265: ex.printStackTrace();
266: }
267: // Try lowercase filename
268: if (new File(pathname2 + filename.toLowerCase()).exists()) {
269: return (pathname2 + filename.toLowerCase());
270: }
271:
272: // Try original pathname
273: if (new File(pathname + filename).exists()) {
274: return (pathname + filename);
275: }
276: // Try lowercase filename
277: if (new File(pathname + filename.toLowerCase()).exists()) {
278: return (pathname + filename.toLowerCase());
279: }
280:
281: // Finally, let's check the local directory
282: if (new File(filename).exists()) {
283: return (filename);
284: }
285: // Try lowercase filename
286: if (new File(filename.toLowerCase()).exists()) {
287: return (filename.toLowerCase());
288: }
289:
290: // Conditions that determine when we give up on the recursive search
291: if ((pathname.equals(File.separator)) || (pathname == null)
292: || (pathname.equals(""))) {
293:
294: throw new java.io.FileNotFoundException(filename);
295: }
296:
297: // Try to find the file in the upper directories
298: // Chop off the last directory from pathname and recurse
299: StringBuffer newPathName = new StringBuffer(128);
300: StringTokenizer st = new StringTokenizer(pathname,
301: File.separator);
302: int tokenCount = st.countTokens() - 1;
303: if (pathname.startsWith(java.io.File.separator))
304: newPathName.append(File.separator);
305: for (int i = 0; i < tokenCount; ++i) {
306: String directory = st.nextToken();
307: newPathName.append(directory);
308: newPathName.append(File.separator);
309: }
310:
311: String newPath = newPathName.toString();
312: return getQualifiedFilename(newPath, filename);
313: }
314:
315: URL getQualifiedURL(String path, String file)
316: throws MalformedURLException {
317:
318: URL url = null;
319:
320: // try the path and the file -- this is the lightwave spec
321: try {
322: // create url
323: url = new URL(path + file);
324: // see if file exists
325: url.getContent();
326: // return url if no exception is thrown
327: return url;
328: } catch (IOException e) {
329: // Ignore - try something different
330: }
331:
332: // try a couple other options, but trying to open connections is slow,
333: // so don't try as many options as getQualifiedFilename
334:
335: // try absolute path
336: try {
337: url = new URL(file);
338: url.getContent();
339: } catch (IOException ex) {
340: // Ignore - try something different
341: }
342:
343: // try the absolute path with the protocol
344: try {
345: url = new URL(protocol + ":" + file);
346: url.getContent();
347: return url;
348: } catch (IOException ex) {
349: // Nothing else to try so give up
350: throw new MalformedURLException(path + file);
351: }
352: }
353:
354: /**
355: * Returns parent object
356: */
357: int getParent() {
358: return parent;
359: }
360:
361: /**
362: * Adds the given child to the transform of this node (its parent).
363: */
364: void addChild(LwsPrimitive child) {
365: debugOutputLn(TRACE, "addChild()");
366: if (objectTransform != null) {
367: debugOutputLn(LINE_TRACE, "objectTransform = "
368: + objectTransform);
369: if (child.getObjectNode() != null) {
370: debugOutputLn(LINE_TRACE, "child has object node");
371: if (hasPivot)
372: pivotTransGroup.addChild(child.getObjectNode());
373: else
374: objectTransform.addChild(child.getObjectNode());
375: }
376: /*
377: if (child.getObjectBehaviors() != null) {
378: debugOutputLn(LINE_TRACE, "child has behaviors");
379: Group bg = child.getObjectBehaviors();
380: debugOutputLn(VALUES, " child behaviors = " + bg);
381: // TODO: should remove intermediate group nodes
382: objectBehavior.addChild(bg);
383: }
384: */
385: }
386: }
387:
388: /**
389: * Creates Java3d objects from the data stored for this object.
390: * The objects created consist of: A TransformGroup that holds the
391: * transform specified by the first keyframe, a Behavior that acts
392: * on the TransformGroup if there are more than 1 keyframes, and
393: * some geometry (created by J3dLwoParser) from an external geometry
394: * file (if the object wasn't an lw3d Null object (group)).
395: */
396: void createJava3dObject(LwsObject cloneObject, int loadBehaviors)
397: throws IncorrectFormatException, ParsingErrorException,
398: FileNotFoundException {
399: String seqToken = new String("_sequence_");
400: Matrix4d mat = new Matrix4d();
401: mat.setIdentity();
402: // Set the node's transform matrix according to the first frame
403: // of the object's motion
404: LwsFrame firstFrame = motion.getFirstFrame();
405: firstFrame.setMatrix(mat);
406: Transform3D t1 = new Transform3D();
407: t1.set(mat);
408: objectTransform = new TransformGroup(t1);
409: objectTransform
410: .setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
411:
412: // This following bit is a hack and should probably be removed.
413: // It was put in here in order to handle the "Tloop" functionality
414: // of holosketch, which was needed for the 1998 Javaone conference
415: // (the HighNoon demo, in particular). Having the code here, or
416: // using it, means that some object file names are tagged as special
417: // because they contain the phrase "_sequence_", which tells this
418: // object file reader that that object file is, in fact, a
419: // sequence file. It then creates a SequenceReader object to
420: // read that file and create an animation from the objects defined
421: // in that file.
422: // See SequenceReader.java for more information on this.
423: // By the way, a better/fuller implementation of this functionality
424: // would involve investigating a standard Plug-In for Lightwave
425: // that allows the writing out of sequence files from Bones data.
426: // i think it would be better to base any Tloop stuff on that
427: // standard than on some proprietary hack of our own.
428:
429: if (fileName != null && fileName.indexOf(seqToken) != -1) { // Tloop
430:
431: int index = fileName.indexOf(seqToken);
432: index += seqToken.length();
433: String seqFilename = fileName.substring(index);
434: int endIndex = seqFilename.indexOf(".lwo");
435: if (endIndex != -1)
436: seqFilename = seqFilename.substring(0, endIndex);
437: if ((new File(seqFilename)).exists()) {
438: SequenceReader sr = new SequenceReader(seqFilename,
439: motion.totalTime, (int) motion.totalFrames);
440: sr.printLines();
441: sr.createJava3dObjects(debugPrinter.getValidOutput(),
442: loadBehaviors);
443: Group g = sr.getObjectNode();
444: if (g != null)
445: objectTransform.addChild(g);
446:
447: // Sequence reader's getObjectBehaviors creates new Vector
448: objectBehavior = sr.getObjectBehaviors();
449:
450: return;
451: }
452: }
453:
454: // Okay, now that that hack is out of the way, let's get on with
455: // "normal" Lightwave object files.
456: if (fileName != null || urlName != null) {
457: // If this object refers to an obj file, load it and create
458: // geometry from it.
459: if (cloneObject == null) {
460: debugOutputLn(VALUES, "About to load binary file for "
461: + fileName);
462: // Create a J3dLwoParser object to parse the geometry file
463: // and create the appropriate geometry
464: J3dLwoParser objParser = null;
465: switch (fileType) {
466: case Lw3dLoader.FILE_TYPE_FILENAME:
467: objParser = new J3dLwoParser(fileName, debugPrinter
468: .getValidOutput());
469: break;
470: case Lw3dLoader.FILE_TYPE_URL:
471: objParser = new J3dLwoParser(urlName, debugPrinter
472: .getValidOutput());
473: break;
474: }
475: objParser.createJava3dGeometry();
476: // pivot points change the parent transform
477: if (hasPivot) {
478: objectTransform.addChild(pivotTransGroup);
479: }
480: if (objParser.getJava3dShapeList() != null) {
481: shapeList = objParser.getJava3dShapeList();
482: for (Enumeration e = shapeList.elements(); e
483: .hasMoreElements();) {
484: if (!hasPivot || pivotTransGroup == null)
485: objectTransform.addChild((Shape3D) e
486: .nextElement());
487: else
488: pivotTransGroup.addChild((Shape3D) e
489: .nextElement());
490: }
491: }
492: } else {
493: // Already read that file: Clone original object
494: debugOutputLn(LINE_TRACE, "Cloning shapes");
495: Vector cloneShapeList = cloneObject.getShapeList();
496: for (Enumeration e = cloneShapeList.elements(); e
497: .hasMoreElements();) {
498: debugOutputLn(LINE_TRACE, " shape clone");
499: Shape3D shape = (Shape3D) e.nextElement();
500: Shape3D cloneShape = (Shape3D) shape.cloneTree();
501: objectTransform.addChild(cloneShape);
502: }
503: }
504: }
505:
506: // Create j3d behaviors for the object's animation
507: objectBehavior = new Vector();
508: if (loadBehaviors != 0) {
509: motion.createJava3dBehaviors(objectTransform);
510: Behavior b = motion.getBehaviors();
511: if (b != null)
512: objectBehavior.addElement(b);
513: }
514: }
515:
516: /**
517: * Return list of Shape3D objects for this object file. This is used
518: * when cloning objects (if the scene file requests the same object file
519: * more than once, that object will be cloned instead of recreated each
520: * time).
521: */
522: Vector getShapeList() {
523: return shapeList;
524: }
525:
526: /**
527: * Return the TransformGroup that holds this object file
528: */
529: public TransformGroup getObjectNode() {
530: return objectTransform;
531: }
532:
533: /**
534: * Return the Group that holds this object's behaviors. The behaviors
535: * are grouped separately from the geometry so that they can be handled
536: * differently by the parent application.
537: */
538: public Vector getObjectBehaviors() {
539: debugOutputLn(TRACE, "getObjectBehaviors()");
540: return objectBehavior;
541: }
542:
543: /**
544: * Utiliy function to print some of the object values. Used in
545: * debugging.
546: */
547: void printVals() {
548: debugOutputLn(VALUES, " OBJECT vals: ");
549: debugOutputLn(VALUES, " fileName = " + fileName);
550: debugOutputLn(VALUES, " objName = " + objName);
551: motion.printVals();
552: }
553: }
|