Home | History | Annotate | Download | only in ogre
      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 
     78     public SceneLoader(){
     79         super();
     80     }
     81 
     82     @Override
     83     public void startDocument() {
     84     }
     85 
     86     @Override
     87     public void endDocument() {
     88     }
     89 
     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 
    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 
    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 
    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 
    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 
    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 
    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
    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
    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
    409     public void characters(char ch[], int start, int length) {
    410     }
    411 
    412 
    413 
    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