Преглед на файлове

- add new BulletDebugAppState for physics debugging
- fix non-displayed physics objects in debug view
- remove dependency of base physics objects to scenegraph api, only jme math dependencies are left

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10342 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

nor..67 преди 12 години
родител
ревизия
8992563b86

+ 62 - 22
engine/src/bullet-common/com/jme3/bullet/BulletAppState.java

@@ -35,6 +35,7 @@ import com.jme3.app.Application;
 import com.jme3.app.state.AppState;
 import com.jme3.app.state.AppStateManager;
 import com.jme3.bullet.PhysicsSpace.BroadphaseType;
+import com.jme3.bullet.debug.BulletDebugAppState;
 import com.jme3.math.Vector3f;
 import com.jme3.renderer.RenderManager;
 import java.util.concurrent.*;
@@ -43,6 +44,7 @@ import java.util.logging.Logger;
 
 /**
  * <code>BulletAppState</code> allows using bullet physics in an Application.
+ *
  * @author normenhansen
  */
 public class BulletAppState implements AppState, PhysicsTickListener {
@@ -56,31 +58,38 @@ public class BulletAppState implements AppState, PhysicsTickListener {
     protected BroadphaseType broadphaseType = BroadphaseType.DBVT;
     protected Vector3f worldMin = new Vector3f(-10000f, -10000f, -10000f);
     protected Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f);
-    private float speed = 1;
+    protected float speed = 1;
     protected boolean active = true;
+    protected boolean debugEnabled = false;
+    protected BulletDebugAppState debugAppState;
     protected float tpf;
     protected Future physicsFuture;
 
     /**
-     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
-     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
+     * Creates a new BulletAppState running a PhysicsSpace for physics
+     * simulation, use getStateManager().addState(bulletAppState) to enable
+     * physics for an Application.
      */
     public BulletAppState() {
     }
 
     /**
-     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
-     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
-     * @param broadphaseType The type of broadphase collision detection, BroadphaseType.DVBT is the default
+     * Creates a new BulletAppState running a PhysicsSpace for physics
+     * simulation, use getStateManager().addState(bulletAppState) to enable
+     * physics for an Application.
+     *
+     * @param broadphaseType The type of broadphase collision detection,
+     * BroadphaseType.DVBT is the default
      */
     public BulletAppState(BroadphaseType broadphaseType) {
         this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), broadphaseType);
     }
 
     /**
-     * Creates a new BulletAppState running a PhysicsSpace for physics simulation,
-     * use getStateManager().addState(bulletAppState) to enable physics for an Application.
-     * An AxisSweep broadphase is used.
+     * Creates a new BulletAppState running a PhysicsSpace for physics
+     * simulation, use getStateManager().addState(bulletAppState) to enable
+     * physics for an Application. An AxisSweep broadphase is used.
+     *
      * @param worldMin The minimum world extent
      * @param worldMax The maximum world extent
      */
