/* * Copyright (c) 2009-2012 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.AmbientLight; 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.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.scene.CameraNode; import com.jme3.scene.LightNode; import com.jme3.scene.Spatial; import com.jme3.scene.control.CameraControl.ControlDirection; 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 int DEFAULT_CAM_WIDTH = 640; private static final int DEFAULT_CAM_HEIGHT = 480; private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); private SceneMaterialLoader materialLoader = new SceneMaterialLoader(); private SceneMeshLoader meshLoader=new SceneMeshLoader(); private Stack<String> elementStack = new Stack<String>(); 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 Camera camera; private CameraNode cameraNode; private int nodeIdx = 0; private static volatile int sceneIdx = 0; public SceneLoader() { super(); } @Override public void startDocument() { } @Override public void endDocument() { } private void reset() { meshLoader.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; camera = null; cameraNode = 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 if (lightType.equals("omni")) { // XXX: It doesn't seem any exporters actually emit this type? light = new AmbientLight(); } 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); } } private void parseCameraClipping(Attributes attribs) throws SAXException { if (attribs.getValue("near") != null) { camera.setFrustumNear(SAXUtil.parseFloat(attribs.getValue("near"))); camera.setFrustumFar(SAXUtil.parseFloat(attribs.getValue("far"))); } else { camera.setFrustumNear(SAXUtil.parseFloat(attribs.getValue("nearPlaneDist"))); camera.setFrustumFar(SAXUtil.parseFloat(attribs.getValue("farPlaneDist"))); } } private void parseCamera(Attributes attribs) throws SAXException { camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); if (SAXUtil.parseString(attribs.getValue("projectionType"), "perspective").equals("parallel")){ camera.setParallelProjection(true); } float fov = SAXUtil.parseFloat(attribs.getValue("fov"), 45f); if (fov < FastMath.PI) { // XXX: Most likely, it is in radians.. fov = fov * FastMath.RAD_TO_DEG; } camera.setFrustumPerspective(fov, (float)DEFAULT_CAM_WIDTH / DEFAULT_CAM_HEIGHT, 1, 1000); cameraNode = new CameraNode(attribs.getValue("name"), camera); cameraNode.setControlDir(ControlDirection.SpatialToCamera); node.attachChild(cameraNode); node = null; } private void parseEntity(Attributes attribs) throws SAXException { 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 { try{ Spatial ogreMesh=(Spatial)meshLoader.load(assetManager.locateAsset(meshKey)); entityNode.attachChild(ogreMesh); }catch(IOException e){ throw new AssetNotFoundException(meshKey.toString()); } } catch (AssetNotFoundException ex) { if (ex.getMessage().equals(meshFile)) { logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{meshKey, key}); // Attach placeholder asset. Spatial model = PlaceholderAssets.getPlaceholderModel(assetManager); model.setKey(key); entityNode.attachChild(model); } else { throw ex; } } node.attachChild(entityNode); node = null; } private void parseNode(Attributes attribs) throws SAXException { 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; } @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"); } else if (qName.equals("item")) { checkTopNode("externals"); } else if (qName.equals("file")) { checkTopNode("item"); // NOTE: This part of the file is ignored, it is parsed // by SceneMaterialLoader in the first pass. } 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'"); } parseNode(attribs); } 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"); parseEntity(attribs); } else if (qName.equals("camera")) { checkTopNode("node"); parseCamera(attribs); } else if (qName.equals("clipping")) { checkTopNode("camera"); parseCameraClipping(attribs); } else if (qName.equals("position")) { if (elementStack.peek().equals("node")) { node.setLocalTranslation(SAXUtil.parseVector3(attribs)); } else if (elementStack.peek().equals("camera")) { cameraNode.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("colourAmbient") || qName.equals("colorAmbient")) { if (elementStack.peek().equals("environment")) { ColorRGBA color = parseColor(attribs); if (!color.equals(ColorRGBA.Black) && !color.equals(ColorRGBA.BlackNoAlpha)) { // Lets add an ambient light to the scene. AmbientLight al = new AmbientLight(); al.setColor(color); root.addLight(al); } } } 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("camera")) { node = cameraNode.getParent(); cameraNode = 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); reset(); // == Run 1st pass over XML file to determine material list == materialList = materialLoader.load(assetManager, folderName, info.openStream()); if (materialList == null || materialList.isEmpty()) { // NOTE: No materials were found by searching the externals section. // Try finding a similarly named material file in the same folder. // (Backward compatibility only!) 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; } } // == Run 2nd pass to load entities and other objects == // 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; } } }