• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2009-2010 jMonkeyEngine
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * * Redistributions of source code must retain the above copyright
10  *   notice, this list of conditions and the following disclaimer.
11  *
12  * * Redistributions in binary form must reproduce the above copyright
13  *   notice, this list of conditions and the following disclaimer in the
14  *   documentation and/or other materials provided with the distribution.
15  *
16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17  *   may be used to endorse or promote products derived from this software
18  *   without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 package com.jme3.scene.plugins.ogre;
34 
35 import com.jme3.asset.*;
36 import com.jme3.light.DirectionalLight;
37 import com.jme3.light.Light;
38 import com.jme3.light.PointLight;
39 import com.jme3.light.SpotLight;
40 import com.jme3.material.MaterialList;
41 import com.jme3.math.Quaternion;
42 import com.jme3.math.Vector3f;
43 import com.jme3.scene.Spatial;
44 import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey;
45 import com.jme3.util.PlaceholderAssets;
46 import com.jme3.util.xml.SAXUtil;
47 import static com.jme3.util.xml.SAXUtil.*;
48 import java.io.IOException;
49 import java.io.InputStreamReader;
50 import java.util.Stack;
51 import java.util.logging.Level;
52 import java.util.logging.Logger;
53 import javax.xml.parsers.ParserConfigurationException;
54 import javax.xml.parsers.SAXParserFactory;
55 import org.xml.sax.Attributes;
56 import org.xml.sax.InputSource;
57 import org.xml.sax.SAXException;
58 import org.xml.sax.XMLReader;
59 import org.xml.sax.helpers.DefaultHandler;
60 
61 public class SceneLoader extends DefaultHandler implements AssetLoader {
62 
63     private static final Logger logger = Logger.getLogger(SceneLoader.class.getName());
64 
65     private Stack<String> elementStack = new Stack<String>();
66     private AssetKey key;
67     private String sceneName;
68     private String folderName;
69     private AssetManager assetManager;
70     private MaterialList materialList;
71     private com.jme3.scene.Node root;
72     private com.jme3.scene.Node node;
73     private com.jme3.scene.Node entityNode;
74     private Light light;
75     private int nodeIdx = 0;
76     private static volatile int sceneIdx = 0;
77 
SceneLoader()78     public SceneLoader(){
79         super();
80     }
81 
82     @Override
startDocument()83     public void startDocument() {
84     }
85 
86     @Override
endDocument()87     public void endDocument() {
88     }
89 
reset()90     private void reset(){
91         elementStack.clear();
92         nodeIdx = 0;
93 
94         // NOTE: Setting some of those to null is only needed
95         // if the parsed file had an error e.g. startElement was called
96         // but not endElement
97         root = null;
98         node = null;
99         entityNode = null;
100         light = null;
101     }
102 
checkTopNode(String topNode)103     private void checkTopNode(String topNode) throws SAXException{
104         if (!elementStack.peek().equals(topNode)){
105             throw new SAXException("dotScene parse error: Expected parent node to be " + topNode);
106         }
107     }
108 
parseQuat(Attributes attribs)109     private Quaternion parseQuat(Attributes attribs) throws SAXException{
110         if (attribs.getValue("x") != null){
111             // defined as quaternion
112             float x = parseFloat(attribs.getValue("x"));
113             float y = parseFloat(attribs.getValue("y"));
114             float z = parseFloat(attribs.getValue("z"));
115             float w = parseFloat(attribs.getValue("w"));
116             return new Quaternion(x,y,z,w);
117         }else if (attribs.getValue("qx") != null){
118             // defined as quaternion with prefix "q"
119             float x = parseFloat(attribs.getValue("qx"));
120             float y = parseFloat(attribs.getValue("qy"));
121             float z = parseFloat(attribs.getValue("qz"));
122             float w = parseFloat(attribs.getValue("qw"));
123             return new Quaternion(x,y,z,w);
124         }else if (attribs.getValue("angle") != null){
125             // defined as angle + axis
126             float angle = parseFloat(attribs.getValue("angle"));
127             float axisX = parseFloat(attribs.getValue("axisX"));
128             float axisY = parseFloat(attribs.getValue("axisY"));
129             float axisZ = parseFloat(attribs.getValue("axisZ"));
130             Quaternion q = new Quaternion();
131             q.fromAngleAxis(angle, new Vector3f(axisX, axisY, axisZ));
132             return q;
133         }else{
134             // defines as 3 angles along XYZ axes
135             float angleX = parseFloat(attribs.getValue("angleX"));
136             float angleY = parseFloat(attribs.getValue("angleY"));
137             float angleZ = parseFloat(attribs.getValue("angleZ"));
138             Quaternion q = new Quaternion();
139             q.fromAngles(angleX, angleY, angleZ);
140             return q;
141         }
142     }
143 
parseLightNormal(Attributes attribs)144     private void parseLightNormal(Attributes attribs) throws SAXException {
145         checkTopNode("light");
146 
147         // SpotLight will be supporting a direction-normal, too.
148         if (light instanceof DirectionalLight)
149             ((DirectionalLight) light).setDirection(parseVector3(attribs));
150         else if (light instanceof SpotLight){
151             ((SpotLight) light).setDirection(parseVector3(attribs));
152         }
153     }
154 
parseLightAttenuation(Attributes attribs)155     private void parseLightAttenuation(Attributes attribs) throws SAXException {
156         // NOTE: Derives range based on "linear" if it is used solely
157         // for the attenuation. Otherwise derives it from "range"
158         checkTopNode("light");
159 
160         if (light instanceof PointLight || light instanceof SpotLight){
161             float range = parseFloat(attribs.getValue("range"));
162             float constant = parseFloat(attribs.getValue("constant"));
163             float linear = parseFloat(attribs.getValue("linear"));
164 
165             String quadraticStr = attribs.getValue("quadratic");
166             if (quadraticStr == null)
167                 quadraticStr = attribs.getValue("quadric");
168 
169             float quadratic = parseFloat(quadraticStr);
170 
171             if (constant == 1 && quadratic == 0 && linear > 0){
172                 range = 1f / linear;
173             }
174 
175             if (light instanceof PointLight){
176                 ((PointLight) light).setRadius(range);
177             }else{
178                 ((SpotLight)light).setSpotRange(range);
179             }
180         }
181     }
182 
parseLightSpotLightRange(Attributes attribs)183     private void parseLightSpotLightRange(Attributes attribs) throws SAXException{
184         checkTopNode("light");
185 
186         float outer = SAXUtil.parseFloat(attribs.getValue("outer"));
187         float inner = SAXUtil.parseFloat(attribs.getValue("inner"));
188 
189         if (!(light instanceof SpotLight)){
190             throw new SAXException("dotScene parse error: spotLightRange "
191                     + "can only appear under 'spot' light elements");
192         }
193 
194         SpotLight sl = (SpotLight) light;
195         sl.setSpotInnerAngle(inner * 0.5f);
196         sl.setSpotOuterAngle(outer * 0.5f);
197     }
198 
parseLight(Attributes attribs)199     private void parseLight(Attributes attribs) throws SAXException {
200         if (node == null || node.getParent() == null)
201             throw new SAXException("dotScene parse error: light can only appear under a node");
202 
203         checkTopNode("node");
204 
205         String lightType = parseString(attribs.getValue("type"), "point");
206         if(lightType.equals("point")) {
207             light = new PointLight();
208         } else if(lightType.equals("directional") || lightType.equals("sun")) {
209             light = new DirectionalLight();
210             // Assuming "normal" property is not provided
211             ((DirectionalLight)light).setDirection(Vector3f.UNIT_Z);
212         } else if(lightType.equals("spotLight") || lightType.equals("spot")) {
213             light = new SpotLight();
214         } else {
215             logger.log(Level.WARNING, "No matching jME3 LightType found for OGRE LightType: {0}", lightType);
216         }
217         logger.log(Level.FINEST, "{0} created.", light);
218 
219         if (!parseBool(attribs.getValue("visible"), true)){
220             // set to disabled
221         }
222 
223         // "attach" it to the parent of this node
224         if (light != null)
225             node.getParent().addLight(light);
226     }
227 
228     @Override
startElement(String uri, String localName, String qName, Attributes attribs)229     public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{
230         if (qName.equals("scene")){
231             if (elementStack.size() != 0){
232                 throw new SAXException("dotScene parse error: 'scene' element must be the root XML element");
233             }
234 
235             String version = attribs.getValue("formatVersion");
236             if (version == null || (!version.equals("1.0.0") && !version.equals("1.0.1")))
237                 logger.log(Level.WARNING, "Unrecognized version number"
238                         + " in dotScene file: {0}", version);
239 
240         }else if (qName.equals("nodes")){
241             if (root != null){
242                 throw new SAXException("dotScene parse error: nodes element was specified twice");
243             }
244             if (sceneName == null)
245                 root = new com.jme3.scene.Node("OgreDotScene"+(++sceneIdx));
246             else
247                 root = new com.jme3.scene.Node(sceneName+"-scene_node");
248 
249             node = root;
250         }else if (qName.equals("externals")){
251             checkTopNode("scene");
252             // Not loaded currently
253         }else if (qName.equals("item")){
254             checkTopNode("externals");
255         }else if (qName.equals("file")){
256             checkTopNode("item");
257 
258             // XXX: Currently material file name is based
259             // on the scene's filename. THIS IS NOT CORRECT.
260             // To solve, port SceneLoader to use DOM instead of SAX
261 
262             //String matFile = folderName+attribs.getValue("name");
263             //try {
264             //    materialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile));
265             //} catch (AssetNotFoundException ex){
266             //    materialList = null;
267             //    logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile);
268             //}
269         }else if (qName.equals("node")){
270             String curElement = elementStack.peek();
271             if (!curElement.equals("node") && !curElement.equals("nodes")){
272                 throw new SAXException("dotScene parse error: "
273                         + "node element can only appear under 'node' or 'nodes'");
274             }
275 
276             String name = attribs.getValue("name");
277             if (name == null)
278                 name = "OgreNode-" + (++nodeIdx);
279 
280             com.jme3.scene.Node newNode = new com.jme3.scene.Node(name);
281             if (node != null){
282                 node.attachChild(newNode);
283             }
284             node = newNode;
285         }else if (qName.equals("property")){
286             if (node != null){
287                 String type = attribs.getValue("type");
288                 String name = attribs.getValue("name");
289                 String data = attribs.getValue("data");
290                 if (type.equals("BOOL")){
291                     node.setUserData(name, Boolean.parseBoolean(data)||data.equals("1"));
292                 }else if (type.equals("FLOAT")){
293                     node.setUserData(name, Float.parseFloat(data));
294                 }else if (type.equals("STRING")){
295                     node.setUserData(name, data);
296                 }else if (type.equals("INT")){
297                     node.setUserData(name, Integer.parseInt(data));
298                 }
299             }
300         }else if (qName.equals("entity")){
301             checkTopNode("node");
302 
303             String name = attribs.getValue("name");
304             if (name == null)
305                 name = "OgreEntity-" + (++nodeIdx);
306             else
307                 name += "-entity";
308 
309             String meshFile = attribs.getValue("meshFile");
310             if (meshFile == null) {
311                 throw new SAXException("Required attribute 'meshFile' missing for 'entity' node");
312             }
313 
314             // TODO: Not currently used
315             String materialName = attribs.getValue("materialName");
316 
317             if (folderName != null) {
318                 meshFile = folderName + meshFile;
319             }
320 
321             // NOTE: append "xml" since its assumed mesh files are binary in dotScene
322             meshFile += ".xml";
323 
324             entityNode = new com.jme3.scene.Node(name);
325             OgreMeshKey meshKey = new OgreMeshKey(meshFile, materialList);
326             try {
327                 Spatial ogreMesh = assetManager.loadModel(meshKey);
328                 entityNode.attachChild(ogreMesh);
329             } catch (AssetNotFoundException ex){
330                 logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{meshKey, key});
331                 // Attach placeholder asset.
332                 entityNode.attachChild(PlaceholderAssets.getPlaceholderModel(assetManager));
333             }
334 
335             node.attachChild(entityNode);
336             node = null;
337         }else if (qName.equals("position")){
338             if (elementStack.peek().equals("node")){
339                 node.setLocalTranslation(SAXUtil.parseVector3(attribs));
340             }
341         }else if (qName.equals("quaternion") || qName.equals("rotation")){
342             node.setLocalRotation(parseQuat(attribs));
343         }else if (qName.equals("scale")){
344             node.setLocalScale(SAXUtil.parseVector3(attribs));
345         } else if (qName.equals("light")) {
346             parseLight(attribs);
347         } else if (qName.equals("colourDiffuse") || qName.equals("colorDiffuse")) {
348             if (elementStack.peek().equals("light")){
349                 if (light != null){
350                     light.setColor(parseColor(attribs));
351                 }
352             }else{
353                 checkTopNode("environment");
354             }
355         } else if (qName.equals("normal") || qName.equals("direction")) {
356             checkTopNode("light");
357             parseLightNormal(attribs);
358         } else if (qName.equals("lightAttenuation")) {
359             parseLightAttenuation(attribs);
360         } else if (qName.equals("spotLightRange") || qName.equals("lightRange")) {
361             parseLightSpotLightRange(attribs);
362         }
363 
364         elementStack.push(qName);
365     }
366 
367     @Override
endElement(String uri, String name, String qName)368     public void endElement(String uri, String name, String qName) throws SAXException {
369         if (qName.equals("node")){
370             node = node.getParent();
371         }else if (qName.equals("nodes")){
372             node = null;
373         }else if (qName.equals("entity")){
374             node = entityNode.getParent();
375             entityNode = null;
376         }else if (qName.equals("light")){
377             // apply the node's world transform on the light..
378             root.updateGeometricState();
379             if (light != null){
380                 if (light instanceof DirectionalLight){
381                     DirectionalLight dl = (DirectionalLight) light;
382                     Quaternion q = node.getWorldRotation();
383                     Vector3f dir = dl.getDirection();
384                     q.multLocal(dir);
385                     dl.setDirection(dir);
386                 }else if (light instanceof PointLight){
387                     PointLight pl = (PointLight) light;
388                     Vector3f pos = node.getWorldTranslation();
389                     pl.setPosition(pos);
390                 }else if (light instanceof SpotLight){
391                     SpotLight sl = (SpotLight) light;
392 
393                     Vector3f pos = node.getWorldTranslation();
394                     sl.setPosition(pos);
395 
396                     Quaternion q = node.getWorldRotation();
397                     Vector3f dir = sl.getDirection();
398                     q.multLocal(dir);
399                     sl.setDirection(dir);
400                 }
401             }
402             light = null;
403         }
404         checkTopNode(qName);
405         elementStack.pop();
406     }
407 
408     @Override
characters(char ch[], int start, int length)409     public void characters(char ch[], int start, int length) {
410     }
411 
412 
413 
load(AssetInfo info)414     public Object load(AssetInfo info) throws IOException {
415         try{
416             key = info.getKey();
417             assetManager = info.getManager();
418             sceneName = key.getName();
419             String ext = key.getExtension();
420             folderName = key.getFolder();
421             sceneName = sceneName.substring(0, sceneName.length() - ext.length() - 1);
422 
423             OgreMaterialKey materialKey = new OgreMaterialKey(sceneName+".material");
424             try {
425                 materialList = (MaterialList) assetManager.loadAsset(materialKey);
426             } catch (AssetNotFoundException ex){
427                 logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{materialKey, key});
428                 materialList = null;
429             }
430 
431             reset();
432 
433             // Added by larynx 25.06.2011
434             // Android needs the namespace aware flag set to true
435             // Kirill 30.06.2011
436             // Now, hack is applied for both desktop and android to avoid
437             // checking with JmeSystem.
438             SAXParserFactory factory = SAXParserFactory.newInstance();
439             factory.setNamespaceAware(true);
440             XMLReader xr = factory.newSAXParser().getXMLReader();
441 
442             xr.setContentHandler(this);
443             xr.setErrorHandler(this);
444 
445             InputStreamReader r = null;
446 
447             try {
448                 r = new InputStreamReader(info.openStream());
449                 xr.parse(new InputSource(r));
450             } finally {
451                 if (r != null){
452                     r.close();
453                 }
454             }
455 
456             return root;
457         }catch (SAXException ex){
458             IOException ioEx = new IOException("Error while parsing Ogre3D dotScene");
459             ioEx.initCause(ex);
460             throw ioEx;
461         } catch (ParserConfigurationException ex) {
462             IOException ioEx = new IOException("Error while parsing Ogre3D dotScene");
463             ioEx.initCause(ex);
464             throw ioEx;
465         }
466     }
467 
468 }
469