001: /*
002: * $RCSfile: Lw3dLoader.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:07 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.loaders.lw3d;
046:
047: import com.sun.j3d.loaders.*;
048: import java.awt.Component;
049: import java.io.*;
050: import java.util.Vector;
051: import java.util.Enumeration;
052: import java.net.URL;
053: import java.net.MalformedURLException;
054:
055: import javax.media.j3d.*;
056: import javax.vecmath.Color3f;
057: import javax.vecmath.Point3d;
058:
059: /**
060: * This class implements the Loader API and allows users to load
061: * Lightwave 3D scene files. In order to load properly, the object
062: * files referred to in the scene files and the image files referred
063: * to by the object files must all be specified with path and filenames
064: * that are valid with respect to the directory in which the application
065: * is being executed.
066: */
067:
068: public class Lw3dLoader extends TextfileParser implements Loader {
069:
070: Vector objectList;
071: Vector lightList;
072: BranchGroup sceneGroupNode;
073: Color3f ambientColor;
074: LwsCamera camera = null;
075: LwsFog fog = null;
076: LwsBackground background = null;
077: int loadFlags = 0;
078: int loadBehaviors = 0;
079: Vector sceneBehaviors;
080: SceneBase scene = null;
081: String basePath = null;
082: String internalBasePath = null;
083: URL baseUrl = null;
084: String internalBaseUrl = null; // store url base as String
085: static final int FILE_TYPE_NONE = 0;
086: static final int FILE_TYPE_URL = 1;
087: static final int FILE_TYPE_FILENAME = 2;
088: static final int FILE_TYPE_READER = 4;
089: int fileType = FILE_TYPE_NONE;
090:
091: /**
092: * Default constructor. Sets up default values for some variables.
093: */
094: public Lw3dLoader() {
095:
096: ambientColor = new Color3f(0f, 0f, 0f);
097: objectList = new Vector();
098: lightList = new Vector();
099: debugPrinter.setValidOutput(0x0);
100:
101: }
102:
103: /**
104: * This constructor takes a flags word that specifies which types of
105: * scenefile items should be loaded into the scene. The possible
106: * values are specified in the com.sun.j3d.loaders.Loader class.
107: */
108: public Lw3dLoader(int flags) {
109:
110: this ();
111: loadFlags = flags;
112: loadBehaviors = (loadFlags & Loader.LOAD_BEHAVIOR_NODES);
113:
114: }
115:
116: /**
117: * This method loads the named file and returns the Scene
118: * containing the scene. Any data files referenced by the Reader
119: * should be located in the same place as the named file; otherwise,
120: * users should specify an alternate base path with the setBaseUrl(URL)
121: * method.
122: */
123: public Scene load(URL url) throws FileNotFoundException,
124: IncorrectFormatException, ParsingErrorException {
125:
126: fileType = FILE_TYPE_URL;
127: setInternalBaseUrl(url);
128: InputStreamReader reader;
129: try {
130: reader = new InputStreamReader(new BufferedInputStream(url
131: .openStream()));
132: } catch (IOException e) {
133: throw new FileNotFoundException(e.getMessage());
134: }
135: Scene returnScene = load(reader);
136: fileType = FILE_TYPE_NONE;
137: return returnScene;
138: }
139:
140: /**
141: * This method loads the named file and returns the Scene
142: * containing the scene. Any data files referenced by this
143: * file should be located in the same place as the named file;
144: * otherwise users should specify an alternate base path with
145: * the setBasePath(String) method.
146: */
147: public Scene load(String fileName) throws FileNotFoundException,
148: IncorrectFormatException, ParsingErrorException {
149:
150: fileType = FILE_TYPE_FILENAME;
151: setInternalBasePath(fileName);
152: Reader reader = new BufferedReader(new FileReader(fileName));
153: Scene returnScene = load(reader);
154: fileType = FILE_TYPE_NONE;
155: return returnScene;
156: }
157:
158: /**
159: * This method loads the Reader and returns the Scene
160: * containing the scene. Any data files referenced by the Reader should
161: * be located in the user's current working directory.
162: */
163: public Scene load(Reader reader) throws FileNotFoundException,
164: IncorrectFormatException, ParsingErrorException {
165:
166: if (fileType == FILE_TYPE_NONE)
167: fileType = FILE_TYPE_READER;
168: StreamTokenizer tokenizer = new StreamTokenizer(reader);
169: setupTokenizer(tokenizer);
170:
171: getAndCheckString(tokenizer, "LWSC");
172: getNumber(tokenizer);
173: getAndCheckString(tokenizer, "FirstFrame");
174: int firstFrame = (int) getNumber(tokenizer);
175: getAndCheckString(tokenizer, "LastFrame");
176: int finalFrame = (int) getNumber(tokenizer);
177: skipUntilString(tokenizer, "FramesPerSecond");
178: double fps = getNumber(tokenizer);
179: float totalTime = (float) (finalFrame - firstFrame)
180: / (float) fps;
181: boolean done = false;
182: while (!done) {
183: int token;
184: try {
185: token = tokenizer.nextToken();
186: } catch (IOException e) {
187: throw new ParsingErrorException(e.getMessage());
188: }
189: switch (tokenizer.ttype) {
190: case StreamTokenizer.TT_EOF:
191: done = true;
192: break;
193: case StreamTokenizer.TT_WORD:
194: debugOutputLn(VALUES, " String = " + tokenizer.sval);
195: if (tokenizer.sval.equals("AddNullObject")) {
196: LwsObject obj = new LwsObject(tokenizer, false,
197: firstFrame, finalFrame, totalTime, this ,
198: debugPrinter.getValidOutput());
199: obj.createJava3dObject(null, loadBehaviors);
200: objectList.addElement(obj);
201: } else if (tokenizer.sval.equals("LoadObject")) {
202: String filename = getString(tokenizer);
203: tokenizer.pushBack(); // push filename token back
204: debugOutputLn(TIME, "loading " + filename + " at "
205: + System.currentTimeMillis());
206: LwsObject obj = new LwsObject(tokenizer, true,
207: firstFrame, finalFrame, totalTime, this ,
208: debugPrinter.getValidOutput());
209: debugOutputLn(TIME, "done loading at "
210: + System.currentTimeMillis());
211: LwsObject cloneObject = null;
212: for (Enumeration e = objectList.elements(); e
213: .hasMoreElements();) {
214: LwsObject tmpObj = (LwsObject) e.nextElement();
215: if (tmpObj.fileName != null
216: && tmpObj.fileName.equals(filename)) {
217: cloneObject = tmpObj;
218: break;
219: }
220: }
221: obj.createJava3dObject(cloneObject, loadBehaviors);
222: objectList.addElement(obj);
223: } else if (tokenizer.sval.equals("AmbientColor")) {
224: ambientColor.x = (float) getNumber(tokenizer) / 255f;
225: ambientColor.y = (float) getNumber(tokenizer) / 255f;
226: ambientColor.z = (float) getNumber(tokenizer) / 255f;
227: } else if (tokenizer.sval.equals("AmbIntensity")) {
228: // TODO: must be able to handle envelopes here
229: float intensity = (float) getNumber(tokenizer);
230: ambientColor.x *= intensity;
231: ambientColor.y *= intensity;
232: ambientColor.z *= intensity;
233: } else if (tokenizer.sval.equals("AddLight")) {
234: LwsLight light = new LwsLight(tokenizer,
235: finalFrame, totalTime, debugPrinter
236: .getValidOutput());
237: light.createJava3dObject(loadBehaviors);
238: lightList.addElement(light);
239: } else if (tokenizer.sval.equals("ShowCamera")) {
240: camera = new LwsCamera(tokenizer, firstFrame,
241: finalFrame, totalTime, debugPrinter
242: .getValidOutput());
243: camera.createJava3dObject(loadBehaviors);
244: } else if (tokenizer.sval.equals("FogType")) {
245: int fogType = (int) getNumber(tokenizer);
246: if (fogType != 0) {
247: fog = new LwsFog(tokenizer, debugPrinter
248: .getValidOutput());
249: fog.createJava3dObject();
250: }
251: } else if (tokenizer.sval.equals("SolidBackdrop")) {
252: background = new LwsBackground(tokenizer,
253: debugPrinter.getValidOutput());
254: background.createJava3dObject();
255: }
256: break;
257: default:
258: debugOutputLn(VALUES, " Unknown ttype, token = "
259: + tokenizer.ttype + ", " + token);
260: break;
261: }
262: }
263:
264: // Set up scene groups and parent objects appropriately
265: sceneGroupNode = new BranchGroup();
266: sceneBehaviors = new Vector();
267: parentObjects();
268: constructScene();
269:
270: return scene;
271:
272: }
273:
274: /**
275: * This method creates the Scene (actually SceneBase) data structure
276: * and adds all appropriate items to it. This is the data structure
277: * that the user will get back from the load() call and inquire to
278: * get data from the scene.
279: */
280: void constructScene() {
281:
282: // Construct Scene data structure
283: scene = new SceneBase();
284:
285: if ((loadFlags & Loader.LOAD_LIGHT_NODES) != 0) {
286: addLights();
287: addAmbient();
288: }
289:
290: if ((loadFlags & Loader.LOAD_FOG_NODES) != 0)
291: addFog();
292:
293: if ((loadFlags & Loader.LOAD_BACKGROUND_NODES) != 0)
294: addBackground();
295:
296: if ((loadFlags & Loader.LOAD_VIEW_GROUPS) != 0)
297: addCamera();
298:
299: if (loadBehaviors != 0)
300: addBehaviors();
301:
302: scene.setSceneGroup(sceneGroupNode);
303:
304: // now add named objects to the scenes name table
305: for (Enumeration e = objectList.elements(); e.hasMoreElements();) {
306:
307: LwsObject obj = (LwsObject) e.nextElement();
308: if (obj.fileName != null)
309: scene.addNamedObject(obj.fileName, (Object) obj
310: .getObjectNode());
311: else if (obj.objName != null)
312: scene.addNamedObject(obj.objName, (Object) obj
313: .getObjectNode());
314:
315: }
316: }
317:
318: /**
319: * Creates a url for internal use. This method is not currently
320: * used (url's are ignored for this loader; only filenames work)
321: */
322: void setInternalBaseUrl(URL url) {
323: // System.out.println("setInternalBaseUrl url = " + url);
324: java.util.StringTokenizer stok = new java.util.StringTokenizer(
325: url.toString(),
326: // java.io.File.separator);
327: "\\/");
328: int tocount = stok.countTokens() - 1;
329: StringBuffer sb = new StringBuffer(80);
330: for (int ji = 0; ji < tocount; ji++) {
331: String a = stok.nextToken();
332: if ((ji == 0) && //(!a.equals("file:"))) {
333: (!a.regionMatches(true, 0, "file:", 0, 5))) {
334: sb.append(a);
335: // urls use / on windows also
336: // sb.append(java.io.File.separator);
337: // sb.append(java.io.File.separator);
338: sb.append('/');
339: sb.append('/');
340: } else {
341: sb.append(a);
342: // urls use / on windows also
343: // sb.append( java.io.File.separator );
344: sb.append('/');
345: }
346: }
347: internalBaseUrl = sb.toString();
348: // System.out.println("internalBaseUrl = " + internalBaseUrl);
349: }
350:
351: /**
352: * Standardizes the filename for use in the loader
353: */
354: void setInternalBasePath(String fileName) {
355: java.util.StringTokenizer stok = new java.util.StringTokenizer(
356: fileName, java.io.File.separator);
357: int tocount = stok.countTokens() - 1;
358: StringBuffer sb = new StringBuffer(80);
359: if (fileName != null
360: && fileName.startsWith(java.io.File.separator))
361: sb.append(java.io.File.separator);
362: for (int ji = 0; ji < tocount; ji++) {
363: String a = stok.nextToken();
364: sb.append(a);
365: sb.append(java.io.File.separator);
366: }
367: internalBasePath = sb.toString();
368: }
369:
370: String getInternalBasePath() {
371: return internalBasePath;
372: }
373:
374: String getInternalBaseUrl() {
375: return internalBaseUrl;
376: }
377:
378: int getFileType() {
379: return fileType;
380: }
381:
382: /**
383: * This method parents all objects in the scene appropriately. If
384: * the scen file specifies a Parent node for the object, then the
385: * object is parented to that node. If not, then the object is
386: * parented to the scene's root.
387: */
388: void parentObjects() {
389: debugOutputLn(TRACE, "parentObjects()");
390: for (Enumeration e = objectList.elements(); e.hasMoreElements();) {
391:
392: LwsObject obj = (LwsObject) e.nextElement();
393: if (obj.getParent() != -1) {
394:
395: LwsObject parent = (LwsObject) objectList.elementAt(obj
396: .getParent() - 1);
397: parent.addChild(obj);
398: debugOutputLn(VALUES, "added child successfully");
399:
400: } else {
401:
402: if (obj.getObjectNode() != null)
403: sceneGroupNode.addChild(obj.getObjectNode());
404:
405: }
406:
407: // Collect all behaviors
408: if (loadBehaviors != 0) {
409: if (!(obj.getObjectBehaviors()).isEmpty()) {
410: sceneBehaviors.addAll(obj.getObjectBehaviors());
411:
412: }
413: }
414: }
415:
416: debugOutputLn(LINE_TRACE, "Done with parentObjects()");
417:
418: }
419:
420: /**
421: * This method sets the base URL name for data files
422: * associated with the file passed into the load(URL) method.
423: * The basePath should be null by default, which is an
424: * indicator to the loader that it should look for any
425: * associated files starting from the same directory as the
426: * file passed into the load(URL) method.
427: */
428: public void setBaseUrl(URL url) {
429: baseUrl = url;
430: }
431:
432: /**
433: * This method sets the base path to be used when searching for all
434: * data files within a Lightwave scene.
435: */
436: public void setBasePath(String pathName) {
437: // This routine standardizes path names so that all pathnames
438: // will have standard file separators, they'll end in a separator
439: // character, and if the user passes in null or "" (meaning to
440: // set the current directory as the base path), this will become
441: // "./" (or ".\")
442: basePath = pathName;
443: if (basePath == null || basePath == "")
444: basePath = "." + java.io.File.separator;
445: basePath = basePath.replace('/', java.io.File.separatorChar);
446: basePath = basePath.replace('\\', java.io.File.separatorChar);
447: if (!basePath.endsWith(java.io.File.separator))
448: basePath = basePath + java.io.File.separator;
449: }
450:
451: /**
452: * Returns the current base URL setting.
453: */
454: public URL getBaseUrl() {
455: return baseUrl;
456: }
457:
458: /**
459: * Returns the current base path setting.
460: */
461: public String getBasePath() {
462: return basePath;
463: }
464:
465: /**
466: * This method sets the load flags for the file. The flags should
467: * equal 0 by default (which tells the loader to only load geometry).
468: */
469: public void setFlags(int flags) {
470: loadFlags = flags;
471: }
472:
473: /**
474: * Returns the current loading flags setting.
475: */
476: public int getFlags() {
477: return loadFlags;
478: }
479:
480: /**
481: * getObject() iterates through the objectList checking the given
482: * name against the fileName and objectName of each object in turn.
483: * For the filename, it carves off the pathname and just checks the
484: * final name (e.g., "missile.lwo").
485: * If name has []'s at the end, it will use the number inside those
486: * brackets to pick which object out of an ordered set it will
487: * send back (objectList is created in the order that objects
488: * exist in the file, so this order should correspond to the order
489: * specified by the user). If no []'s exist, just pass back the
490: * first one encountered that matches.
491: */
492: public TransformGroup getObject(String name) {
493: debugOutputLn(TRACE, "getObject()");
494: int indexNumber = -1;
495: int currentObjectCount = 0;
496: String subobjectName = name;
497: if (name.indexOf("[") != -1) {
498: // caller wants specifically numbered subjbect; get that number
499: int bracketsIndex = name.indexOf("[");
500: subobjectName = name.substring(0, bracketsIndex);
501: String bracketsString = name.substring(bracketsIndex);
502: int bracketEndIndex = bracketsString.indexOf("]");
503: String indexString = bracketsString.substring(1,
504: bracketEndIndex);
505: indexNumber = (new Integer(indexString)).intValue();
506: }
507: for (Enumeration e = objectList.elements(); e.hasMoreElements();) {
508: LwsObject tempObj = (LwsObject) e.nextElement();
509: debugOutputLn(VALUES, "tempObj, file, objname = " + tempObj
510: + tempObj.fileName + tempObj.objName);
511: if ((tempObj.fileName != null && tempObj.fileName
512: .indexOf(subobjectName) != -1)
513: || (tempObj.objName != null && tempObj.objName
514: .indexOf(subobjectName) != -1)) {
515: if (indexNumber < 0
516: || indexNumber == currentObjectCount)
517: return tempObj.getObjectNode();
518: else
519: currentObjectCount++;
520: }
521: }
522: debugOutputLn(VALUES, " no luck - wanted " + name
523: + " returning null");
524: return null;
525: }
526:
527: /**
528: * This method sets up the StreamTokenizer for the scene file. Note
529: * that we're not parsing numbers as numbers because the tokenizer
530: * does not interpret scientific notation correctly.
531: */
532: void setupTokenizer(StreamTokenizer tokenizer) {
533: tokenizer.resetSyntax();
534: tokenizer.wordChars('a', 'z');
535: tokenizer.wordChars('A', 'Z');
536: tokenizer.wordChars(128 + 32, 255);
537: tokenizer.whitespaceChars(0, ' ');
538: tokenizer.commentChar('/');
539: tokenizer.quoteChar('"');
540: tokenizer.quoteChar('\'');
541: tokenizer.wordChars('0', '9');
542: tokenizer.wordChars('.', '.');
543: tokenizer.wordChars('-', '-');
544: tokenizer.wordChars('/', '/');
545: tokenizer.wordChars('\\', '\\');
546: tokenizer.wordChars('_', '_');
547: tokenizer.wordChars('&', '&');
548: tokenizer.ordinaryChar('(');
549: tokenizer.ordinaryChar(')');
550: tokenizer.whitespaceChars('\r', '\r');
551:
552: // add ':' as wordchar so urls will work
553: tokenizer.wordChars(':', ':');
554: // add '~' as wordchar for urls
555: tokenizer.wordChars('~', '~');
556: }
557:
558: /**
559: * Adds Ambient lighting effects to the scene
560: */
561: void addAmbient() {
562: AmbientLight aLgt = new AmbientLight(ambientColor);
563: BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,
564: 0.0, 0.0), 100000.0);
565: aLgt.setInfluencingBounds(bounds);
566: sceneGroupNode.addChild(aLgt);
567: // scope ambient light to the lw3d scene
568: aLgt.addScope(sceneGroupNode);
569: scene.addLightNode(aLgt);
570: }
571:
572: /**
573: * Add any defined lights to the java3d scene
574: */
575: void addLights() {
576: // Add lights to the scene
577: for (Enumeration e1 = lightList.elements(); e1
578: .hasMoreElements();) {
579:
580: debugOutputLn(LINE_TRACE, "adding light to scene group");
581: LwsLight light = (LwsLight) e1.nextElement();
582:
583: if (light.getObjectNode() != null) {
584: // scope light to the lw3d scene
585: light.getLight().addScope(sceneGroupNode);
586:
587: if (light.getParent() != -1) {
588: LwsObject parent = (LwsObject) objectList
589: .elementAt(light.getParent() - 1);
590: parent.addChild(light);
591: } else { // No parent - add to scene group
592: sceneGroupNode.addChild(light.getObjectNode());
593:
594: }
595:
596: // collect behaviors if LOAD_BEHAVIOR_NODES is set
597: if (loadBehaviors != 0) {
598: if (!(light.getObjectBehaviors()).isEmpty())
599: sceneBehaviors.addAll(light
600: .getObjectBehaviors());
601:
602: }
603:
604: scene.addLightNode(light.getLight());
605: } else
606: debugOutputLn(LINE_TRACE, "light object null?");
607: }
608: }
609:
610: /**
611: * Adds the Camera's transform group to the scene, either by parenting
612: * it to the appropriate object or by adding it to the scene root.
613: * To use this camera data, users can request the camera/view data
614: * for the scene and can then insert a ViewPlatform in the transform group.
615: */
616: void addCamera() {
617: // Add camera effects to scene.
618: if (camera != null) {
619: if (camera.getParent() != -1) {
620: debugOutputLn(VALUES, "camera parent = "
621: + camera.getParent());
622: LwsObject parent = (LwsObject) objectList
623: .elementAt(camera.getParent() - 1);
624: parent.addChild(camera);
625: debugOutputLn(VALUES, "added child successfully");
626: } else {
627: sceneGroupNode.addChild(camera.getObjectNode());
628:
629: }
630:
631: // collect behaviors if LOAD_BEHAVIOR_NODES is set
632: if (loadBehaviors != 0) {
633: if (!(camera.getObjectBehaviors()).isEmpty())
634: sceneBehaviors.addAll(camera.getObjectBehaviors());
635: }
636:
637: scene.addViewGroup(camera.getObjectNode());
638: }
639: }
640:
641: /**
642: * Add appropriate fog effects to the scene
643: */
644: void addFog() {
645: if (fog != null) {
646: Fog fogNode = fog.getObjectNode();
647: if (fogNode != null) {
648: sceneGroupNode.addChild(fogNode);
649: scene.addFogNode(fogNode);
650: }
651: }
652: }
653:
654: /**
655: * Add the behaviors to the scene
656: */
657: void addBehaviors() {
658: if (!sceneBehaviors.isEmpty()) {
659: Enumeration e = sceneBehaviors.elements();
660: while (e.hasMoreElements()) {
661: scene.addBehaviorNode((Behavior) e.nextElement());
662: }
663: }
664: }
665:
666: /**
667: * Add appropriate background effects to the scene. Note that the java3d
668: * background may not have all of the information of the lw3d background,
669: * as the loader does not currently process items such as gradients between
670: * the horizon and sky colors
671: */
672: void addBackground() {
673: if (background != null) {
674: Background bgNode = background.getObjectNode();
675: if (bgNode != null) {
676: sceneGroupNode.addChild(bgNode);
677: scene.addBackgroundNode(bgNode);
678: }
679: }
680: }
681:
682: }
|