[COMMITED]OgreXML-Importer: add userdefined properties to SceneObjects

In ogre-format you can also add user-defined properties(meta-data) to scene-objects.

e.g. in blender you can select an object swtich to LogicView(F4) and add a property

(STRING,FLOAT,TIME,BOOL,INT)



In the scene-xml you can find it here:

  • <scene formatVersion="1.0.0">
  •  <nodes>
  •    <node>
  •      …
  •      <userData>
  •        <property type="STRING" name="specialName" data="xyz"/>
  •        <property type="FLOAT" name="prop" data="0.0"/>
  •        <property type="BOOL" name="prop1" data="1"/>
  •        <property type="INT" name="prop2" data="0"/>
  •        <property type="TIME" name="prop3" data="3.0"/>
  •      </userData>
  •    </node>
  • </scene>



    That tag was not implemented yet. To do so I created a specialized DotScene-Object that extends  com.jme.scene.Node and has a HashMap for collecting the properties. After the scene is loaded you can

    access the data via the corresponding dotScene-node's getUserProperty(String name)-Method



    Here the new class:


package com.jmex.model.ogrexml;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;

import com.jme.scene.Node;

public class DotSceneNode extends Node{

   /**
    *
    * DotSceneNode for ogre dotscene files
    *
    * Adds user defined properties to scene-objects that can be exported
    * by the OgreScene-Exporter
    *
    * There a 5 different types that are mapped to Java-Wrappers:
    * STRING->String
    * FLOAT->Float
    * TIME->Float
    * BOOL->Boolean
    * INT->Integer
    *
    * (e.g. blender:
    *          - LogicPanel(F4)->Add Property
    *          - select LogicProperties in OgreScene-Exporter
    * )
    *
    * It can be found in the dotscene.xml - file:
    *
    * <scene formatVersion="1.0.0">
    *     <nodes>
    *       <node>
    *         ...
    *         <userData>
    *           <property type="STRING" name="specialName" data="xyz"/>
    *           <property type="FLOAT" name="prop" data="0.0"/>
    *           <property type="BOOL" name="prop1" data="1"/>
    *           <property type="INT" name="prop2" data="0"/>
    *           <property type="TIME" name="prop3" data="3.0"/>
    *         </userData>
    *       </node>
    * </scene>
    *
    * @author Thomas Trocha (thomas.trocha (at) gmail.com)
    *
    */
   private static final long serialVersionUID = 1L;
   private HashMap<String,Object> userProperties;
   
   public DotSceneNode() {
      super();
      userProperties = new HashMap<String, Object>();
   }

   public DotSceneNode(String name) {
      super(name);
      userProperties = new HashMap<String, Object>();
   }

   protected void addUserProperty(String key,Object value)
   {
      userProperties.put(key, value);
   }
   
   /**
    *
    * get user-defined property
    *
    * @param key
    * @return
    */
   public Object getUserProperty(String key)
   {
      return userProperties.get(key);
   }
   
   public Set<String> getUserPropertyKeys()
   {
      return userProperties.keySet();
   }
}



Here the patch for SceneLoader.java

Index: src/com/jmex/model/ogrexml/SceneLoader.java
===================================================================
--- src/com/jmex/model/ogrexml/SceneLoader.java   (revision 4366)
+++ src/com/jmex/model/ogrexml/SceneLoader.java   (working copy)
@@ -276,10 +276,45 @@
                     lightNode = childNode;
                 }
             } else if (tagName.equals("node")) {
-                com.jme.scene.Node newNode = new com.jme.scene.Node();
+                DotSceneNode newNode = new DotSceneNode();
                 loadNode(newNode, childNode);  // This is the recurse!
                 targetJmeNode.attachChild(newNode);
-            } else if (!(childNode instanceof Text)) {
+            }
+            else if (tagName.equals("userData"))
+            {
+               NodeList props = childNode.getChildNodes();
+               for (int j=0;j<props.getLength();j++)
+               {
+                  Node propNode = props.item(j);
+                  tagName = propNode.getNodeName();
+                  if (tagName.equals("property"))
+                    {
+                       String propType = getAttribute(propNode, "type");
+                       String propKey = getAttribute(propNode,"name");
+                       String propValue = getAttribute(propNode,"data");
+                       Object property;
+                       if (propType.equalsIgnoreCase("FLOAT") || propType.equalsIgnoreCase("TIME"))
+                       {
+                          property = new Float(propValue);
+                       }
+                       else if (propType.equalsIgnoreCase("BOOL"))
+                       {
+                          property = new Boolean(propValue);
+                       }
+                       else if (propType.equalsIgnoreCase("INT"))
+                       {
+                          property = new Integer(propValue);
+                       }
+                       else
+                       {
+                          property = new String(propValue);
+                       }
+                       ((DotSceneNode)targetJmeNode).addUserProperty(propKey, property);
+                    }
+                  System.out.println(tagName);
+               }
+            }
+            else if (!(childNode instanceof Text)) {
                 logger.warning("Ignoring unexpected element '" + tagName
                         + "' of type " + childNode.getClass().getName());
             }