@@ -101,7 +110,6 @@ public class BulletAppState implements AppState, PhysicsTickListener {
         executor = new ScheduledThreadPoolExecutor(1);
         final BulletAppState app = this;
         Callable<Boolean> call = new Callable<Boolean>() {
-
             public Boolean call() throws Exception {
                 detachedPhysicsLastUpdate = System.currentTimeMillis();
                 pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType);
@@ -120,7 +128,6 @@ public class BulletAppState implements AppState, PhysicsTickListener {
         }
     }
     private Callable<Boolean> parallelPhysicsUpdate = new Callable<Boolean>() {
-
         public Boolean call() throws Exception {
             pSpace.update(tpf * getSpeed());
             return true;
@@ -128,7 +135,6 @@ public class BulletAppState implements AppState, PhysicsTickListener {
     };
     long detachedPhysicsLastUpdate = 0;
     private Callable<Boolean> detachedPhysicsUpdate = new Callable<Boolean>() {
-
         public Boolean call() throws Exception {
             pSpace.update(getPhysicsSpace().getAccuracy() * getSpeed());
             pSpace.distributeEvents();
@@ -144,8 +150,8 @@ public class BulletAppState implements AppState, PhysicsTickListener {
     }
 
     /**
-     * The physics system is started automatically on attaching, if you want to start it
-     * before for some reason, you can use this method.
+     * The physics system is started automatically on attaching, if you want to
+     * start it before for some reason, you can use this method.
      */
     public void startPhysics() {
         //start physics thread(pool)
@@ -165,21 +171,30 @@ public class BulletAppState implements AppState, PhysicsTickListener {
         if (!initialized) {
             startPhysics();
         }
+        this.stateManager = stateManager;
         initialized = true;
     }
 
     public boolean isInitialized() {
         return initialized;
     }
-    
+
     public void setEnabled(boolean enabled) {
         this.active = enabled;
     }
-    
+
     public boolean isEnabled() {
         return active;
     }
 
+    public void setDebugEnabled(boolean debugEnabled) {
+        this.debugEnabled = debugEnabled;
+    }
+
+    public boolean isDebugEnabled() {
+        return debugEnabled;
+    }
+
     public void stateAttached(AppStateManager stateManager) {
         if (!initialized) {
             startPhysics();
@@ -187,17 +202,36 @@ public class BulletAppState implements AppState, PhysicsTickListener {
         if (threadingType == ThreadingType.PARALLEL) {
             PhysicsSpace.setLocalThreadPhysicsSpace(pSpace);
         }
+        if (debugEnabled) {
+            debugAppState = new BulletDebugAppState(pSpace);
+            stateManager.attach(debugAppState);
+        }
     }
 
     public void stateDetached(AppStateManager stateManager) {
     }
 
     public void update(float tpf) {
+        if (debugEnabled && debugAppState == null) {
+            debugAppState = new BulletDebugAppState(pSpace);
+            stateManager.attach(debugAppState);
+            pSpace.enableDebug(app.getAssetManager());
+        } else if (!debugEnabled && debugAppState != null) {
+            stateManager.detach(debugAppState);
+            debugAppState = null;
+            pSpace.enableDebug(null);
+        }
+        //TODO: remove when deprecation of PhysicsSpace.enableDebug is through
+        if (pSpace.getDebugManager() != null && !debugEnabled) {
+            debugEnabled = true;
+        } else if (pSpace.getDebugManager() == null && debugEnabled) {
+            debugEnabled = false;
+        }
         if (!active) {
             return;
         }
 //        if (threadingType != ThreadingType.DETACHED) {
-            pSpace.distributeEvents();
+        pSpace.distributeEvents();
 //        }
         this.tpf = tpf;
     }
@@ -228,6 +262,10 @@ public class BulletAppState implements AppState, PhysicsTickListener {
     }
 
     public void cleanup() {
+        if (debugAppState != null) {
+            stateManager.detach(debugAppState);
+            debugAppState = null;
+        }
         if (executor != null) {
             executor.shutdown();
             executor = null;
@@ -245,6 +283,7 @@ public class BulletAppState implements AppState, PhysicsTickListener {
 
     /**
      * Use before attaching state
+     *
      * @param threadingType the threadingType to set
      */
     public void setThreadingType(ThreadingType threadingType) {
@@ -289,13 +328,14 @@ public class BulletAppState implements AppState, PhysicsTickListener {
     public enum ThreadingType {
 
         /**
-         * Default mode; user update, physics update and rendering happen sequentially (single threaded)
+         * Default mode; user update, physics update and rendering happen
+         * sequentially (single threaded)
          */
         SEQUENTIAL,
         /**
-         * Parallel threaded mode; physics update and rendering are executed in parallel, update order is kept.<br/>
-         * Multiple BulletAppStates will execute in parallel in this mode.
+         * Parallel threaded mode; physics update and rendering are executed in
+         * parallel, update order is kept.<br/> Multiple BulletAppStates will
+         * execute in parallel in this mode.
          */
-        PARALLEL,
-    }
+        PARALLEL,}
 }

+ 81 - 0
engine/src/bullet-common/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java

@@ -0,0 +1,81 @@
+/*
+ * 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.bullet.debug;
+
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.AbstractControl;
+
+/**
+ *
+ * @author normenhansen
+ */
+public abstract class AbstractPhysicsDebugControl extends AbstractControl {
+
+    private final Quaternion tmp_inverseWorldRotation = new Quaternion();
+    protected final BulletDebugAppState debugAppState;
+
+    public AbstractPhysicsDebugControl(BulletDebugAppState debugAppState) {
+        this.debugAppState = debugAppState;
+    }
+
+    /**
+     * This is called on the physics thread for debug controls
+     */
+    @Override
+    protected abstract void controlUpdate(float tpf);
+
+    protected void applyPhysicsTransform(Vector3f worldLocation, Quaternion worldRotation) {
+        applyPhysicsTransform(worldLocation, worldRotation, this.spatial);
+    }
+
+    protected void applyPhysicsTransform(Vector3f worldLocation, Quaternion worldRotation, Spatial spatial) {
+        if (spatial != null) {
+            Vector3f localLocation = spatial.getLocalTranslation();
+            Quaternion localRotationQuat = spatial.getLocalRotation();
+            if (spatial.getParent() != null) {
+                localLocation.set(worldLocation).subtractLocal(spatial.getParent().getWorldTranslation());
+                localLocation.divideLocal(spatial.getParent().getWorldScale());
+                tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+                localRotationQuat.set(worldRotation);
+                tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat);
+                spatial.setLocalTranslation(localLocation);
+                spatial.setLocalRotation(localRotationQuat);
+            } else {
+                spatial.setLocalTranslation(worldLocation);
+                spatial.setLocalRotation(worldRotation);
+            }
+        }
+
+    }
+}

+ 92 - 0
engine/src/bullet-common/com/jme3/bullet/debug/BulletCharacterDebugControl.java

@@ -0,0 +1,92 @@
+/*
+ * 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.bullet.debug;
+
+import com.jme3.bullet.objects.PhysicsCharacter;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class BulletCharacterDebugControl extends AbstractPhysicsDebugControl {
+
+    protected final PhysicsCharacter body;
+    protected final Geometry geom;
+    protected final Vector3f location = new Vector3f();
+    protected final Quaternion rotation = new Quaternion();
+
+    public BulletCharacterDebugControl(BulletDebugAppState debugAppState, PhysicsCharacter body) {
+        super(debugAppState);
+        this.body = body;
+        this.geom = new Geometry(body.toString());
+        geom.setMaterial(debugAppState.DEBUG_PINK);
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial) {
+        if (spatial != null && spatial instanceof Node) {
+            Node node = (Node) spatial;
+            node.attachChild(geom);
+        } else if (spatial == null && this.spatial != null) {
+            Node node = (Node) this.spatial;
+            node.detachChild(geom);
+        }
+        super.setSpatial(spatial);
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        Mesh mesh = debugAppState.getShapeBuffer().getShapeMesh(body.getCollisionShape());
+        if (mesh != null) {
+            if (geom.getMesh() != mesh) {
+                geom.setMesh(mesh);
+            }
+        } else {
+            if (geom.getMesh() != BulletDebugAppState.CollisionShapeBuffer.NO_MESH) {
+                geom.setMesh(BulletDebugAppState.CollisionShapeBuffer.NO_MESH);
+            }
+        }
+        applyPhysicsTransform(body.getPhysicsLocation(location), Quaternion.IDENTITY);
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+    }
+}

+ 361 - 0
engine/src/bullet-common/com/jme3/bullet/debug/BulletDebugAppState.java

@@ -0,0 +1,361 @@
+/*
+ * 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.bullet.debug;
+
+import com.jme3.app.Application;
+import com.jme3.app.state.AbstractAppState;
+import com.jme3.app.state.AppStateManager;
+import com.jme3.asset.AssetManager;
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.bullet.objects.PhysicsCharacter;
+import com.jme3.bullet.objects.PhysicsGhostObject;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.bullet.util.DebugShapeFactory;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class BulletDebugAppState extends AbstractAppState {
+
+    protected static final Logger logger = Logger.getLogger(BulletDebugAppState.class.getName());
+    protected final PhysicsSpace space;
+    protected final CollisionShapeBuffer shapeBuffer = new CollisionShapeBuffer();
+    protected final ArrowBuffer arrowBuffer = new ArrowBuffer();
+    protected final Node physicsDebugRootNode = new Node("Physics Debug Root Node");
+    protected ViewPort viewPort;
+    protected RenderManager rm;
+    public Material DEBUG_BLUE;
+    public Material DEBUG_RED;
+    public Material DEBUG_GREEN;
+    public Material DEBUG_YELLOW;
+    public Material DEBUG_MAGENTA;
+    public Material DEBUG_PINK;
+    protected HashMap<PhysicsRigidBody, Spatial> bodies = new HashMap<PhysicsRigidBody, Spatial>();
+    protected HashMap<PhysicsJoint, Spatial> joints = new HashMap<PhysicsJoint, Spatial>();
+    protected HashMap<PhysicsGhostObject, Spatial> ghosts = new HashMap<PhysicsGhostObject, Spatial>();
+    protected HashMap<PhysicsCharacter, Spatial> characters = new HashMap<PhysicsCharacter, Spatial>();
+    protected HashMap<PhysicsVehicle, Spatial> vehicles = new HashMap<PhysicsVehicle, Spatial>();
+
+    public BulletDebugAppState(PhysicsSpace space) {
+        this.space = space;
+    }
+
+    @Override
+    public void initialize(AppStateManager stateManager, Application app) {
+        super.initialize(stateManager, app);
+        this.rm = app.getRenderManager();
+        setupMaterials(app);
+        physicsDebugRootNode.setCullHint(Spatial.CullHint.Never);
+        viewPort = rm.createMainView("Physics Debug Overlay", app.getCamera());
+        viewPort.setClearFlags(false, true, false);
+        viewPort.attachScene(physicsDebugRootNode);
+    }
+
+    @Override
+    public void cleanup() {
+        rm.removeMainView(viewPort);
+        super.cleanup();
+    }
+
+    @Override
+    public void update(float tpf) {
+        super.update(tpf);
+        //update all object links
+        updateRigidBodies();
+        updateGhosts();
+        updateCharacters();
+        updateJoints();
+        updateVehicles();
+        //update our debug root node
+        physicsDebugRootNode.updateLogicalState(tpf);
+        physicsDebugRootNode.updateGeometricState();
+        //reset shapes -> this removes all meshes for shapes that were not used this update cycle
+        shapeBuffer.resetShapes();
+        //reset arrows -> this makes arrow meshes available for the next update cycle
+        arrowBuffer.resetArrows();
+    }
+
+    @Override
+    public void render(RenderManager rm) {
+        super.render(rm);
+        if (viewPort != null) {
+            rm.renderScene(physicsDebugRootNode, viewPort);
+        }
+    }
+
+    private void setupMaterials(Application app) {
+        AssetManager manager = app.getAssetManager();
+        DEBUG_BLUE = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        DEBUG_BLUE.getAdditionalRenderState().setWireframe(true);
+        DEBUG_BLUE.setColor("Color", ColorRGBA.Blue);
+        DEBUG_GREEN = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        DEBUG_GREEN.getAdditionalRenderState().setWireframe(true);
+        DEBUG_GREEN.setColor("Color", ColorRGBA.Green);
+        DEBUG_RED = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        DEBUG_RED.getAdditionalRenderState().setWireframe(true);
+        DEBUG_RED.setColor("Color", ColorRGBA.Red);
+        DEBUG_YELLOW = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        DEBUG_YELLOW.getAdditionalRenderState().setWireframe(true);
+        DEBUG_YELLOW.setColor("Color", ColorRGBA.Yellow);
+        DEBUG_MAGENTA = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        DEBUG_MAGENTA.getAdditionalRenderState().setWireframe(true);
+        DEBUG_MAGENTA.setColor("Color", ColorRGBA.Magenta);
+        DEBUG_PINK = new Material(manager, "Common/MatDefs/Misc/Unshaded.j3md");
+        DEBUG_PINK.getAdditionalRenderState().setWireframe(true);
+        DEBUG_PINK.setColor("Color", ColorRGBA.Pink);
+    }
+
+    private void updateRigidBodies() {
+        HashMap<PhysicsRigidBody, Spatial> oldObjects = bodies;
+        bodies = new HashMap<PhysicsRigidBody, Spatial>();
+        Collection<PhysicsRigidBody> current = space.getRigidBodyList();
+        //create new map
+        for (Iterator<PhysicsRigidBody> it = current.iterator(); it.hasNext();) {
+            PhysicsRigidBody physicsObject = it.next();
+            //copy existing spatials
+            if (oldObjects.containsKey(physicsObject)) {
+                Spatial spat = oldObjects.get(physicsObject);
+                bodies.put(physicsObject, spat);
+                oldObjects.remove(physicsObject);
+            } else {
+                logger.log(Level.FINE, "Create new debug RigidBody");
+                //create new spatial
+                Node node = new Node(physicsObject.toString());
+                node.addControl(new BulletRigidBodyDebugControl(this, physicsObject));
+                bodies.put(physicsObject, node);
+                physicsDebugRootNode.attachChild(node);
+            }
+        }
+        //remove leftover spatials
+        for (Map.Entry<PhysicsRigidBody, Spatial> entry : oldObjects.entrySet()) {
+            PhysicsRigidBody object = entry.getKey();
+            Spatial spatial = entry.getValue();
+            spatial.removeFromParent();
+        }
+    }
+
+    private void updateJoints() {
+        HashMap<PhysicsJoint, Spatial> oldObjects = joints;
+        joints = new HashMap<PhysicsJoint, Spatial>();
+        Collection<PhysicsJoint> current = space.getJointList();
+        //create new map
+        for (Iterator<PhysicsJoint> it = current.iterator(); it.hasNext();) {
+            PhysicsJoint physicsObject = it.next();
+            //copy existing spatials
+            if (oldObjects.containsKey(physicsObject)) {
+                Spatial spat = oldObjects.get(physicsObject);
+                joints.put(physicsObject, spat);
+                oldObjects.remove(physicsObject);
+            } else {
+                logger.log(Level.FINE, "Create new debug Joint");
+                //create new spatial
+                Node node = new Node(physicsObject.toString());
+                node.addControl(new BulletJointDebugControl(this, physicsObject));
+                joints.put(physicsObject, node);
+                physicsDebugRootNode.attachChild(node);
+            }
+        }
+        //remove leftover spatials
+        for (Map.Entry<PhysicsJoint, Spatial> entry : oldObjects.entrySet()) {
+            PhysicsJoint object = entry.getKey();
+            Spatial spatial = entry.getValue();
+            spatial.removeFromParent();
+        }
+    }
+
+    private void updateGhosts() {
+        HashMap<PhysicsGhostObject, Spatial> oldObjects = ghosts;
+        ghosts = new HashMap<PhysicsGhostObject, Spatial>();
+        Collection<PhysicsGhostObject> current = space.getGhostObjectList();
+        //create new map
+        for (Iterator<PhysicsGhostObject> it = current.iterator(); it.hasNext();) {
+            PhysicsGhostObject physicsObject = it.next();
+            //copy existing spatials
+            if (oldObjects.containsKey(physicsObject)) {
+                Spatial spat = oldObjects.get(physicsObject);
+                ghosts.put(physicsObject, spat);
+                oldObjects.remove(physicsObject);
+            } else {
+                logger.log(Level.FINE, "Create new debug GhostObject");
+                //create new spatial
+                Node node = new Node(physicsObject.toString());
+                node.addControl(new BulletGhostObjectDebugControl(this, physicsObject));
+                ghosts.put(physicsObject, node);
+                physicsDebugRootNode.attachChild(node);
+            }
+        }
+        //remove leftover spatials
+        for (Map.Entry<PhysicsGhostObject, Spatial> entry : oldObjects.entrySet()) {
+            PhysicsGhostObject object = entry.getKey();
+            Spatial spatial = entry.getValue();
+            spatial.removeFromParent();
+        }
+    }
+
+    private void updateCharacters() {
+        HashMap<PhysicsCharacter, Spatial> oldObjects = characters;
+        characters = new HashMap<PhysicsCharacter, Spatial>();
+        Collection<PhysicsCharacter> current = space.getCharacterList();
+        //create new map
+        for (Iterator<PhysicsCharacter> it = current.iterator(); it.hasNext();) {
+            PhysicsCharacter physicsObject = it.next();
+            //copy existing spatials
+            if (oldObjects.containsKey(physicsObject)) {
+                Spatial spat = oldObjects.get(physicsObject);
+                characters.put(physicsObject, spat);
+                oldObjects.remove(physicsObject);
+            } else {
+                logger.log(Level.FINE, "Create new debug Character");
+                //create new spatial
+                Node node = new Node(physicsObject.toString());
+                node.addControl(new BulletCharacterDebugControl(this, physicsObject));
+                characters.put(physicsObject, node);
+                physicsDebugRootNode.attachChild(node);
+            }
+        }
+        //remove leftover spatials
+        for (Map.Entry<PhysicsCharacter, Spatial> entry : oldObjects.entrySet()) {
+            PhysicsCharacter object = entry.getKey();
+            Spatial spatial = entry.getValue();
+            spatial.removeFromParent();
+        }
+    }
+
+    private void updateVehicles() {
+        HashMap<PhysicsVehicle, Spatial> oldObjects = vehicles;
+        vehicles = new HashMap<PhysicsVehicle, Spatial>();
+        Collection<PhysicsVehicle> current = space.getVehicleList();
+        //create new map
+        for (Iterator<PhysicsVehicle> it = current.iterator(); it.hasNext();) {
+            PhysicsVehicle physicsObject = it.next();
+            //copy existing spatials
+            if (oldObjects.containsKey(physicsObject)) {
+                Spatial spat = oldObjects.get(physicsObject);
+                vehicles.put(physicsObject, spat);
+                oldObjects.remove(physicsObject);
+            } else {
+                logger.log(Level.FINE, "Create new debug Vehicle");
+                //create new spatial
+                Node node = new Node(physicsObject.toString());
+                node.addControl(new BulletVehicleDebugControl(this, physicsObject));
+                vehicles.put(physicsObject, node);
+                physicsDebugRootNode.attachChild(node);
+            }
+        }
+        //remove leftover spatials
+        for (Map.Entry<PhysicsVehicle, Spatial> entry : oldObjects.entrySet()) {
+            PhysicsVehicle object = entry.getKey();
+            Spatial spatial = entry.getValue();
+            spatial.removeFromParent();
+        }
+    }
+
+    public ArrowBuffer getArrowBuffer() {
+        return arrowBuffer;
+    }
+
+    public CollisionShapeBuffer getShapeBuffer() {
+        return shapeBuffer;
+    }
+
+    public static class CollisionShapeBuffer {
+
+        public static final Mesh NO_MESH = new Arrow(new Vector3f(0, 0, 0));
+        private HashMap<CollisionShape, Mesh> shapes = new HashMap<CollisionShape, Mesh>();
+        private HashMap<CollisionShape, Mesh> usedShapes = new HashMap<CollisionShape, Mesh>();
+
+        public void resetShapes() {
+            shapes = usedShapes;
+            usedShapes = new HashMap<CollisionShape, Mesh>();
+        }
+
+        public Mesh getShapeMesh(CollisionShape shape) {
+            if (shape == null) {
+                return null;
+            }
+            Mesh mesh = shapes.get(shape);
+            if (mesh == null) {
+                logger.log(Level.FINE, "Create new debug MESH");
+                mesh = DebugShapeFactory.getDebugMesh(shape);
+                shapes.put(shape, mesh);
+            }
+            usedShapes.put(shape, mesh);
+            return mesh;
+        }
+    }
+
+    public static class ArrowBuffer {
+
+        private final Queue<Arrow> arrows = new ConcurrentLinkedQueue<Arrow>();
+        private final Queue<Arrow> usedArrows = new ConcurrentLinkedQueue<Arrow>();
+
+        public void resetArrows() {
+            arrows.addAll(usedArrows);
+        }
+
+        public Arrow getArrow() {
+            return getArrow(Vector3f.UNIT_Y);
+        }
+
+        public Arrow getArrow(Vector3f extent) {
+            Arrow arrow = arrows.poll();
+            if (arrow == null) {
+                arrow = new Arrow(extent);
+            } else {
+                arrow.setArrowExtent(extent);
+            }
+            usedArrows.add(arrow);
+            return arrow;
+        }
+    }
+}

+ 91 - 0
engine/src/bullet-common/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java

@@ -0,0 +1,91 @@
+/*
+ * 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.bullet.debug;
+
+import com.jme3.bullet.objects.PhysicsGhostObject;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class BulletGhostObjectDebugControl extends AbstractPhysicsDebugControl{
+    protected final PhysicsGhostObject body;
+    protected final Geometry geom;
+    protected final Vector3f location = new Vector3f();
+    protected final Quaternion rotation = new Quaternion();
+
+    public BulletGhostObjectDebugControl(BulletDebugAppState debugAppState, PhysicsGhostObject body) {
+        super(debugAppState);
+        this.body = body;
+        this.geom = new Geometry(body.toString());
+        geom.setMaterial(debugAppState.DEBUG_YELLOW);
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial) {
+        if (spatial != null && spatial instanceof Node) {
+            Node node = (Node) spatial;
+            node.attachChild(geom);
+        } else if (spatial == null && this.spatial != null) {
+            Node node = (Node) this.spatial;
+            node.detachChild(geom);
+        }
+        super.setSpatial(spatial);
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        Mesh mesh = debugAppState.getShapeBuffer().getShapeMesh(body.getCollisionShape());
+        if (mesh != null) {
+            if (geom.getMesh() != mesh) {
+                geom.setMesh(mesh);
+            }
+        } else {
+            if (geom.getMesh() != BulletDebugAppState.CollisionShapeBuffer.NO_MESH) {
+                geom.setMesh(BulletDebugAppState.CollisionShapeBuffer.NO_MESH);
+            }
+        }
+        applyPhysicsTransform(body.getPhysicsLocation(location), Quaternion.IDENTITY);
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+    }
+}

+ 106 - 0
engine/src/bullet-common/com/jme3/bullet/debug/BulletJointDebugControl.java

@@ -0,0 +1,106 @@
+/*
+ * 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.bullet.debug;
+
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class BulletJointDebugControl extends AbstractPhysicsDebugControl {
+
+    protected final PhysicsJoint body;
+    protected final Geometry geomA;
+    protected final Arrow arrowA;
+    protected final Geometry geomB;
+    protected final Arrow arrowB;
+    protected final Transform a = new Transform(new Vector3f(), new Quaternion());
+    protected final Transform b = new Transform(new Vector3f(), new Quaternion());
+    protected final Vector3f offA = new Vector3f();
+    protected final Vector3f offB = new Vector3f();
+
+    public BulletJointDebugControl(BulletDebugAppState debugAppState, PhysicsJoint body) {
+        super(debugAppState);
+        this.body = body;
+        this.geomA = new Geometry(body.toString());
+        arrowA = new Arrow(Vector3f.ZERO);
+        geomA.setMesh(arrowA);
+        geomA.setMaterial(debugAppState.DEBUG_GREEN);
+        this.geomB = new Geometry(body.toString());
+        arrowB = new Arrow(Vector3f.ZERO);
+        geomB.setMesh(arrowB);
+        geomB.setMaterial(debugAppState.DEBUG_GREEN);
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial) {
+        if (spatial != null && spatial instanceof Node) {
+            Node node = (Node) spatial;
+            node.attachChild(geomA);
+            node.attachChild(geomB);
+        } else if (spatial == null && this.spatial != null) {
+            Node node = (Node) this.spatial;
+            node.detachChild(geomA);
+            node.detachChild(geomB);
+        }
+        super.setSpatial(spatial);
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        body.getBodyA().getPhysicsLocation(a.getTranslation());
+        body.getBodyA().getPhysicsRotation(a.getRotation());
+
+        body.getBodyB().getPhysicsLocation(b.getTranslation());
+        body.getBodyB().getPhysicsRotation(b.getRotation());
+
+        geomA.setLocalTransform(a);
+        geomB.setLocalTransform(b);
+
+        arrowA.setArrowExtent(body.getPivotA());
+        arrowB.setArrowExtent(body.getPivotB());
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+    }
+}

+ 95 - 0
engine/src/bullet-common/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java

@@ -0,0 +1,95 @@
+/*
+ * 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.bullet.debug;
+
+import com.jme3.bullet.debug.BulletDebugAppState.CollisionShapeBuffer;
+import com.jme3.bullet.objects.PhysicsRigidBody;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class BulletRigidBodyDebugControl extends AbstractPhysicsDebugControl {
+
+    private static final Logger logger = Logger.getLogger(BulletRigidBodyDebugControl.class.getName());
+    protected final PhysicsRigidBody body;
+    protected final Geometry geom;
+    protected final Vector3f location = new Vector3f();
+    protected final Quaternion rotation = new Quaternion();
+
+    public BulletRigidBodyDebugControl(BulletDebugAppState debugAppState, PhysicsRigidBody body) {
+        super(debugAppState);
+        this.body = body;
+        this.geom = new Geometry(body.toString());
+        geom.setMaterial(debugAppState.DEBUG_BLUE);
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial) {
+        if (spatial != null && spatial instanceof Node) {
+            Node node = (Node) spatial;
+            node.attachChild(geom);
+        } else if (spatial == null && this.spatial != null) {
+            Node node = (Node) this.spatial;
+            node.detachChild(geom);
+        }
+        super.setSpatial(spatial);
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        Mesh mesh = debugAppState.getShapeBuffer().getShapeMesh(body.getCollisionShape());
+        if (mesh != null) {
+            if (geom.getMesh() != mesh) {
+                geom.setMesh(mesh);
+            }
+        } else {
+            if (geom.getMesh() != CollisionShapeBuffer.NO_MESH) {
+                geom.setMesh(CollisionShapeBuffer.NO_MESH);
+            }
+        }
+        applyPhysicsTransform(body.getPhysicsLocation(location), body.getPhysicsRotation(rotation));
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+    }
+}

+ 142 - 0
engine/src/bullet-common/com/jme3/bullet/debug/BulletVehicleDebugControl.java

@@ -0,0 +1,142 @@
+/*
+ * 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.bullet.debug;
+
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.bullet.objects.VehicleWheel;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class BulletVehicleDebugControl extends AbstractPhysicsDebugControl {
+
+    protected final PhysicsVehicle body;
+    protected final Node suspensionNode;
+    protected final Vector3f location = new Vector3f();
+    protected final Quaternion rotation = new Quaternion();
+
+    public BulletVehicleDebugControl(BulletDebugAppState debugAppState, PhysicsVehicle body) {
+        super(debugAppState);
+        this.body = body;
+        suspensionNode = new Node("Suspension");
+        createVehicle();
+    }
+
+    @Override
+    public void setSpatial(Spatial spatial) {
+        if (spatial != null && spatial instanceof Node) {
+            Node node = (Node) spatial;
+            node.attachChild(suspensionNode);
+        } else if (spatial == null && this.spatial != null) {
+            Node node = (Node) this.spatial;
+            node.detachChild(suspensionNode);
+        }
+        super.setSpatial(spatial);
+    }
+
+    private void createVehicle() {
+        suspensionNode.detachAllChildren();
+        for (int i = 0; i < body.getNumWheels(); i++) {
+            VehicleWheel physicsVehicleWheel = body.getWheel(i);
+            Vector3f location = physicsVehicleWheel.getLocation().clone();
+            Vector3f direction = physicsVehicleWheel.getDirection().clone();
+            Vector3f axle = physicsVehicleWheel.getAxle().clone();
+            float restLength = physicsVehicleWheel.getRestLength();
+            float radius = physicsVehicleWheel.getRadius();
+
+            Arrow locArrow = new Arrow(location);
+            Arrow axleArrow = new Arrow(axle.normalizeLocal().multLocal(0.3f));
+            Arrow wheelArrow = new Arrow(direction.normalizeLocal().multLocal(radius));
+            Arrow dirArrow = new Arrow(direction.normalizeLocal().multLocal(restLength));
+            Geometry locGeom = new Geometry("WheelLocationDebugShape" + i, locArrow);
+            Geometry dirGeom = new Geometry("WheelDirectionDebugShape" + i, dirArrow);
+            Geometry axleGeom = new Geometry("WheelAxleDebugShape" + i, axleArrow);
+            Geometry wheelGeom = new Geometry("WheelRadiusDebugShape" + i, wheelArrow);
+            dirGeom.setLocalTranslation(location);
+            axleGeom.setLocalTranslation(location.add(direction));
+            wheelGeom.setLocalTranslation(location.add(direction));
+            locGeom.setMaterial(debugAppState.DEBUG_GREEN);
+            dirGeom.setMaterial(debugAppState.DEBUG_GREEN);
+            axleGeom.setMaterial(debugAppState.DEBUG_GREEN);
+            wheelGeom.setMaterial(debugAppState.DEBUG_GREEN);
+            suspensionNode.attachChild(locGeom);
+            suspensionNode.attachChild(dirGeom);
+            suspensionNode.attachChild(axleGeom);
+            suspensionNode.attachChild(wheelGeom);
+        }
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        for (int i = 0; i < body.getNumWheels(); i++) {
+            VehicleWheel physicsVehicleWheel = body.getWheel(i);
+            Vector3f location = physicsVehicleWheel.getLocation().clone();
+            Vector3f direction = physicsVehicleWheel.getDirection().clone();
+            Vector3f axle = physicsVehicleWheel.getAxle().clone();
+            float restLength = physicsVehicleWheel.getRestLength();
+            float radius = physicsVehicleWheel.getRadius();
+
+            Geometry locGeom = (Geometry) suspensionNode.getChild("WheelLocationDebugShape" + i);
+            Geometry dirGeom = (Geometry) suspensionNode.getChild("WheelDirectionDebugShape" + i);
+            Geometry axleGeom = (Geometry) suspensionNode.getChild("WheelAxleDebugShape" + i);
+            Geometry wheelGeom = (Geometry) suspensionNode.getChild("WheelRadiusDebugShape" + i);
+
+            Arrow locArrow = (Arrow) locGeom.getMesh();
+            locArrow.setArrowExtent(location);
+            Arrow axleArrow = (Arrow) axleGeom.getMesh();
+            axleArrow.setArrowExtent(axle.normalizeLocal().multLocal(0.3f));
+            Arrow wheelArrow = (Arrow) wheelGeom.getMesh();
+            wheelArrow.setArrowExtent(direction.normalizeLocal().multLocal(radius));
+            Arrow dirArrow = (Arrow) dirGeom.getMesh();
+            dirArrow.setArrowExtent(direction.normalizeLocal().multLocal(restLength));
+
+            dirGeom.setLocalTranslation(location);
+            axleGeom.setLocalTranslation(location.addLocal(direction));
+            wheelGeom.setLocalTranslation(location);
+            i++;
+        }
+        applyPhysicsTransform(body.getPhysicsLocation(location), body.getPhysicsRotation(rotation));
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+    }
+}

+ 1 - 1
engine/src/bullet/com/jme3/bullet/PhysicsSpace.java

@@ -920,7 +920,7 @@ public class PhysicsSpace {
     /**
      * Enable debug display for physics
      * @param manager AssetManager to use to create debug materials
-     * @Deprecated in favor of BulletDebugAppState, use BulletAppState.enableDebug() to add automatically
+     * @Deprecated in favor of BulletDebugAppState, use BulletAppState.setDebugEnabled(boolean) to add automatically
      */
     @Deprecated
     public void enableDebug(AssetManager manager) {

+ 1 - 1
engine/src/jbullet/com/jme3/bullet/PhysicsSpace.java

@@ -859,7 +859,7 @@ public class PhysicsSpace {
     /**
      * Enable debug display for physics
      * @param manager AssetManager to use to create debug materials
-     * @Deprecated in favor of BulletDebugAppState, use BulletAppState.enableDebug() to add automatically
+     * @Deprecated in favor of BulletDebugAppState, use BulletAppState.setDebugEnabled(boolean) to add automatically
      */
     @Deprecated
     public void enableDebug(AssetManager manager) {