|
@@ -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;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|