Saving materials with physics

Has anybody looked into doing this yet?



I started toying with it this afternoon because I think its something that should happen, and my project has reached the point that it's needed. Also, I noticed a TODO about saving the physics mesh, how bad is that?

I believe the Collada format has support for such things, but I don't believe anything on the jME side supports it yet…I'm sure we could apply such a patch if you were interested in contributing it though. :wink:

I think nymon talks about the .jme format saved from a scenegraph not about exporting from a modelling tool.



Saving material should be easy. I'm not sure what the state of that is (don't have the code at hand), but I thought it was saved. If not expect it to be saved after January (if you do not beat us to it ;)).



Saving meshes should not be that hard as well, but will be implementation dependent. So the ODE implementation must do that. I do not need it myself - that's why I did not bother to code it. Patches welcome (you need to read the mesh data with some ODE/jni functions and write it to the stream or construct a TriMesh object from it and write that one).

I was talking about the .jme format. Right now materials are not saved, and I was doing some quick changes in the code to try it. I only had a few minutes when I was working on it and hit a wall when trying to save the ContactHandlingDetails because its an interface, and it can't implement Savable? I didn't really look at it all that hard and in hindsight it occurs to me "Duh, of course it doesn't implement savable, it should extend it.". I guess it was the end of a long day. I would like to get that working so maybe this weekend.

OK, here it is. I've run it through all my tests and it seems to be good. If you want the patch a different way just hollar.  :slight_smile:



Index: src/com/jmex/physics/contact/ContactHandlingDetails.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/contact/ContactHandlingDetails.java,v
retrieving revision 1.6
diff -u -r1.6 ContactHandlingDetails.java
--- src/com/jmex/physics/contact/ContactHandlingDetails.java   22 Sep 2007 14:28:39 -0000   1.6
+++ src/com/jmex/physics/contact/ContactHandlingDetails.java   31 Dec 2007 05:41:29 -0000
@@ -33,13 +33,14 @@
 
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
+import com.jme.util.export.Savable;
 
 /**
  * Interface for contact handling detail like friction, bouciness, etc.
  *
  * @author Irrisor
  */