Actually that would be the first time I will comit something....so I will have to ask for write access!

Seems a very useful feature.

To request write access you have to write mojomonk, see sticky post.



If its a one time thing, someone else can commit it also.

The userData|Property-Tags seems to be non standard and only available in the blender dotscene-exporter.

In the dotscene-dtd (http://www.ogre3d.org/wiki/index.php/DotSceneFormat) there is a tag “userDataReference” that seems to be similar.



Can anyone who is using OgreMax-Export create a simple dotscene file with an object that has simple (String/Float) userdata added? (http://www.ogremax.com/Documents/OgreMaxSceneExporter-3DSMax/user-data-page.html) If that is possible. Maybe there is something like a “String” or a “Float”-Class.


Well, the current OgreXMl importer doesn't work for OgreMax-Export anyway so… At least not in the form it's on svn.

Ah…didn't know that! One point more to change that! So some sample data from OgreMax-Exporter would be great…

Here's one not working with the current importer…



Scene file, although without user data (I might ask my artist to create one later to post here):


<scene formatVersion="1.0" upAxis="y" unitsPerMeter="39.3701" minOgreVersion="1.7" author="OgreMax Scene Exporter by Derek Nedelman (www.ogremax.com)">
    <environment>
        <colourAmbient r="0.521569" g="0.521569" b="0.521569" />
        <colourBackground r="0.760784" g="0.760784" b="0.760784" />
    </environment>
    <nodes>
        <node name="Box04">
            <scale x="1" y="1.0" z="1" />
            <position x="58.1994" y="0.0" z="21.5434" />
            <rotation qx="0" qy="0" qz="0" qw="1" />
            <entity name="Box04" id="6" meshFile="Box04.mesh" castShadows="true" receiveShadows="true">
                <subentities>
                    <subentity index="0" materialName="mirror" />
                </subentities>
            </entity>
        </node>
    </nodes>
    <renderTextures>
        <renderTexture name="Map4" width="512" height="512" textureType="2d" clearEveryFrame="true" autoUpdate="true" hideRenderObject="true">
            <backgroundColor r="0.760784" g="0.760784" b="0.760784" />
            <materials>
                <material name="mirror" technique="0" pass="0" textureUnit="0" />
            </materials>
        </renderTexture>
    </renderTextures>
</scene>



Is only the OrgeMax-Scenefile not working or the mesh-files as well?



Most helpful would be a scene with scene-files, meshes and material-files.



The problem why the scene file is not working is that the jME-OgreImporter needs

to load an external material-file with all materials of all entities in the scene. The needed

xml-tag is not included==>NULLPOINTER-EXCEPTION



So in order to make it work with the ogreMax-scene you should just need that:


Index: src/com/jmex/model/ogrexml/SceneLoader.java
===================================================================
--- src/com/jmex/model/ogrexml/SceneLoader.java   (revision 4375)
+++ src/com/jmex/model/ogrexml/SceneLoader.java   (working copy)
@@ -344,7 +344,14 @@
         // transformation attributes.  We should not ignore them.
         Node environment = getChildNode(sceneXmlNode, "environment");
 
-        loadExternals(externals);
+        try
+        {
+           loadExternals(externals);
+        }
+        catch (Exception e)
+        {
+           e.printStackTrace();
+        }
         if (!modelsOnly) {
             loadEnvironment(environment);
         }



But I don't know what is about the materials. Are they exported by OgreMax for every Entity?

That scene file seems to have some sort of mirror effect applied to the box mesh. I doubt that you will ever be able to load such a file… Really the Ogre3D importer in jME is only intended to support animated mesh support, the scene importer is there as an extra.

The scene loader needs some minor changes to be able to work at all. The mesh files should be quite ok. The material loader requires a lot more work to fully support the ogre format.

I see it the same away. For me a Scene is just a composition of entities with given Translation,Rotation and Scale.

I was just wondering why there was no external material-file specified in the OgreMax(like in every Blender dotscene-file). I think the cause is that OgreMax is build for handling .mesh files (is that right) and .mesh files have the material data already included. So there is no need for an external material file. But as long I can't see a complete ogremax-scene-archive I can just guess.



The scene as it is at the moment with additional userData is everything I would expect from my scene-file (yet). Still very very good work momoko_Fan!

Actually there is a .material file in the ogre format. And it contains a lot of information.



Yes that scene has a mirror effect and that's about the only thing I have left to implement in my version of the loader. But it's really tricky to get right.  :frowning:



here's the material file for the same scene:


material NoMaterial
{
   technique
   {
      pass
      {
         ambient 0.7 0.7 0.7
         diffuse 0.7 0.7 0.7
      }
   }
}


material mirror
{
   receive_shadows off
   transparency_casts_shadows off
   technique Map1
   {
      pass Map2
      {
         ambient 0.698039 0.698039 0.698039 1
         diffuse 0.698039 0.698039 0.698039 1
         specular 0.898039 0.898039 0.898039 1 20
         emissive 0 0 0 1
         scene_blend one zero
         depth_check on
         depth_write on
         depth_func less_equal
         depth_bias 0 0
         alpha_rejection always_pass 0
         cull_hardware clockwise
         cull_software back
         lighting on
         shading gouraud
         polygon_mode solid
         colour_write on
         max_lights 8
         start_light 0
         iteration once
         texture_unit Map3
         {
            texture Map4 2d
            binding_type fragment
            tex_coord_set 0
            tex_address_mode wrap wrap wrap
            tex_border_colour 0 0 0 1
            filtering trilinear
            max_anisotropy 1
            mipmap_bias 0
            colour_op_ex modulate src_texture src_current
            alpha_op_ex modulate src_texture src_current
            colour_op_multipass_fallback one zero
            env_map off
         }

      }

   }

}



With the new features of JME2, you can do it like this :


            } else if (tagName.equals("node")) {
                com.jme.scene.Node newNode = new com.jme.scene.Node();
                //DotSceneNode newNode = new DotSceneNode();
                loadNode(newNode, childNode);  // This is the recurse!
                targetJmeNode.attachChild(newNode);
            } else if (tagName.equals("userData")) {
               Node parentNode = childNode.getParentNode();
               NodeList props = childNode.getChildNodes();
               
               
               StringFloatMap floatAttrMap = null;
               StringBoolMap boolAttrMap = null;
               StringIntMap intAttrMap = null;
               StringStringMap strAttrMap = null;
               
               for (int j=0;j<props.getLength();j++)
               {
                  Node propNode = props.item(j);
                  tagName = propNode.getNodeName();
                  if (tagName.equals("property"))
                    {
                       String propType = getAttribute(propNode, "type");
                       String propKey = getAttribute(propNode,"name");
                       String propValue = getAttribute(propNode,"data");
                       
                       if (propType.equalsIgnoreCase("FLOAT") || propType.equalsIgnoreCase("TIME"))
                       {
                          if (floatAttrMap == null)
                          {
                             floatAttrMap = new StringFloatMap();
                            targetJmeNode.setUserData("floatSpatialAppAttrs", floatAttrMap);
                          }
                        floatAttrMap.put(getAttribute(propNode,"name"), new Float(getAttribute(propNode,"data")));
                       }
                       else if (propType.equalsIgnoreCase("BOOL"))
                       {
                          if (boolAttrMap == null)
                          {
                             boolAttrMap = new StringBoolMap();
                            targetJmeNode.setUserData("boolSpatialAppAttrs", boolAttrMap);
                          }
                          boolAttrMap.put(getAttribute(propNode,"name"), new Boolean(getAttribute(propNode,"data")));
                       }
                       else if (propType.equalsIgnoreCase("INT"))
                       {
                          if (intAttrMap == null) {
                             intAttrMap = new StringIntMap();
                             targetJmeNode.setUserData("intSpatialAppAttrs", intAttrMap);
                          }
                          intAttrMap.put(getAttribute(propNode,"name"), new Integer(getAttribute(propNode,"data")));                        
                       }
                       else
                       {
                          if (strAttrMap == null) {
                             strAttrMap = new StringStringMap();
                             targetJmeNode.setUserData("stringSpatialAppAttrs", strAttrMap);
                          }
                        strAttrMap.put(getAttribute(propNode,"name"), getAttribute(propNode,"data"));                        
                          }
                       
                       
                       
//                       Object property;
//                       if (propType.equalsIgnoreCase("FLOAT") || propType.equalsIgnoreCase("TIME"))
//                       {
//                          property = new Float(propValue);
//                       }
//                       else if (propType.equalsIgnoreCase("BOOL"))
//                       {
//                          property = new Boolean(propValue);
//                       }
//                       else if (propType.equalsIgnoreCase("INT"))
//                       {
//                          property = new Integer(propValue);
//                       }
//                       else
//                       {
//                          property = new String(propValue);
//                       }
//                       ((DotSceneNode)targetJmeNode).addUserProperty(propKey, property);
                     
                       // TODO : Implementer les autres types
                       
                   }
                  //System.out.println(tagName);
               }
            } else if (!(childNode instanceof Text)) {
        logger.warning("Ignoring unexpected element '" + tagName
                        + "' of type " + childNode.getClass().getName());
            }
        }



and access the datas like this :

         
System.out.println("TAG = " + ((Map)(sceneNode.getChild("Plane_groundDotNode").getUserData("stringSpatialAppAttrs"))).get("tag"));



So, no need for subclassing the Node class.

Hope this will help

Cool, thx a lot! I will check and contribute it.

Ok, I checked and commit it! Beside the fact that


new Boolean("1") == false



Everything was perfect. I once struggled over that as well!!

Keep on rocking and thx for the patch