/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.scene.plugins.ogre; import com.jme3.asset.*; import com.jme3.light.DirectionalLight; import com.jme3.light.Light; import com.jme3.light.PointLight; import com.jme3.light.SpotLight; import com.jme3.material.MaterialList; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.ogre.matext.OgreMaterialKey; import com.jme3.util.PlaceholderAssets; import com.jme3.util.xml.SAXUtil; import static com.jme3.util.xml.SAXUtil.*; import java.io.IOException; import java.io.InputStreamReader; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; public class SceneLoader extends DefaultHandler implements AssetLoader { private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); private Stack elementStack = new Stack(); private AssetKey key; private String sceneName; private String folderName; private AssetManager assetManager; private MaterialList materialList; private com.jme3.scene.Node root; private com.jme3.scene.Node node; private com.jme3.scene.Node entityNode; private Light light; private int nodeIdx = 0; private static volatile int sceneIdx = 0; public SceneLoader(){ super(); } @Override public void startDocument() { } @Override public void endDocument() { } private void reset(){ elementStack.clear(); nodeIdx = 0; // NOTE: Setting some of those to null is only needed // if the parsed file had an error e.g. startElement was called // but not endElement root = null; node = null; entityNode = null; light = null; } private void checkTopNode(String topNode) throws SAXException{ if (!elementStack.peek().equals(topNode)){ throw new SAXException("dotScene parse error: Expected parent node to be " + topNode); } } private Quaternion parseQuat(Attributes attribs) throws SAXException{ if (attribs.getValue("x") != null){ // defined as quaternion float x = parseFloat(attribs.getValue("x")); float y = parseFloat(attribs.getValue("y")); float z = parseFloat(attribs.getValue("z")); float w = parseFloat(attribs.getValue("w")); return new Quaternion(x,y,z,w); }else if (attribs.getValue("qx") != null){ // defined as quaternion with prefix "q" float x = parseFloat(attribs.getValue("qx")); float y = parseFloat(attribs.getValue("qy")); float z = parseFloat(attribs.getValue("qz")); float w = parseFloat(attribs.getValue("qw")); return new Quaternion(x,y,z,w); }else if (attribs.getValue("angle") != null){ // defined as angle + axis float angle = parseFloat(attribs.getValue("angle")); float axisX = parseFloat(attribs.getValue("axisX")); float axisY = parseFloat(attribs.getValue("axisY")); float axisZ = parseFloat(attribs.getValue("axisZ")); Quaternion q = new Quaternion(); q.fromAngleAxis(angle, new Vector3f(axisX, axisY, axisZ)); return q; }else{ // defines as 3 angles along XYZ axes float angleX = parseFloat(attribs.getValue("angleX")); float angleY = parseFloat(attribs.getValue("angleY")); float angleZ = parseFloat(attribs.getValue("angleZ")); Quaternion q = new Quaternion(); q.fromAngles(angleX, angleY, angleZ); return q; } } private void parseLightNormal(Attributes attribs) throws SAXException { checkTopNode("light"); // SpotLight will be supporting a direction-normal, too. if (light instanceof DirectionalLight) ((DirectionalLight) light).setDirection(parseVector3(attribs)); else if (light instanceof SpotLight){ ((SpotLight) light).setDirection(parseVector3(attribs)); } } private void parseLightAttenuation(Attributes attribs) throws SAXException { // NOTE: Derives range based on "linear" if it is used solely // for the attenuation. Otherwise derives it from "range" checkTopNode("light"); if (light instanceof PointLight || light instanceof SpotLight){ float range = parseFloat(attribs.getValue("range")); float constant = parseFloat(attribs.getValue("constant")); float linear = parseFloat(attribs.getValue("linear")); String quadraticStr = attribs.getValue("quadratic"); if (quadraticStr == null) quadraticStr = attribs.getValue("quadric"); float quadratic = parseFloat(quadraticStr); if (constant == 1 && quadratic == 0 && linear > 0){ range = 1f / linear; } if (light instanceof PointLight){ ((PointLight) light).setRadius(range); }else{ ((SpotLight)light).setSpotRange(range); } } } private void parseLightSpotLightRange(Attributes attribs) throws SAXException{ checkTopNode("light"); float outer = SAXUtil.parseFloat(attribs.getValue("outer")); float inner = SAXUtil.parseFloat(attribs.getValue("inner")); if (!(light instanceof SpotLight)){ throw new SAXException("dotScene parse error: spotLightRange " + "can only appear under 'spot' light elements"); } SpotLight sl = (SpotLight) light; sl.setSpotInnerAngle(inner * 0.5f); sl.setSpotOuterAngle(outer * 0.5f); } private void parseLight(Attributes attribs) throws SAXException { if (node == null || node.getParent() == null) throw new SAXException("dotScene parse error: light can only appear under a node"); checkTopNode("node"); String lightType = parseString(attribs.getValue("type"), "point"); if(lightType.equals("point")) { light = new PointLight(); } else if(lightType.equals("directional") || lightType.equals("sun")) { light = new DirectionalLight(); // Assuming "normal" property is not provided ((DirectionalLight)light).setDirection(Vector3f.UNIT_Z); } else if(lightType.equals("spotLight") || lightType.equals("spot")) { light = new SpotLight(); } else { logger.log(Level.WARNING, "No matching jME3 LightType found for OGRE LightType: {0}", lightType); } logger.log(Level.FINEST, "{0} created.", light); if (!parseBool(attribs.getValue("visible"), true)){ // set to disabled } // "attach" it to the parent of this node if (light != null) node.getParent().addLight(light); } @Override public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{ if (qName.equals("scene")){ if (elementStack.size() != 0){ throw new SAXException("dotScene parse error: 'scene' element must be the root XML element"); } String version = attribs.getValue("formatVersion"); if (version == null || (!version.equals("1.0.0") && !version.equals("1.0.1"))) logger.log(Level.WARNING, "Unrecognized version number" + " in dotScene file: {0}", version); }else if (qName.equals("nodes")){ if (root != null){ throw new SAXException("dotScene parse error: nodes element was specified twice"); } if (sceneName == null) root = new com.jme3.scene.Node("OgreDotScene"+(++sceneIdx)); else root = new com.jme3.scene.Node(sceneName+"-scene_node"); node = root; }else if (qName.equals("externals")){ checkTopNode("scene"); // Not loaded currently }else if (qName.equals("item")){ checkTopNode("externals"); }else if (qName.equals("file")){ checkTopNode("item"); // XXX: Currently material file name is based // on the scene's filename. THIS IS NOT CORRECT. // To solve, port SceneLoader to use DOM instead of SAX //String matFile = folderName+attribs.getValue("name"); //try { // materialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile)); //} catch (AssetNotFoundException ex){ // materialList = null; // logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile); //} }else if (qName.equals("node")){ String curElement = elementStack.peek(); if (!curElement.equals("node") && !curElement.equals("nodes")){ throw new SAXException("dotScene parse error: " + "node element can only appear under 'node' or 'nodes'"); } String name = attribs.getValue("name"); if (name == null) name = "OgreNode-" + (++nodeIdx); com.jme3.scene.Node newNode = new com.jme3.scene.Node(name); if (node != null){ node.attachChild(newNode); } node = newNode; }else if (qName.equals("property")){ if (node != null){ String type = attribs.getValue("type"); String name = attribs.getValue("name"); String data = attribs.getValue("data"); if (type.equals("BOOL")){ node.setUserData(name, Boolean.parseBoolean(data)||data.equals("1")); }else if (type.equals("FLOAT")){ node.setUserData(name, Float.parseFloat(data)); }else if (type.equals("STRING")){ node.setUserData(name, data); }else if (type.equals("INT")){ node.setUserData(name, Integer.parseInt(data)); } } }else if (qName.equals("entity")){ checkTopNode("node"); String name = attribs.getValue("name"); if (name == null) name = "OgreEntity-" + (++nodeIdx); else name += "-entity"; String meshFile = attribs.getValue("meshFile"); if (meshFile == null) { throw new SAXException("Required attribute 'meshFile' missing for 'entity' node"); } // TODO: Not currently used String materialName = attribs.getValue("materialName"); if (folderName != null) { meshFile = folderName + meshFile; } // NOTE: append "xml" since its assumed mesh files are binary in dotScene meshFile += ".xml"; entityNode = new com.jme3.scene.Node(name); OgreMeshKey meshKey = new OgreMeshKey(meshFile, materialList); try { Spatial ogreMesh = assetManager.loadModel(meshKey); entityNode.attachChild(ogreMesh); } catch (AssetNotFoundException ex){ logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{meshKey, key}); // Attach placeholder asset. entityNode.attachChild(PlaceholderAssets.getPlaceholderModel(assetManager)); } node.attachChild(entityNode); node = null; }else if (qName.equals("position")){ if (elementStack.peek().equals("node")){ node.setLocalTranslation(SAXUtil.parseVector3(attribs)); } }else if (qName.equals("quaternion") || qName.equals("rotation")){ node.setLocalRotation(parseQuat(attribs)); }else if (qName.equals("scale")){ node.setLocalScale(SAXUtil.parseVector3(attribs)); } else if (qName.equals("light")) { parseLight(attribs); } else if (qName.equals("colourDiffuse") || qName.equals("colorDiffuse")) { if (elementStack.peek().equals("light")){ if (light != null){ light.setColor(parseColor(attribs)); } }else{ checkTopNode("environment"); } } else if (qName.equals("normal") || qName.equals("direction")) { checkTopNode("light"); parseLightNormal(attribs); } else if (qName.equals("lightAttenuation")) { parseLightAttenuation(attribs); } else if (qName.equals("spotLightRange") || qName.equals("lightRange")) { parseLightSpotLightRange(attribs); } elementStack.push(qName); } @Override public void endElement(String uri, String name, String qName) throws SAXException { if (qName.equals("node")){ node = node.getParent(); }else if (qName.equals("nodes")){ node = null; }else if (qName.equals("entity")){ node = entityNode.getParent(); entityNode = null; }else if (qName.equals("light")){ // apply the node's world transform on the light.. root.updateGeometricState(); if (light != null){ if (light instanceof DirectionalLight){ DirectionalLight dl = (DirectionalLight) light; Quaternion q = node.getWorldRotation(); Vector3f dir = dl.getDirection(); q.multLocal(dir); dl.setDirection(dir); }else if (light instanceof PointLight){ PointLight pl = (PointLight) light; Vector3f pos = node.getWorldTranslation(); pl.setPosition(pos); }else if (light instanceof SpotLight){ SpotLight sl = (SpotLight) light; Vector3f pos = node.getWorldTranslation(); sl.setPosition(pos); Quaternion q = node.getWorldRotation(); Vector3f dir = sl.getDirection(); q.multLocal(dir); sl.setDirection(dir); } } light = null; } checkTopNode(qName); elementStack.pop(); } @Override public void characters(char ch[], int start, int length) { } public Object load(AssetInfo info) throws IOException { try{ key = info.getKey(); assetManager = info.getManager(); sceneName = key.getName(); String ext = key.getExtension(); folderName = key.getFolder(); sceneName = sceneName.substring(0, sceneName.length() - ext.length() - 1); OgreMaterialKey materialKey = new OgreMaterialKey(sceneName+".material"); try { materialList = (MaterialList) assetManager.loadAsset(materialKey); } catch (AssetNotFoundException ex){ logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{materialKey, key}); materialList = null; } reset(); // Added by larynx 25.06.2011 // Android needs the namespace aware flag set to true // Kirill 30.06.2011 // Now, hack is applied for both desktop and android to avoid // checking with JmeSystem. SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); XMLReader xr = factory.newSAXParser().getXMLReader(); xr.setContentHandler(this); xr.setErrorHandler(this); InputStreamReader r = null; try { r = new InputStreamReader(info.openStream()); xr.parse(new InputSource(r)); } finally { if (r != null){ r.close(); } } return root; }catch (SAXException ex){ IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); ioEx.initCause(ex); throw ioEx; } catch (ParserConfigurationException ex) { IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); ioEx.initCause(ex); throw ioEx; } } }