-public interface ContactHandlingDetails {
+public interface ContactHandlingDetails extends Savable {
     /**
      * @return true if the contact should be completely ignored (no collision, no events)
      * @see #isApplied()
Index: src/com/jmex/physics/contact/MutableContactInfo.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/contact/MutableContactInfo.java,v
retrieving revision 1.7
diff -u -r1.7 MutableContactInfo.java
--- src/com/jmex/physics/contact/MutableContactInfo.java   22 Sep 2007 14:28:39 -0000   1.7
+++ src/com/jmex/physics/contact/MutableContactInfo.java   31 Dec 2007 05:41:29 -0000
@@ -31,8 +31,15 @@
  */
 package com.jmex.physics.contact;
 
+import java.io.IOException;
+
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
+import com.jme.util.export.InputCapsule;
+import com.jme.util.export.JMEExporter;
+import com.jme.util.export.JMEImporter;
+import com.jme.util.export.OutputCapsule;
+import com.jmex.physics.material.Material;
 
 /**
  * Helper class to specify contact details.
@@ -294,6 +301,60 @@
     public final float getDampingCoefficient() {
         return this.dampingCoefficient;
     }
+
+    public static final String IGNORED_PROPERTY = "ignored";
+    public static final String MU_PROPERTY = "mu";
+    public static final String MUORTHOGONAL_PROPERTY = "muOrthogonal";
+    public static final String BOUNCE_PROPERTY = "bounce";
+    public static final String MINIMUMBOUNCEVELOCITY_PROPERTY = "minimumBounceVelocity";
+    public static final String SURFACEMOTION_PROPERTY = "sufaceMotion";
+    public static final String SLIP_PROPERTY = "slip";
+    public static final String APPLIED_PROPERTY = "applied";
+    public static final String FRICTIONDIRECTION_PROPERTY = "frictionDirection";
+    public static final String SPRINGCONSTANT_PROPERTY = "springConstant";
+    public static final String DAMPINGCOEFFICIENT_PROPERTY = "dampingCoefficient";
+
+   public Class getClassTag() {
+      return MutableContactInfo.class;
+   }
+
+   public void read(JMEImporter im) throws IOException {
+
+        InputCapsule capsule = im.getCapsule( this );
+       
+        setIgnored(capsule.readBoolean(IGNORED_PROPERTY, false));
+        setMu(capsule.readFloat(MU_PROPERTY, Float.NaN));
+        setMuOrthogonal(capsule.readFloat(MUORTHOGONAL_PROPERTY, Float.NaN));
+        setBounce(capsule.readFloat(BOUNCE_PROPERTY, Float.NaN));
+        setMinimumBounceVelocity(capsule.readFloat(MINIMUMBOUNCEVELOCITY_PROPERTY, Float.NaN));
+        setSurfaceMotion((Vector2f) capsule.readSavable(SURFACEMOTION_PROPERTY, null));
+        setSlip((Vector2f) capsule.readSavable(SLIP_PROPERTY, null));
+        setApplied(capsule.readBoolean(APPLIED_PROPERTY, true));
+
+        Vector3f frictionDirection = (Vector3f)capsule.readSavable(FRICTIONDIRECTION_PROPERTY, Vector3f.ZERO);
+        if (!Vector3f.ZERO.equals(frictionDirection))
+           setFrictionDirection(frictionDirection);
+       
+        setSpringConstant(capsule.readFloat(SPRINGCONSTANT_PROPERTY, Float.NaN));
+        setDampingCoefficient(capsule.readFloat(DAMPINGCOEFFICIENT_PROPERTY, Float.NaN));
+   }
+
+   public void write(JMEExporter ex) throws IOException {
+      
+      OutputCapsule capsule = ex.getCapsule( this );
+      
+      capsule.write(isIgnored(), IGNORED_PROPERTY, false);
+      capsule.write(getMu(), MU_PROPERTY, Float.NaN);
+      capsule.write(getMuOrthogonal(), MUORTHOGONAL_PROPERTY, Float.NaN);
+      capsule.write(getBounce(), BOUNCE_PROPERTY, Float.NaN);
+      capsule.write(getMinimumBounceVelocity(), MINIMUMBOUNCEVELOCITY_PROPERTY, Float.NaN);
+        capsule.write(getSurfaceMotion( null ), SURFACEMOTION_PROPERTY, null);
+        capsule.write(getSlip( null ), SLIP_PROPERTY, null);
+        capsule.write(isApplied(), APPLIED_PROPERTY, true);
+        capsule.write(getFrictionDirection(null), FRICTIONDIRECTION_PROPERTY, Vector3f.ZERO);
+        capsule.write(getSpringConstant(), SPRINGCONSTANT_PROPERTY, Float.NaN);
+        capsule.write(getDampingCoefficient(), DAMPINGCOEFFICIENT_PROPERTY, Float.NaN);
+   }
 }
 
 /*
Index: src/com/jmex/physics/PhysicsNode.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/PhysicsNode.java,v
retrieving revision 1.32
diff -u -r1.32 PhysicsNode.java
--- src/com/jmex/physics/PhysicsNode.java   26 Nov 2007 10:28:37 -0000   1.32
+++ src/com/jmex/physics/PhysicsNode.java   31 Dec 2007 05:41:29 -0000
@@ -32,6 +32,8 @@
 
 package com.jmex.physics;
 
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -54,6 +56,11 @@
 import com.jme.scene.shape.Capsule;
 import com.jme.scene.shape.GeoSphere;
 import com.jme.scene.shape.Sphere;
+import com.jme.util.export.InputCapsule;
+import com.jme.util.export.JMEExporter;
+import com.jme.util.export.JMEImporter;
+import com.jme.util.export.OutputCapsule;
+import com.jme.util.export.Savable;
 import com.jmex.physics.geometry.PhysicsBox;
 import com.jmex.physics.geometry.PhysicsCapsule;
 import com.jmex.physics.geometry.PhysicsCylinder;
@@ -79,7 +86,7 @@
  * @see StaticPhysicsNode
  * @see PhysicsCollisionGeometry
  */
-public abstract class PhysicsNode extends Node implements PhysicsSpatial {
+public abstract class PhysicsNode extends Node implements PhysicsSpatial, Savable {
     /**
      * Constructor.
      *
@@ -627,6 +634,31 @@
     public final PhysicsNode getPhysicsNode() {
         return this;
     }
+
+    @Override
+    public Class getClassTag() {
+          return PhysicsNode.class;
+    }
+   
+    public static final String MATERIAL_PROPERTY = "material";
+
+   @Override
+   public void read(JMEImporter im) throws IOException {
+      super.read(im);
+
+        InputCapsule capsule = im.getCapsule( this );
+       
+        setMaterial((Material)capsule.readSavable(MATERIAL_PROPERTY, Material.DEFAULT));
+   }
+
+   @Override
+   public void write(JMEExporter ex) throws IOException {
+      super.write(ex);
+
+        OutputCapsule capsule = ex.getCapsule( this );
+
+        capsule.write(getMaterial(), MATERIAL_PROPERTY, Material.DEFAULT);
+   }
 }
 
 /*
Index: src/com/jmex/physics/material/Material.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/material/Material.java,v
retrieving revision 1.16
diff -u -r1.16 Material.java
--- src/com/jmex/physics/material/Material.java   25 Dec 2007 11:07:00 -0000   1.16
+++ src/com/jmex/physics/material/Material.java   31 Dec 2007 05:41:29 -0000
@@ -31,12 +31,18 @@
  */
 package com.jmex.physics.material;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
 import com.jme.renderer.ColorRGBA;
+import com.jme.util.export.InputCapsule;
+import com.jme.util.export.JMEExporter;
+import com.jme.util.export.JMEImporter;
+import com.jme.util.export.OutputCapsule;
+import com.jme.util.export.Savable;
 import com.jmex.physics.contact.ContactHandlingDetails;
 import com.jmex.physics.contact.MutableContactInfo;
 
@@ -47,7 +53,7 @@
  * @see com.jmex.physics.PhysicsCollisionGeometry#setMaterial(Material)
  * @see com.jmex.physics.PhysicsNode#setMaterial(Material)
  */
-public class Material {
+public class Material implements Savable {
     public static final Material IRON = new Material( "iron" );
     public static final Material WOOD = new Material( "wood" );
     public static final Material CONCRETE = new Material( "concrete" );
@@ -213,7 +219,7 @@
         info.setApplied( true );
     }
 
-    private final String name;
+    private String name;
 
     /**
      * @return name for this material (might be null)
@@ -223,6 +229,15 @@
     }
 
     /**
+     * Sets the name of the material, can be null
+     *
+     * @param name
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
      * Create a new material.
      *
      * @param name name for this material, can be null
@@ -444,6 +459,43 @@
     public void setSpringPenetrationDepth( float springPenetrationDepth ) {
         this.springPenetrationDepth = springPenetrationDepth;
     }
+
+    private static final String NAME_PROPERTY = "name";
+    private static final String DENSITY_PROPERTY = "density";
+    private static final String CONTACTHANDLINGDETAILS_PROPERTY = "contacthandlingdetails";
+    private static final String SURFACEMOTION_PROPERTY = "sufacemotion";
+    private static final String SPRINGPENETRATIONDEPTH_PROPERTY = "springpenetrationdepth";
+
+   public Class getClassTag() {
+      return Material.class;
+   }
+
+   public void read(JMEImporter im) throws IOException {
+
+        InputCapsule capsule = im.getCapsule( this );
+       
+        setName(capsule.readString(NAME_PROPERTY, null));
+        setDensity(capsule.readFloat(DENSITY_PROPERTY, 1.0f));
+       
+        contactDetails = (Map<Material, ContactHandlingDetails>)capsule.readSavableMap(CONTACTHANDLINGDETAILS_PROPERTY, null);
+
+        Vector3f surfaceMotion = (Vector3f) capsule.readSavable(SURFACEMOTION_PROPERTY, Vector3f.ZERO );
+        if (!Vector3f.ZERO.equals(surfaceMotion))
+           setSurfaceMotion(surfaceMotion);
+       
+        setSpringPenetrationDepth(capsule.readFloat(SPRINGPENETRATIONDEPTH_PROPERTY, 0.0f));
+   }
+
+   public void write(JMEExporter ex) throws IOException {
+      
+      OutputCapsule capsule = ex.getCapsule( this );
+      
+        capsule.write(getName(), NAME_PROPERTY, null);
+        capsule.write(getDensity(), DENSITY_PROPERTY, 1.0f);
+        capsule.writeSavableMap(contactDetails, CONTACTHANDLINGDETAILS_PROPERTY, null);
+        capsule.write(getSurfaceMotion( null ), SURFACEMOTION_PROPERTY, Vector3f.ZERO );
+        capsule.write(getSpringPenetrationDepth(), SPRINGPENETRATIONDEPTH_PROPERTY, 0.0f);
+   }
 }
 
 /*

Nice. Will have a look at that in detail when I get back to my own pc in a few weeks.

While we're at it - is there any reason that Material shouldn't be serializable?

Do you mean java.io.Serializable? Why would that be needed?

Patch applied!

Please check it out and test it, as I had to make some changes to the way the materials were loaded.

Thanks again, will do.

@random task

I've run it through a few tests and we've still got a problem… it doesn't want to compute the mass for a material change when working with a loaded scene.

Try this:

Create a dynamic node, add a cube as collision geom.

Set material to sponge, compute mass (it works).

Set material to osmium, compute mass (it works) .

Save the scene to binary.



Load scene.

Material is osmium and mass is correct.

Change material to sponge, compute mass (doesn't work!).

Check material with getMaterial() returns sponge, but it behaves like osmium.



Save scene again, load it back.

Check material gives osmium, not sponge.



I've tested it with the original patch I posted above and it did not have these problems, works fine, so hopefully that helps you narrow it down.

Sorry about not responding earlier. You are correct, I meant Serializable, the interface.

I'm saving the material properties of physics nodes, along with other information, through serialization. So far I've just gone in and modified the Material class to implement serialization.



Is there any special reason why Material shouldn't implement Serializable? Many other elements of JME do. This class doesn't seem to contain any references to system dependent information, or to other classes that should not be serialized.

sbayless said:

Is there any special reason why Material shouldn't implement Serializable?

No :)
(I just don't use it and can't recommend to use it, but a patch - or just a "works fine with Serializable, please add implements clause" - is still welcome)

I haven't posted a patch before, so this might be the wrong format.

Because Materials hold references to contact handling details, those need to implement Serializable as well (included in the patch). This has been applied to the implementing class MutableContactInfo.



As far as I can see, none of these classes need any special handling for this to work - just a declared versionUID (done). I've tested this this, and it seems to work without problems.



Index: src/com/jmex/physics/contact/ContactHandlingDetails.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/contact/ContactHandlingDetails.java,v
retrieving revision 1.7
diff -u -r1.7 ContactHandlingDetails.java
--- src/com/jmex/physics/contact/ContactHandlingDetails.java   7 Feb 2008 18:05:40 -0000   1.7
+++ src/com/jmex/physics/contact/ContactHandlingDetails.java   18 Feb 2008 17:59:41 -0000
@@ -31,6 +31,8 @@
  */
 package com.jmex.physics.contact;
 
+import java.io.Serializable;
+
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
 import com.jme.util.export.Savable;
@@ -40,7 +42,7 @@
  *
  * @author Irrisor
  */
-public interface ContactHandlingDetails extends Savable {
+public interface ContactHandlingDetails extends Savable, Serializable {
     /**
      * @return true if the contact should be completely ignored (no collision, no events)
      * @see #isApplied()
Index: src/com/jmex/physics/contact/MutableContactInfo.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/contact/MutableContactInfo.java,v
retrieving revision 1.8
diff -u -r1.8 MutableContactInfo.java
--- src/com/jmex/physics/contact/MutableContactInfo.java   7 Feb 2008 18:05:40 -0000   1.8
+++ src/com/jmex/physics/contact/MutableContactInfo.java   18 Feb 2008 17:59:41 -0000
@@ -32,6 +32,7 @@
 package com.jmex.physics.contact;
 
 import java.io.IOException;
+import java.io.Serializable;
 
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
@@ -47,7 +48,8 @@
  * @see ContactHandlingDetails
  */
 public class MutableContactInfo implements ContactHandlingDetails {
-    private boolean ignored;
+    private static final long serialVersionUID = 1;
+   private boolean ignored;
     private float mu;
     private float muOrthogonal;
     private float bounce;

I don't know what happened to randomtask, but the patch I gave earlier worked perfectly. I don't know what he had to change or why, but he's left it in a state where it doesn't function as expected. I don't care how its done, I just need it to work.

Sorry, the previous patch I posted didn't include the changes to the Material class - just the other classes that had to be changed.



Index: src/com/jmex/physics/contact/ContactHandlingDetails.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/contact/ContactHandlingDetails.java,v
retrieving revision 1.7
diff -u -r1.7 ContactHandlingDetails.java
--- src/com/jmex/physics/contact/ContactHandlingDetails.java   7 Feb 2008 18:05:40 -0000   1.7
+++ src/com/jmex/physics/contact/ContactHandlingDetails.java   18 Feb 2008 20:20:45 -0000
@@ -31,6 +31,8 @@
  */
 package com.jmex.physics.contact;
 
+import java.io.Serializable;
+
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
 import com.jme.util.export.Savable;
@@ -40,7 +42,7 @@
  *
  * @author Irrisor
  */
-public interface ContactHandlingDetails extends Savable {
+public interface ContactHandlingDetails extends Savable, Serializable {
     /**
      * @return true if the contact should be completely ignored (no collision, no events)
      * @see #isApplied()
Index: src/com/jmex/physics/contact/MutableContactInfo.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/contact/MutableContactInfo.java,v
retrieving revision 1.8
diff -u -r1.8 MutableContactInfo.java
--- src/com/jmex/physics/contact/MutableContactInfo.java   7 Feb 2008 18:05:40 -0000   1.8
+++ src/com/jmex/physics/contact/MutableContactInfo.java   18 Feb 2008 20:20:45 -0000
@@ -32,6 +32,7 @@
 package com.jmex.physics.contact;
 
 import java.io.IOException;
+import java.io.Serializable;
 
 import com.jme.math.Vector2f;
 import com.jme.math.Vector3f;
@@ -47,7 +48,8 @@
  * @see ContactHandlingDetails
  */
 public class MutableContactInfo implements ContactHandlingDetails {
-    private boolean ignored;
+    private static final long serialVersionUID = 1L;
+   private boolean ignored;
     private float mu;
     private float muOrthogonal;
     private float bounce;
Index: src/com/jmex/physics/material/Material.java
===================================================================
RCS file: /cvs/jmephysics/src/com/jmex/physics/material/Material.java,v
retrieving revision 1.17
diff -u -r1.17 Material.java
--- src/com/jmex/physics/material/Material.java   7 Feb 2008 18:05:41 -0000   1.17
+++ src/com/jmex/physics/material/Material.java   18 Feb 2008 20:20:45 -0000
@@ -32,6 +32,7 @@
 package com.jmex.physics.material;
 
 import java.io.IOException;
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
@@ -55,7 +56,8 @@
  * @see com.jmex.physics.PhysicsCollisionGeometry#setMaterial(Material)
  * @see com.jmex.physics.PhysicsNode#setMaterial(Material)
  */
-public class Material implements Savable {
+public class Material implements Savable, Serializable {
+   private static final long serialVersionUID = 1L;   
     public static final Material IRON = new Material( "iron" );
     public static final Material WOOD = new Material( "wood" );
     public static final Material CONCRETE = new Material( "concrete" );

@irrisor: Can you please look into this? The problem I'm having with not being able to set materials (not just compute mass) on a loaded scene is a real pain. I don't think its directly related to the patch randomtask applied, but maybe to another code change along the way.


I've run it through a few tests and we've still got a problem... it doesn't want to compute the mass for a material change when working with a loaded scene.
Try this:
Create a dynamic node, add a cube as collision geom.
Set material to sponge, compute mass (it works).
Set material to osmium, compute mass (it works) .
Save the scene to binary.

Load scene.
Material is osmium and mass is correct.
Change material to sponge, compute mass (doesn't work!).
Check material with getMaterial() returns sponge, but it behaves like osmium.

Save scene again, load it back.
Check material gives osmium, not sponge.

please drop randomtask a pm, he does not read the forums frequently

Hi, sorry for vanishing after the patch!

And also sorry for screwing up nymon's patch in the first place…



Anyways, the problem is that after loading, the box has a material assigned, so when you change the material of the node, the mass is still calculated "correctly", because essentially the material is still osmium.

Fix is coming asap, as well as a short explanation of the changes I had to make to the patch!

Thanks for your patience, guys…

Fix is done, please update and check! This time, I was smart enough to toggle the forum notification, so I may actually read your feedback :slight_smile:



Now for the details on the patch:

Material.getContactHandlingDetails() does a lookup into its contactDetails hash map. The problem with nymon's patch was, once you loaded a physics scene, the materials were unserialized and although they contained the same properties, there were actually different instances than the ones in the contactDetails maps for non-serialized materials. So the modification I made was that after loading a PhysicsNode oder PhysicsCollisionGeometry, I compare the loaded material with the default materials and if there's a match, I replace it. When the PCGs are loaded from the PNs read() method, there material replacement happens before the material of the PN is set. So when PCG.setMaterial() is called, the PN does not yet have a material assigned, so the PCG material is not set to null as it should be. Does that make sense to you? Hmm, I don't know how to put it better, but there it is, and I hope it works for you now.



Btw, your description of the bug was very helpful, I could translate it line by line into a unit test, and that made it very easy to come up with the fix. Nice work…