aboutsummaryrefslogtreecommitdiff
path: root/engine/src/bullet/com/jme3/bullet/objects
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/bullet/com/jme3/bullet/objects')
-rw-r--r--engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java319
-rw-r--r--engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java301
-rw-r--r--engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java752
-rw-r--r--engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java584
-rw-r--r--engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java423
-rw-r--r--engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java163
-rw-r--r--engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java45
7 files changed, 2587 insertions, 0 deletions
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java
new file mode 100644
index 0000000..fb98fec
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsCharacter.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (c) 2009-2010 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.objects;
+
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Basic Bullet Character
+ * @author normenhansen
+ */
+public class PhysicsCharacter extends PhysicsCollisionObject {
+
+ protected long characterId = 0;
+ protected float stepHeight;
+ protected Vector3f walkDirection = new Vector3f();
+ protected float fallSpeed = 55.0f;
+ protected float jumpSpeed = 10.0f;
+ protected int upAxis = 1;
+ protected boolean locationDirty = false;
+ //TEMP VARIABLES
+ protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+
+ public PhysicsCharacter() {
+ }
+
+ /**
+ * @param shape The CollisionShape (no Mesh or CompoundCollisionShapes)
+ * @param stepHeight The quantization size for vertical movement
+ */
+ public PhysicsCharacter(CollisionShape shape, float stepHeight) {
+ this.collisionShape = shape;
+// if (shape instanceof MeshCollisionShape || shape instanceof CompoundCollisionShape) {
+// throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh or compound collision shapes"));
+// }
+ this.stepHeight = stepHeight;
+ buildObject();
+ }
+
+ protected void buildObject() {
+ if (objectId == 0) {
+ objectId = createGhostObject();
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Creating GhostObject {0}", Long.toHexString(objectId));
+ initUserPointer();
+ }
+ setCharacterFlags(objectId);
+ attachCollisionShape(objectId, collisionShape.getObjectId());
+ if (characterId != 0) {
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing Character {0}", Long.toHexString(objectId));
+ finalizeNativeCharacter(characterId);
+ }
+ characterId = createCharacterObject(objectId, collisionShape.getObjectId(), stepHeight);
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Creating Character {0}", Long.toHexString(characterId));
+ }
+
+ private native long createGhostObject();
+
+ private native void setCharacterFlags(long objectId);
+
+ private native long createCharacterObject(long objectId, long shapeId, float stepHeight);
+
+ /**
+ * Sets the location of this physics character
+ * @param location
+ */
+ public void warp(Vector3f location) {
+ warp(characterId, location);
+ }
+
+ private native void warp(long characterId, Vector3f location);
+
+ /**
+ * Set the walk direction, works continuously.
+ * This should probably be called setPositionIncrementPerSimulatorStep.
+ * This is neither a direction nor a velocity, but the amount to
+ * increment the position each physics tick. So vector length = accuracy*speed in m/s
+ * @param vec the walk direction to set
+ */
+ public void setWalkDirection(Vector3f vec) {
+ walkDirection.set(vec);
+ setWalkDirection(characterId, vec);
+ }
+
+ private native void setWalkDirection(long characterId, Vector3f vec);
+
+ /**
+ * @return the currently set walkDirection
+ */
+ public Vector3f getWalkDirection() {
+ return walkDirection;
+ }
+
+ public void setUpAxis(int axis) {
+ upAxis = axis;
+ setUpAxis(characterId, axis);
+ }
+
+ private native void setUpAxis(long characterId, int axis);
+
+ public int getUpAxis() {
+ return upAxis;
+ }
+
+ public void setFallSpeed(float fallSpeed) {
+ this.fallSpeed = fallSpeed;
+ setFallSpeed(characterId, fallSpeed);
+ }
+
+ private native void setFallSpeed(long characterId, float fallSpeed);
+
+ public float getFallSpeed() {
+ return fallSpeed;
+ }
+
+ public void setJumpSpeed(float jumpSpeed) {
+ this.jumpSpeed = jumpSpeed;
+ setJumpSpeed(characterId, jumpSpeed);
+ }
+
+ private native void setJumpSpeed(long characterId, float jumpSpeed);
+
+ public float getJumpSpeed() {
+ return jumpSpeed;
+ }
+
+ public void setGravity(float value) {
+ setGravity(characterId, value);
+ }
+
+ private native void setGravity(long characterId, float gravity);
+
+ public float getGravity() {
+ return getGravity(characterId);
+ }
+
+ private native float getGravity(long characterId);
+
+ public void setMaxSlope(float slopeRadians) {
+ setMaxSlope(characterId, slopeRadians);
+ }
+
+ private native void setMaxSlope(long characterId, float slopeRadians);
+
+ public float getMaxSlope() {
+ return getMaxSlope(characterId);
+ }
+
+ private native float getMaxSlope(long characterId);
+
+ public boolean onGround() {
+ return onGround(characterId);
+ }
+
+ private native boolean onGround(long characterId);
+
+ public void jump() {
+ jump(characterId);
+ }
+
+ private native void jump(long characterId);
+
+ @Override
+ public void setCollisionShape(CollisionShape collisionShape) {
+// if (!(collisionShape.getObjectId() instanceof ConvexShape)) {
+// throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes"));
+// }
+ super.setCollisionShape(collisionShape);
+ if (objectId == 0) {
+ buildObject();
+ } else {
+ attachCollisionShape(objectId, collisionShape.getObjectId());
+ }
+ }
+
+ /**
+ * Set the physics location (same as warp())
+ * @param location the location of the actual physics object
+ */
+ public void setPhysicsLocation(Vector3f location) {
+ warp(location);
+ }
+
+ /**
+ * @return the physicsLocation
+ */
+ public Vector3f getPhysicsLocation(Vector3f trans) {
+ if (trans == null) {
+ trans = new Vector3f();
+ }
+ getPhysicsLocation(objectId, trans);
+ return trans;
+ }
+
+ private native void getPhysicsLocation(long objectId, Vector3f vec);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Vector3f getPhysicsLocation() {
+ return getPhysicsLocation(null);
+ }
+
+ public void setCcdSweptSphereRadius(float radius) {
+ setCcdSweptSphereRadius(objectId, radius);
+ }
+
+ private native void setCcdSweptSphereRadius(long objectId, float radius);
+
+ public void setCcdMotionThreshold(float threshold) {
+ setCcdMotionThreshold(objectId, threshold);
+ }
+
+ private native void setCcdMotionThreshold(long objectId, float threshold);
+
+ public float getCcdSweptSphereRadius() {
+ return getCcdSweptSphereRadius(objectId);
+ }
+
+ private native float getCcdSweptSphereRadius(long objectId);
+
+ public float getCcdMotionThreshold() {
+ return getCcdMotionThreshold(objectId);
+ }
+
+ private native float getCcdMotionThreshold(long objectId);
+
+ public float getCcdSquareMotionThreshold() {
+ return getCcdSquareMotionThreshold(objectId);
+ }
+
+ private native float getCcdSquareMotionThreshold(long objectId);
+
+ /**
+ * used internally
+ */
+ public long getControllerId() {
+ return characterId;
+ }
+
+ public void destroy() {
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(stepHeight, "stepHeight", 1.0f);
+ capsule.write(getGravity(), "gravity", 9.8f * 3);
+ capsule.write(getMaxSlope(), "maxSlope", 1.0f);
+ capsule.write(fallSpeed, "fallSpeed", 55.0f);
+ capsule.write(jumpSpeed, "jumpSpeed", 10.0f);
+ capsule.write(upAxis, "upAxis", 1);
+ capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+ capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+ capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ stepHeight = capsule.readFloat("stepHeight", 1.0f);
+ buildObject();
+ setGravity(capsule.readFloat("gravity", 9.8f * 3));
+ setMaxSlope(capsule.readFloat("maxSlope", 1.0f));
+ setFallSpeed(capsule.readFloat("fallSpeed", 55.0f));
+ setJumpSpeed(capsule.readFloat("jumpSpeed", 10.0f));
+ setUpAxis(capsule.readInt("upAxis", 1));
+ setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+ setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+ setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ finalizeNativeCharacter(characterId);
+ }
+
+ private native void finalizeNativeCharacter(long characterId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java
new file mode 100644
index 0000000..2f87164
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsGhostObject.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2009-2010 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.objects;
+
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <i>From Bullet manual:</i><br>
+ * GhostObject can keep track of all objects that are overlapping.
+ * By default, this overlap is based on the AABB.
+ * This is useful for creating a character controller,
+ * collision sensors/triggers, explosions etc.<br>
+ * @author normenhansen
+ */
+public class PhysicsGhostObject extends PhysicsCollisionObject {
+
+ protected boolean locationDirty = false;
+ protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+ private List<PhysicsCollisionObject> overlappingObjects = new LinkedList<PhysicsCollisionObject>();
+
+ public PhysicsGhostObject() {
+ }
+
+ public PhysicsGhostObject(CollisionShape shape) {
+ collisionShape = shape;
+ buildObject();
+ }
+
+ public PhysicsGhostObject(Spatial child, CollisionShape shape) {
+ collisionShape = shape;
+ buildObject();
+ }
+
+ protected void buildObject() {
+ if (objectId == 0) {
+// gObject = new PairCachingGhostObject();
+ objectId = createGhostObject();
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Ghost Object {0}", Long.toHexString(objectId));
+ setGhostFlags(objectId);
+ initUserPointer();
+ }
+// if (gObject == null) {
+// gObject = new PairCachingGhostObject();
+// gObject.setCollisionFlags(gObject.getCollisionFlags() | CollisionFlags.NO_CONTACT_RESPONSE);
+// }
+ attachCollisionShape(objectId, collisionShape.getObjectId());
+ }
+
+ private native long createGhostObject();
+
+ private native void setGhostFlags(long objectId);
+
+ @Override
+ public void setCollisionShape(CollisionShape collisionShape) {
+ super.setCollisionShape(collisionShape);
+ if (objectId == 0) {
+ buildObject();
+ } else {
+ attachCollisionShape(objectId, collisionShape.getObjectId());
+ }
+ }
+
+ /**
+ * Sets the physics object location
+ * @param location the location of the actual physics object
+ */
+ public void setPhysicsLocation(Vector3f location) {
+ setPhysicsLocation(objectId, location);
+ }
+
+ private native void setPhysicsLocation(long objectId, Vector3f location);
+
+ /**
+ * Sets the physics object rotation
+ * @param rotation the rotation of the actual physics object
+ */
+ public void setPhysicsRotation(Matrix3f rotation) {
+ setPhysicsRotation(objectId, rotation);
+ }
+
+ private native void setPhysicsRotation(long objectId, Matrix3f rotation);
+
+ /**
+ * Sets the physics object rotation
+ * @param rotation the rotation of the actual physics object
+ */
+ public void setPhysicsRotation(Quaternion rotation) {
+ setPhysicsRotation(objectId, rotation);
+ }
+
+ private native void setPhysicsRotation(long objectId, Quaternion rotation);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Vector3f getPhysicsLocation(Vector3f trans) {
+ if (trans == null) {
+ trans = new Vector3f();
+ }
+ getPhysicsLocation(objectId, trans);
+ return trans;
+ }
+
+ private native void getPhysicsLocation(long objectId, Vector3f vector);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Quaternion getPhysicsRotation(Quaternion rot) {
+ if (rot == null) {
+ rot = new Quaternion();
+ }
+ getPhysicsRotation(objectId, rot);
+ return rot;
+ }
+
+ private native void getPhysicsRotation(long objectId, Quaternion rot);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) {
+ if (rot == null) {
+ rot = new Matrix3f();
+ }
+ getPhysicsRotationMatrix(objectId, rot);
+ return rot;
+ }
+
+ private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Vector3f getPhysicsLocation() {
+ Vector3f vec = new Vector3f();
+ getPhysicsLocation(objectId, vec);
+ return vec;
+ }
+
+ /**
+ * @return the physicsLocation
+ */
+ public Quaternion getPhysicsRotation() {
+ Quaternion quat = new Quaternion();
+ getPhysicsRotation(objectId, quat);
+ return quat;
+ }
+
+ public Matrix3f getPhysicsRotationMatrix() {
+ Matrix3f mtx = new Matrix3f();
+ getPhysicsRotationMatrix(objectId, mtx);
+ return mtx;
+ }
+
+ /**
+ * used internally
+ */
+// public PairCachingGhostObject getObjectId() {
+// return gObject;
+// }
+ /**
+ * destroys this PhysicsGhostNode and removes it from memory
+ */
+ public void destroy() {
+ }
+
+ /**
+ * Another Object is overlapping with this GhostNode,
+ * if and if only there CollisionShapes overlaps.
+ * They could be both regular PhysicsRigidBodys or PhysicsGhostObjects.
+ * @return All CollisionObjects overlapping with this GhostNode.
+ */
+ public List<PhysicsCollisionObject> getOverlappingObjects() {
+ overlappingObjects.clear();
+ getOverlappingObjects(objectId);
+// for (com.bulletphysics.collision.dispatch.CollisionObject collObj : gObject.getOverlappingPairs()) {
+// overlappingObjects.add((PhysicsCollisionObject) collObj.getUserPointer());
+// }
+ return overlappingObjects;
+ }
+
+ protected native void getOverlappingObjects(long objectId);
+
+ private void addOverlappingObject_native(PhysicsCollisionObject co) {
+ overlappingObjects.add(co);
+ }
+
+ /**
+ *
+ * @return With how many other CollisionObjects this GhostNode is currently overlapping.
+ */
+ public int getOverlappingCount() {
+ return getOverlappingCount(objectId);
+ }
+
+ private native int getOverlappingCount(long objectId);
+
+ /**
+ *
+ * @param index The index of the overlapping Node to retrieve.
+ * @return The Overlapping CollisionObject at the given index.
+ */
+ public PhysicsCollisionObject getOverlapping(int index) {
+ return overlappingObjects.get(index);
+ }
+
+ public void setCcdSweptSphereRadius(float radius) {
+ setCcdSweptSphereRadius(objectId, radius);
+ }
+
+ private native void setCcdSweptSphereRadius(long objectId, float radius);
+
+ public void setCcdMotionThreshold(float threshold) {
+ setCcdMotionThreshold(objectId, threshold);
+ }
+
+ private native void setCcdMotionThreshold(long objectId, float threshold);
+
+ public float getCcdSweptSphereRadius() {
+ return getCcdSweptSphereRadius(objectId);
+ }
+
+ private native float getCcdSweptSphereRadius(long objectId);
+
+ public float getCcdMotionThreshold() {
+ return getCcdMotionThreshold(objectId);
+ }
+
+ private native float getCcdMotionThreshold(long objectId);
+
+ public float getCcdSquareMotionThreshold() {
+ return getCcdSquareMotionThreshold(objectId);
+ }
+
+ private native float getCcdSquareMotionThreshold(long objectId);
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+ capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+ capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f());
+ capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+ capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+ InputCapsule capsule = e.getCapsule(this);
+ buildObject();
+ setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+ setPhysicsRotation(((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f())));
+ setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+ setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+ }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java
new file mode 100644
index 0000000..0d41798
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsRigidBody.java
@@ -0,0 +1,752 @@
+/*
+ * Copyright (c) 2009-2010 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.objects;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.collision.shapes.MeshCollisionShape;
+import com.jme3.bullet.joints.PhysicsJoint;
+import com.jme3.bullet.objects.infos.RigidBodyMotionState;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>PhysicsRigidBody - Basic physics object</p>
+ * @author normenhansen
+ */
+public class PhysicsRigidBody extends PhysicsCollisionObject {
+
+ protected RigidBodyMotionState motionState = new RigidBodyMotionState();
+ protected float mass = 1.0f;
+ protected boolean kinematic = false;
+ protected ArrayList<PhysicsJoint> joints = new ArrayList<PhysicsJoint>();
+
+ public PhysicsRigidBody() {
+ }
+
+ /**
+ * Creates a new PhysicsRigidBody with the supplied collision shape
+ * @param child
+ * @param shape
+ */
+ public PhysicsRigidBody(CollisionShape shape) {
+ collisionShape = shape;
+ rebuildRigidBody();
+ }
+
+ public PhysicsRigidBody(CollisionShape shape, float mass) {
+ collisionShape = shape;
+ this.mass = mass;
+ rebuildRigidBody();
+ }
+
+ /**
+ * Builds/rebuilds the phyiscs body when parameters have changed
+ */
+ protected void rebuildRigidBody() {
+ boolean removed = false;
+ if (collisionShape instanceof MeshCollisionShape && mass != 0) {
+ throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+ }
+ if (objectId != 0) {
+ if (isInWorld(objectId)) {
+ PhysicsSpace.getPhysicsSpace().remove(this);
+ removed = true;
+ }
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing RigidBody {0}", Long.toHexString(objectId));
+ finalizeNative(objectId);
+ }
+ preRebuild();
+ objectId = createRigidBody(mass, motionState.getObjectId(), collisionShape.getObjectId());
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created RigidBody {0}", Long.toHexString(objectId));
+ postRebuild();
+ if (removed) {
+ PhysicsSpace.getPhysicsSpace().add(this);
+ }
+ }
+
+ protected void preRebuild() {
+ }
+
+ private native long createRigidBody(float mass, long motionStateId, long collisionShapeId);
+
+ protected void postRebuild() {
+ if (mass == 0.0f) {
+ setStatic(objectId, true);
+ } else {
+ setStatic(objectId, false);
+ }
+ initUserPointer();
+ }
+
+ /**
+ * @return the motionState
+ */
+ public RigidBodyMotionState getMotionState() {
+ return motionState;
+ }
+
+ public boolean isInWorld() {
+ return isInWorld(objectId);
+ }
+
+ private native boolean isInWorld(long objectId);
+
+ /**
+ * Sets the physics object location
+ * @param location the location of the actual physics object
+ */
+ public void setPhysicsLocation(Vector3f location) {
+ setPhysicsLocation(objectId, location);
+ }
+
+ private native void setPhysicsLocation(long objectId, Vector3f location);
+
+ /**
+ * Sets the physics object rotation
+ * @param rotation the rotation of the actual physics object
+ */
+ public void setPhysicsRotation(Matrix3f rotation) {
+ setPhysicsRotation(objectId, rotation);
+ }
+
+ private native void setPhysicsRotation(long objectId, Matrix3f rotation);
+
+ /**
+ * Sets the physics object rotation
+ * @param rotation the rotation of the actual physics object
+ */
+ public void setPhysicsRotation(Quaternion rotation) {
+ setPhysicsRotation(objectId, rotation);
+ }
+
+ private native void setPhysicsRotation(long objectId, Quaternion rotation);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Vector3f getPhysicsLocation(Vector3f trans) {
+ if (trans == null) {
+ trans = new Vector3f();
+ }
+ getPhysicsLocation(objectId, trans);
+ return trans;
+ }
+
+ private native void getPhysicsLocation(long objectId, Vector3f vector);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Quaternion getPhysicsRotation(Quaternion rot) {
+ if (rot == null) {
+ rot = new Quaternion();
+ }
+ getPhysicsRotation(objectId, rot);
+ return rot;
+ }
+
+ private native void getPhysicsRotation(long objectId, Quaternion rot);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) {
+ if (rot == null) {
+ rot = new Matrix3f();
+ }
+ getPhysicsRotationMatrix(objectId, rot);
+ return rot;
+ }
+
+ private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot);
+
+ /**
+ * @return the physicsLocation
+ */
+ public Vector3f getPhysicsLocation() {
+ Vector3f vec = new Vector3f();
+ getPhysicsLocation(objectId, vec);
+ return vec;
+ }
+
+ /**
+ * @return the physicsLocation
+ */
+ public Quaternion getPhysicsRotation() {
+ Quaternion quat = new Quaternion();
+ getPhysicsRotation(objectId, quat);
+ return quat;
+ }
+
+ public Matrix3f getPhysicsRotationMatrix() {
+ Matrix3f mtx = new Matrix3f();
+ getPhysicsRotationMatrix(objectId, mtx);
+ return mtx;
+ }
+
+// /**
+// * Gets the physics object location
+// * @param location the location of the actual physics object is stored in this Vector3f
+// */
+// public Vector3f getInterpolatedPhysicsLocation(Vector3f location) {
+// if (location == null) {
+// location = new Vector3f();
+// }
+// rBody.getInterpolationWorldTransform(tempTrans);
+// return Converter.convert(tempTrans.origin, location);
+// }
+//
+// /**
+// * Gets the physics object rotation
+// * @param rotation the rotation of the actual physics object is stored in this Matrix3f
+// */
+// public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) {
+// if (rotation == null) {
+// rotation = new Matrix3f();
+// }
+// rBody.getInterpolationWorldTransform(tempTrans);
+// return Converter.convert(tempTrans.basis, rotation);
+// }
+ /**
+ * Sets the node to kinematic mode. in this mode the node is not affected by physics
+ * but affects other physics objects. Iits kinetic force is calculated by the amount
+ * of movement it is exposed to and its weight.
+ * @param kinematic
+ */
+ public void setKinematic(boolean kinematic) {
+ this.kinematic = kinematic;
+ setKinematic(objectId, kinematic);
+ }
+
+ private native void setKinematic(long objectId, boolean kinematic);
+
+ public boolean isKinematic() {
+ return kinematic;
+ }
+
+ public void setCcdSweptSphereRadius(float radius) {
+ setCcdSweptSphereRadius(objectId, radius);
+ }
+
+ private native void setCcdSweptSphereRadius(long objectId, float radius);
+
+ /**
+ * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection<br/>
+ * This avoids the problem of fast objects moving through other objects, set to zero to disable (default)
+ * @param threshold
+ */
+ public void setCcdMotionThreshold(float threshold) {
+ setCcdMotionThreshold(objectId, threshold);
+ }
+
+ private native void setCcdMotionThreshold(long objectId, float threshold);
+
+ public float getCcdSweptSphereRadius() {
+ return getCcdSweptSphereRadius(objectId);
+ }
+
+ private native float getCcdSweptSphereRadius(long objectId);
+
+ public float getCcdMotionThreshold() {
+ return getCcdMotionThreshold(objectId);
+ }
+
+ private native float getCcdMotionThreshold(long objectId);
+
+ public float getCcdSquareMotionThreshold() {
+ return getCcdSquareMotionThreshold(objectId);
+ }
+
+ private native float getCcdSquareMotionThreshold(long objectId);
+
+ public float getMass() {
+ return mass;
+ }
+
+ /**
+ * Sets the mass of this PhysicsRigidBody, objects with mass=0 are static.
+ * @param mass
+ */
+ public void setMass(float mass) {
+ this.mass = mass;
+ if (collisionShape instanceof MeshCollisionShape && mass != 0) {
+ throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+ }
+ if (objectId != 0) {
+ if (collisionShape != null) {
+ updateMassProps(objectId, collisionShape.getObjectId(), mass);
+ }
+ if (mass == 0.0f) {
+ setStatic(objectId, true);
+ } else {
+ setStatic(objectId, false);
+ }
+ }
+ }
+
+ private native void setStatic(long objectId, boolean state);
+
+ private native long updateMassProps(long objectId, long collisionShapeId, float mass);
+
+ public Vector3f getGravity() {
+ return getGravity(null);
+ }
+
+ public Vector3f getGravity(Vector3f gravity) {
+ if (gravity == null) {
+ gravity = new Vector3f();
+ }
+ getGravity(objectId, gravity);
+ return gravity;
+ }
+
+ private native void getGravity(long objectId, Vector3f gravity);
+
+ /**
+ * Set the local gravity of this PhysicsRigidBody<br/>
+ * Set this after adding the node to the PhysicsSpace,
+ * the PhysicsSpace assigns its current gravity to the physics node when its added.
+ * @param gravity the gravity vector to set
+ */
+ public void setGravity(Vector3f gravity) {
+ setGravity(objectId, gravity);
+ }
+
+ private native void setGravity(long objectId, Vector3f gravity);
+
+ public float getFriction() {
+ return getFriction(objectId);
+ }
+
+ private native float getFriction(long objectId);
+
+ /**
+ * Sets the friction of this physics object
+ * @param friction the friction of this physics object
+ */
+ public void setFriction(float friction) {
+ setFriction(objectId, friction);
+ }
+
+ private native void setFriction(long objectId, float friction);
+
+ public void setDamping(float linearDamping, float angularDamping) {
+ setDamping(objectId, linearDamping, angularDamping);
+ }
+
+ private native void setDamping(long objectId, float linearDamping, float angularDamping);
+
+// private native void setRestitution(long objectId, float factor);
+//
+// public void setLinearDamping(float linearDamping) {
+// constructionInfo.linearDamping = linearDamping;
+// rBody.setDamping(linearDamping, constructionInfo.angularDamping);
+// }
+//
+// private native void setRestitution(long objectId, float factor);
+//
+ public void setLinearDamping(float linearDamping) {
+ setDamping(objectId, linearDamping, getAngularDamping());
+ }
+
+ public void setAngularDamping(float angularDamping) {
+ setAngularDamping(objectId, angularDamping);
+ }
+ private native void setAngularDamping(long objectId, float factor);
+
+ public float getLinearDamping() {
+ return getLinearDamping(objectId);
+ }
+
+ private native float getLinearDamping(long objectId);
+
+ public float getAngularDamping() {
+ return getAngularDamping(objectId);
+ }
+
+ private native float getAngularDamping(long objectId);
+
+ public float getRestitution() {
+ return getRestitution(objectId);
+ }
+
+ private native float getRestitution(long objectId);
+
+ /**
+ * The "bouncyness" of the PhysicsRigidBody, best performance if restitution=0
+ * @param restitution
+ */
+ public void setRestitution(float restitution) {
+ setRestitution(objectId, restitution);
+ }
+
+ private native void setRestitution(long objectId, float factor);
+
+ /**
+ * Get the current angular velocity of this PhysicsRigidBody
+ * @return the current linear velocity
+ */
+ public Vector3f getAngularVelocity() {
+ Vector3f vec = new Vector3f();
+ getAngularVelocity(objectId, vec);
+ return vec;
+ }
+
+ private native void getAngularVelocity(long objectId, Vector3f vec);
+
+ /**
+ * Get the current angular velocity of this PhysicsRigidBody
+ * @param vec the vector to store the velocity in
+ */
+ public void getAngularVelocity(Vector3f vec) {
+ getAngularVelocity(objectId, vec);
+ }
+
+ /**
+ * Sets the angular velocity of this PhysicsRigidBody
+ * @param vec the angular velocity of this PhysicsRigidBody
+ */
+ public void setAngularVelocity(Vector3f vec) {
+ setAngularVelocity(objectId, vec);
+ activate();
+ }
+
+ private native void setAngularVelocity(long objectId, Vector3f vec);
+
+ /**
+ * Get the current linear velocity of this PhysicsRigidBody
+ * @return the current linear velocity
+ */
+ public Vector3f getLinearVelocity() {
+ Vector3f vec = new Vector3f();
+ getLinearVelocity(objectId, vec);
+ return vec;
+ }
+
+ private native void getLinearVelocity(long objectId, Vector3f vec);
+
+ /**
+ * Get the current linear velocity of this PhysicsRigidBody
+ * @param vec the vector to store the velocity in
+ */
+ public void getLinearVelocity(Vector3f vec) {
+ getLinearVelocity(objectId, vec);
+ }
+
+ /**
+ * Sets the linear velocity of this PhysicsRigidBody
+ * @param vec the linear velocity of this PhysicsRigidBody
+ */
+ public void setLinearVelocity(Vector3f vec) {
+ setLinearVelocity(objectId, vec);
+ activate();
+ }
+
+ private native void setLinearVelocity(long objectId, Vector3f vec);
+
+ /**
+ * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+ * updates the physics space.<br>
+ * To apply an impulse, use applyImpulse, use applyContinuousForce to apply continuous force.
+ * @param force the force
+ * @param location the location of the force
+ */
+ public void applyForce(Vector3f force, Vector3f location) {
+ applyForce(objectId, force, location);
+ activate();
+ }
+
+ private native void applyForce(long objectId, Vector3f force, Vector3f location);
+
+ /**
+ * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+ * updates the physics space.<br>
+ * To apply an impulse, use applyImpulse.
+ *
+ * @param force the force
+ */
+ public void applyCentralForce(Vector3f force) {
+ applyCentralForce(objectId, force);
+ activate();
+ }
+
+ private native void applyCentralForce(long objectId, Vector3f force);
+
+ /**
+ * Apply a force to the PhysicsRigidBody, only applies force if the next physics update call
+ * updates the physics space.<br>
+ * To apply an impulse, use applyImpulse.
+ *
+ * @param torque the torque
+ */
+ public void applyTorque(Vector3f torque) {
+ applyTorque(objectId, torque);
+ activate();
+ }
+
+ private native void applyTorque(long objectId, Vector3f vec);
+
+ /**
+ * Apply an impulse to the PhysicsRigidBody in the next physics update.
+ * @param impulse applied impulse
+ * @param rel_pos location relative to object
+ */
+ public void applyImpulse(Vector3f impulse, Vector3f rel_pos) {
+ applyImpulse(objectId, impulse, rel_pos);
+ activate();
+ }
+
+ private native void applyImpulse(long objectId, Vector3f impulse, Vector3f rel_pos);
+
+ /**
+ * Apply a torque impulse to the PhysicsRigidBody in the next physics update.
+ * @param vec
+ */
+ public void applyTorqueImpulse(Vector3f vec) {
+ applyTorqueImpulse(objectId, vec);
+ activate();
+ }
+
+ private native void applyTorqueImpulse(long objectId, Vector3f vec);
+
+ /**
+ * Clear all forces from the PhysicsRigidBody
+ *
+ */
+ public void clearForces() {
+ clearForces(objectId);
+ }
+
+ private native void clearForces(long objectId);
+
+ public void setCollisionShape(CollisionShape collisionShape) {
+ super.setCollisionShape(collisionShape);
+ if (collisionShape instanceof MeshCollisionShape && mass != 0) {
+ throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!");
+ }
+ if (objectId == 0) {
+ rebuildRigidBody();
+ } else {
+ setCollisionShape(objectId, collisionShape.getObjectId());
+ updateMassProps(objectId, collisionShape.getObjectId(), mass);
+ }
+ }
+
+ private native void setCollisionShape(long objectId, long collisionShapeId);
+
+ /**
+ * reactivates this PhysicsRigidBody when it has been deactivated because it was not moving
+ */
+ public void activate() {
+ activate(objectId);
+ }
+
+ private native void activate(long objectId);
+
+ public boolean isActive() {
+ return isActive(objectId);
+ }
+
+ private native boolean isActive(long objectId);
+
+ /**
+ * sets the sleeping thresholds, these define when the object gets deactivated
+ * to save ressources. Low values keep the object active when it barely moves
+ * @param linear the linear sleeping threshold
+ * @param angular the angular sleeping threshold
+ */
+ public void setSleepingThresholds(float linear, float angular) {
+ setSleepingThresholds(objectId, linear, angular);
+ }
+
+ private native void setSleepingThresholds(long objectId, float linear, float angular);
+
+ public void setLinearSleepingThreshold(float linearSleepingThreshold) {
+ setLinearSleepingThreshold(objectId, linearSleepingThreshold);
+ }
+
+ private native void setLinearSleepingThreshold(long objectId, float linearSleepingThreshold);
+
+ public void setAngularSleepingThreshold(float angularSleepingThreshold) {
+ setAngularSleepingThreshold(objectId, angularSleepingThreshold);
+ }
+
+ private native void setAngularSleepingThreshold(long objectId, float angularSleepingThreshold);
+
+ public float getLinearSleepingThreshold() {
+ return getLinearSleepingThreshold(objectId);
+ }
+
+ private native float getLinearSleepingThreshold(long objectId);
+
+ public float getAngularSleepingThreshold() {
+ return getAngularSleepingThreshold(objectId);
+ }
+
+ private native float getAngularSleepingThreshold(long objectId);
+
+ public float getAngularFactor() {
+ return getAngularFactor(objectId);
+ }
+
+ private native float getAngularFactor(long objectId);
+
+ public void setAngularFactor(float factor) {
+ setAngularFactor(objectId, factor);
+ }
+
+ private native void setAngularFactor(long objectId, float factor);
+
+ /**
+ * do not use manually, joints are added automatically
+ */
+ public void addJoint(PhysicsJoint joint) {
+ if (!joints.contains(joint)) {
+ joints.add(joint);
+ }
+ updateDebugShape();
+ }
+
+ /**
+ *
+ */
+ public void removeJoint(PhysicsJoint joint) {
+ joints.remove(joint);
+ }
+
+ /**
+ * Returns a list of connected joints. This list is only filled when
+ * the PhysicsRigidBody is actually added to the physics space or loaded from disk.
+ * @return list of active joints connected to this PhysicsRigidBody
+ */
+ public List<PhysicsJoint> getJoints() {
+ return joints;
+ }
+
+ @Override
+ protected Spatial getDebugShape() {
+ //add joints
+ Spatial shape = super.getDebugShape();
+ Node node = null;
+ if (shape instanceof Node) {
+ node = (Node) shape;
+ } else {
+ node = new Node("DebugShapeNode");
+ node.attachChild(shape);
+ }
+ int i = 0;
+ for (Iterator<PhysicsJoint> it = joints.iterator(); it.hasNext();) {
+ PhysicsJoint physicsJoint = it.next();
+ Vector3f pivot = null;
+ if (physicsJoint.getBodyA() == this) {
+ pivot = physicsJoint.getPivotA();
+ } else {
+ pivot = physicsJoint.getPivotB();
+ }
+ Arrow arrow = new Arrow(pivot);
+ Geometry geom = new Geometry("DebugBone" + i, arrow);
+ geom.setMaterial(debugMaterialGreen);
+ node.attachChild(geom);
+ i++;
+ }
+ return node;
+ }
+
+ @Override
+ public void write(JmeExporter e) throws IOException {
+ super.write(e);
+ OutputCapsule capsule = e.getCapsule(this);
+
+ capsule.write(getMass(), "mass", 1.0f);
+
+ capsule.write(getGravity(), "gravity", Vector3f.ZERO);
+ capsule.write(getFriction(), "friction", 0.5f);
+ capsule.write(getRestitution(), "restitution", 0);
+ capsule.write(getAngularFactor(), "angularFactor", 1);
+ capsule.write(kinematic, "kinematic", false);
+
+ capsule.write(getLinearDamping(), "linearDamping", 0);
+ capsule.write(getAngularDamping(), "angularDamping", 0);
+ capsule.write(getLinearSleepingThreshold(), "linearSleepingThreshold", 0.8f);
+ capsule.write(getAngularSleepingThreshold(), "angularSleepingThreshold", 1.0f);
+
+ capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0);
+ capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0);
+
+ capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f());
+ capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f());
+
+ capsule.writeSavableArrayList(joints, "joints", null);
+ }
+
+ @Override
+ public void read(JmeImporter e) throws IOException {
+ super.read(e);
+
+ InputCapsule capsule = e.getCapsule(this);
+ float mass = capsule.readFloat("mass", 1.0f);
+ this.mass = mass;
+ rebuildRigidBody();
+ setGravity((Vector3f) capsule.readSavable("gravity", Vector3f.ZERO.clone()));
+ setFriction(capsule.readFloat("friction", 0.5f));
+ setKinematic(capsule.readBoolean("kinematic", false));
+
+ setRestitution(capsule.readFloat("restitution", 0));
+ setAngularFactor(capsule.readFloat("angularFactor", 1));
+ setDamping(capsule.readFloat("linearDamping", 0), capsule.readFloat("angularDamping", 0));
+ setSleepingThresholds(capsule.readFloat("linearSleepingThreshold", 0.8f), capsule.readFloat("angularSleepingThreshold", 1.0f));
+ setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0));
+ setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0));
+
+ setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f()));
+ setPhysicsRotation((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f()));
+
+ joints = capsule.readSavableArrayList("joints", null);
+ }
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java b/engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java
new file mode 100644
index 0000000..fba1e83
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/PhysicsVehicle.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright (c) 2009-2010 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.objects;
+
+import com.jme3.bullet.PhysicsSpace;
+import com.jme3.bullet.collision.shapes.CollisionShape;
+import com.jme3.bullet.objects.infos.VehicleTuning;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.Arrow;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * <p>PhysicsVehicleNode - Special PhysicsNode that implements vehicle functions</p>
+ * <p>
+ * <i>From bullet manual:</i><br>
+ * For most vehicle simulations, it is recommended to use the simplified Bullet
+ * vehicle model as provided in btRaycastVehicle. Instead of simulation each wheel
+ * and chassis as separate rigid bodies, connected by constraints, it uses a simplified model.
+ * This simplified model has many benefits, and is widely used in commercial driving games.<br>
+ * The entire vehicle is represented as a single rigidbody, the chassis.
+ * The collision detection of the wheels is approximated by ray casts,
+ * and the tire friction is a basic anisotropic friction model.
+ * </p>
+ * @author normenhansen
+ */
+public class PhysicsVehicle extends PhysicsRigidBody {
+
+ protected long vehicleId = 0;
+ protected long rayCasterId = 0;
+ protected VehicleTuning tuning = new VehicleTuning();
+ protected ArrayList<VehicleWheel> wheels = new ArrayList<VehicleWheel>();
+ protected PhysicsSpace physicsSpace;
+
+ public PhysicsVehicle() {
+ }
+
+ public PhysicsVehicle(CollisionShape shape) {
+ super(shape);
+ }
+
+ public PhysicsVehicle(CollisionShape shape, float mass) {
+ super(shape, mass);
+ }
+
+ /**
+ * used internally
+ */
+ public void updateWheels() {
+ if (vehicleId != 0) {
+ for (int i = 0; i < wheels.size(); i++) {
+ updateWheelTransform(vehicleId, i, true);
+ wheels.get(i).updatePhysicsState();
+ }
+ }
+ }
+
+ private native void updateWheelTransform(long vehicleId, int wheel, boolean interpolated);
+
+ /**
+ * used internally
+ */
+ public void applyWheelTransforms() {
+ if (wheels != null) {
+ for (int i = 0; i < wheels.size(); i++) {
+ wheels.get(i).applyWheelTransform();
+ }
+ }
+ }
+
+ @Override
+ protected void postRebuild() {
+ super.postRebuild();
+ motionState.setVehicle(this);
+ createVehicle(physicsSpace);
+ }
+
+ /**
+ * Used internally, creates the actual vehicle constraint when vehicle is added to phyicsspace
+ */
+ public void createVehicle(PhysicsSpace space) {
+ physicsSpace = space;
+ if (space == null) {
+ return;
+ }
+ if (space.getSpaceId() == 0) {
+ throw new IllegalStateException("Physics space is not initialized!");
+ }
+ if (rayCasterId != 0) {
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing RayCaster {0}", Long.toHexString(rayCasterId));
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing Vehicle {0}", Long.toHexString(vehicleId));
+ finalizeNative(rayCasterId, vehicleId);
+ }
+ rayCasterId = createVehicleRaycaster(objectId, space.getSpaceId());
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created RayCaster {0}", Long.toHexString(rayCasterId));
+ vehicleId = createRaycastVehicle(objectId, rayCasterId);
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Vehicle {0}", Long.toHexString(vehicleId));
+ setCoordinateSystem(vehicleId, 0, 1, 2);
+ for (VehicleWheel wheel : wheels) {
+ wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
+ }
+ }
+
+ private native long createVehicleRaycaster(long objectId, long physicsSpaceId);
+
+ private native long createRaycastVehicle(long objectId, long rayCasterId);
+
+ private native void setCoordinateSystem(long objectId, int a, int b, int c);
+
+ private native int addWheel(long objectId, Vector3f location, Vector3f direction, Vector3f axle, float restLength, float radius, VehicleTuning tuning, boolean frontWheel);
+
+ /**
+ * Add a wheel to this vehicle
+ * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
+ * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
+ * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
+ * @param suspensionRestLength The current length of the suspension (metres)
+ * @param wheelRadius the wheel radius
+ * @param isFrontWheel sets if this wheel is a front wheel (steering)
+ * @return the PhysicsVehicleWheel object to get/set infos on the wheel
+ */
+ public VehicleWheel addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
+ return addWheel(null, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+ }
+
+ /**
+ * Add a wheel to this vehicle
+ * @param spat the wheel Geometry
+ * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
+ * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
+ * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
+ * @param suspensionRestLength The current length of the suspension (metres)
+ * @param wheelRadius the wheel radius
+ * @param isFrontWheel sets if this wheel is a front wheel (steering)
+ * @return the PhysicsVehicleWheel object to get/set infos on the wheel
+ */
+ public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
+ VehicleWheel wheel = null;
+ if (spat == null) {
+ wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+ } else {
+ wheel = new VehicleWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
+ }
+ wheel.setFrictionSlip(tuning.frictionSlip);
+ wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm);
+ wheel.setSuspensionStiffness(tuning.suspensionStiffness);
+ wheel.setWheelsDampingCompression(tuning.suspensionCompression);
+ wheel.setWheelsDampingRelaxation(tuning.suspensionDamping);
+ wheel.setMaxSuspensionForce(tuning.maxSuspensionForce);
+ wheels.add(wheel);
+ if (vehicleId != 0) {
+ wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
+ }
+ if (debugShape != null) {
+ updateDebugShape();
+ }
+ return wheel;
+ }
+
+ /**
+ * This rebuilds the vehicle as there is no way in bullet to remove a wheel.
+ * @param wheel
+ */
+ public void removeWheel(int wheel) {
+ wheels.remove(wheel);
+ rebuildRigidBody();
+// updateDebugShape();
+ }
+
+ /**
+ * You can get access to the single wheels via this method.
+ * @param wheel the wheel index
+ * @return the WheelInfo of the selected wheel
+ */
+ public VehicleWheel getWheel(int wheel) {
+ return wheels.get(wheel);
+ }
+
+ public int getNumWheels() {
+ return wheels.size();
+ }
+
+ /**
+ * @return the frictionSlip
+ */
+ public float getFrictionSlip() {
+ return tuning.frictionSlip;
+ }
+
+ /**
+ * Use before adding wheels, this is the default used when adding wheels.
+ * After adding the wheel, use direct wheel access.<br>
+ * The coefficient of friction between the tyre and the ground.
+ * Should be about 0.8 for realistic cars, but can increased for better handling.
+ * Set large (10000.0) for kart racers
+ * @param frictionSlip the frictionSlip to set
+ */
+ public void setFrictionSlip(float frictionSlip) {
+ tuning.frictionSlip = frictionSlip;
+ }
+
+ /**
+ * The coefficient of friction between the tyre and the ground.
+ * Should be about 0.8 for realistic cars, but can increased for better handling.
+ * Set large (10000.0) for kart racers
+ * @param wheel
+ * @param frictionSlip
+ */
+ public void setFrictionSlip(int wheel, float frictionSlip) {
+ wheels.get(wheel).setFrictionSlip(frictionSlip);
+ }
+
+ /**
+ * Reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
+ * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
+ * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
+ * You should also try lowering the vehicle's centre of mass
+ */
+ public void setRollInfluence(int wheel, float rollInfluence) {
+ wheels.get(wheel).setRollInfluence(rollInfluence);
+ }
+
+ /**
+ * @return the maxSuspensionTravelCm
+ */
+ public float getMaxSuspensionTravelCm() {
+ return tuning.maxSuspensionTravelCm;
+ }
+
+ /**
+ * Use before adding wheels, this is the default used when adding wheels.
+ * After adding the wheel, use direct wheel access.<br>
+ * The maximum distance the suspension can be compressed (centimetres)
+ * @param maxSuspensionTravelCm the maxSuspensionTravelCm to set
+ */
+ public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
+ tuning.maxSuspensionTravelCm = maxSuspensionTravelCm;
+ }
+
+ /**
+ * The maximum distance the suspension can be compressed (centimetres)
+ * @param wheel
+ * @param maxSuspensionTravelCm
+ */
+ public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) {
+ wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm);
+ }
+
+ public float getMaxSuspensionForce() {
+ return tuning.maxSuspensionForce;
+ }
+
+ /**
+ * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
+ * handle the weight of your vehcile.
+ * @param maxSuspensionForce
+ */
+ public void setMaxSuspensionForce(float maxSuspensionForce) {
+ tuning.maxSuspensionForce = maxSuspensionForce;
+ }
+
+ /**
+ * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
+ * handle the weight of your vehcile.
+ * @param wheel
+ * @param maxSuspensionForce
+ */
+ public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) {
+ wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce);
+ }
+
+ /**
+ * @return the suspensionCompression
+ */
+ public float getSuspensionCompression() {
+ return tuning.suspensionCompression;
+ }
+
+ /**
+ * Use before adding wheels, this is the default used when adding wheels.
+ * After adding the wheel, use direct wheel access.<br>
+ * The damping coefficient for when the suspension is compressed.
+ * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+ * 0.1 to 0.3 are good values
+ * @param suspensionCompression the suspensionCompression to set
+ */
+ public void setSuspensionCompression(float suspensionCompression) {
+ tuning.suspensionCompression = suspensionCompression;
+ }
+
+ /**
+ * The damping coefficient for when the suspension is compressed.
+ * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+ * 0.1 to 0.3 are good values
+ * @param wheel
+ * @param suspensionCompression
+ */
+ public void setSuspensionCompression(int wheel, float suspensionCompression) {
+ wheels.get(wheel).setWheelsDampingCompression(suspensionCompression);
+ }
+
+ /**
+ * @return the suspensionDamping
+ */
+ public float getSuspensionDamping() {
+ return tuning.suspensionDamping;
+ }
+
+ /**
+ * Use before adding wheels, this is the default used when adding wheels.
+ * After adding the wheel, use direct wheel access.<br>
+ * The damping coefficient for when the suspension is expanding.
+ * See the comments for setSuspensionCompression for how to set k.
+ * @param suspensionDamping the suspensionDamping to set
+ */
+ public void setSuspensionDamping(float suspensionDamping) {
+ tuning.suspensionDamping = suspensionDamping;
+ }
+
+ /**
+ * The damping coefficient for when the suspension is expanding.
+ * See the comments for setSuspensionCompression for how to set k.
+ * @param wheel
+ * @param suspensionDamping
+ */
+ public void setSuspensionDamping(int wheel, float suspensionDamping) {
+ wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping);
+ }
+
+ /**
+ * @return the suspensionStiffness
+ */
+ public float getSuspensionStiffness() {
+ return tuning.suspensionStiffness;
+ }
+
+ /**
+ * Use before adding wheels, this is the default used when adding wheels.
+ * After adding the wheel, use direct wheel access.<br>
+ * The stiffness constant for the suspension. 10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+ * @param suspensionStiffness
+ */
+ public void setSuspensionStiffness(float suspensionStiffness) {
+ tuning.suspensionStiffness = suspensionStiffness;
+ }
+
+ /**
+ * The stiffness constant for the suspension. 10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+ * @param wheel
+ * @param suspensionStiffness
+ */
+ public void setSuspensionStiffness(int wheel, float suspensionStiffness) {
+ wheels.get(wheel).setSuspensionStiffness(suspensionStiffness);
+ }
+
+ /**
+ * Reset the suspension
+ */
+ public void resetSuspension() {
+ resetSuspension(vehicleId);
+ }
+
+ private native void resetSuspension(long vehicleId);
+
+ /**
+ * Apply the given engine force to all wheels, works continuously
+ * @param force the force
+ */
+ public void accelerate(float force) {
+ for (int i = 0; i < wheels.size(); i++) {
+ accelerate(i, force);
+ }
+ }
+
+ /**
+ * Apply the given engine force, works continuously
+ * @param wheel the wheel to apply the force on
+ * @param force the force
+ */
+ public void accelerate(int wheel, float force) {
+ applyEngineForce(vehicleId, wheel, force);
+
+ }
+
+ private native void applyEngineForce(long vehicleId, int wheel, float force);
+
+ /**
+ * Set the given steering value to all front wheels (0 = forward)
+ * @param value the steering angle of the front wheels (Pi = 360deg)
+ */
+ public void steer(float value) {
+ for (int i = 0; i < wheels.size(); i++) {
+ if (getWheel(i).isFrontWheel()) {
+ steer(i, value);
+ }
+ }
+ }
+
+ /**
+ * Set the given steering value to the given wheel (0 = forward)
+ * @param wheel the wheel to set the steering on
+ * @param value the steering angle of the front wheels (Pi = 360deg)
+ */
+ public void steer(int wheel, float value) {
+ steer(vehicleId, wheel, value);
+ }
+
+ private native void steer(long vehicleId, int wheel, float value);
+
+ /**
+ * Apply the given brake force to all wheels, works continuously
+ * @param force the force
+ */
+ public void brake(float force) {
+ for (int i = 0; i < wheels.size(); i++) {
+ brake(i, force);
+ }
+ }
+
+ /**
+ * Apply the given brake force, works continuously
+ * @param wheel the wheel to apply the force on
+ * @param force the force
+ */
+ public void brake(int wheel, float force) {
+ brake(vehicleId, wheel, force);
+ }
+
+ private native void brake(long vehicleId, int wheel, float force);
+
+ /**
+ * Get the current speed of the vehicle in km/h
+ * @return
+ */
+ public float getCurrentVehicleSpeedKmHour() {
+ return getCurrentVehicleSpeedKmHour(vehicleId);
+ }
+
+ private native float getCurrentVehicleSpeedKmHour(long vehicleId);
+
+ /**
+ * Get the current forward vector of the vehicle in world coordinates
+ * @param vector
+ * @return
+ */
+ public Vector3f getForwardVector(Vector3f vector) {
+ if (vector == null) {
+ vector = new Vector3f();
+ }
+ getForwardVector(vehicleId, vector);
+ return vector;
+ }
+
+ private native void getForwardVector(long objectId, Vector3f vector);
+
+ /**
+ * used internally
+ */
+ public long getVehicleId() {
+ return vehicleId;
+ }
+
+ @Override
+ protected Spatial getDebugShape() {
+ Spatial shape = super.getDebugShape();
+ Node node = null;
+ if (shape instanceof Node) {
+ node = (Node) shape;
+ } else {
+ node = new Node("DebugShapeNode");
+ node.attachChild(shape);
+ }
+ int i = 0;
+ for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
+ VehicleWheel physicsVehicleWheel = it.next();
+ 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(debugMaterialGreen);
+ dirGeom.setMaterial(debugMaterialGreen);
+ axleGeom.setMaterial(debugMaterialGreen);
+ wheelGeom.setMaterial(debugMaterialGreen);
+ node.attachChild(locGeom);
+ node.attachChild(dirGeom);
+ node.attachChild(axleGeom);
+ node.attachChild(wheelGeom);
+ i++;
+ }
+ return node;
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule capsule = im.getCapsule(this);
+ tuning = new VehicleTuning();
+ tuning.frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
+ tuning.maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
+ tuning.maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
+ tuning.suspensionCompression = capsule.readFloat("suspensionCompression", 0.83f);
+ tuning.suspensionDamping = capsule.readFloat("suspensionDamping", 0.88f);
+ tuning.suspensionStiffness = capsule.readFloat("suspensionStiffness", 5.88f);
+ wheels = capsule.readSavableArrayList("wheelsList", new ArrayList<VehicleWheel>());
+ motionState.setVehicle(this);
+ super.read(im);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule capsule = ex.getCapsule(this);
+ capsule.write(tuning.frictionSlip, "frictionSlip", 10.5f);
+ capsule.write(tuning.maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
+ capsule.write(tuning.maxSuspensionForce, "maxSuspensionForce", 6000f);
+ capsule.write(tuning.suspensionCompression, "suspensionCompression", 0.83f);
+ capsule.write(tuning.suspensionDamping, "suspensionDamping", 0.88f);
+ capsule.write(tuning.suspensionStiffness, "suspensionStiffness", 5.88f);
+ capsule.writeSavableArrayList(wheels, "wheelsList", new ArrayList<VehicleWheel>());
+ super.write(ex);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing RayCaster {0}", Long.toHexString(rayCasterId));
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing Vehicle {0}", Long.toHexString(vehicleId));
+ finalizeNative(rayCasterId, vehicleId);
+ }
+
+ private native void finalizeNative(long rayCaster, long vehicle);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java b/engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java
new file mode 100644
index 0000000..11eab60
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/VehicleWheel.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) 2009-2010 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.objects;
+
+import com.jme3.bullet.collision.PhysicsCollisionObject;
+import com.jme3.export.*;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.io.IOException;
+
+/**
+ * Stores info about one wheel of a PhysicsVehicle
+ * @author normenhansen
+ */
+public class VehicleWheel implements Savable {
+
+ protected long wheelId = 0;
+ protected int wheelIndex = 0;
+ protected boolean frontWheel;
+ protected Vector3f location = new Vector3f();
+ protected Vector3f direction = new Vector3f();
+ protected Vector3f axle = new Vector3f();
+ protected float suspensionStiffness = 20.0f;
+ protected float wheelsDampingRelaxation = 2.3f;
+ protected float wheelsDampingCompression = 4.4f;
+ protected float frictionSlip = 10.5f;
+ protected float rollInfluence = 1.0f;
+ protected float maxSuspensionTravelCm = 500f;
+ protected float maxSuspensionForce = 6000f;
+ protected float radius = 0.5f;
+ protected float restLength = 1f;
+ protected Vector3f wheelWorldLocation = new Vector3f();
+ protected Quaternion wheelWorldRotation = new Quaternion();
+ protected Spatial wheelSpatial;
+ protected Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f();
+ protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
+ private boolean applyLocal = false;
+
+ public VehicleWheel() {
+ }
+
+ public VehicleWheel(Spatial spat, Vector3f location, Vector3f direction, Vector3f axle,
+ float restLength, float radius, boolean frontWheel) {
+ this(location, direction, axle, restLength, radius, frontWheel);
+ wheelSpatial = spat;
+ }
+
+ public VehicleWheel(Vector3f location, Vector3f direction, Vector3f axle,
+ float restLength, float radius, boolean frontWheel) {
+ this.location.set(location);
+ this.direction.set(direction);
+ this.axle.set(axle);
+ this.frontWheel = frontWheel;
+ this.restLength = restLength;
+ this.radius = radius;
+ }
+
+ public synchronized void updatePhysicsState() {
+ getWheelLocation(wheelId, wheelIndex, wheelWorldLocation);
+ getWheelRotation(wheelId, wheelIndex, tmp_Matrix);
+ wheelWorldRotation.fromRotationMatrix(tmp_Matrix);
+ }
+
+ private native void getWheelLocation(long vehicleId, int wheelId, Vector3f location);
+
+ private native void getWheelRotation(long vehicleId, int wheelId, Matrix3f location);
+
+ public synchronized void applyWheelTransform() {
+ if (wheelSpatial == null) {
+ return;
+ }
+ Quaternion localRotationQuat = wheelSpatial.getLocalRotation();
+ Vector3f localLocation = wheelSpatial.getLocalTranslation();
+ if (!applyLocal && wheelSpatial.getParent() != null) {
+ localLocation.set(wheelWorldLocation).subtractLocal(wheelSpatial.getParent().getWorldTranslation());
+ localLocation.divideLocal(wheelSpatial.getParent().getWorldScale());
+ tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+
+ localRotationQuat.set(wheelWorldRotation);
+ tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat);
+
+ wheelSpatial.setLocalTranslation(localLocation);
+ wheelSpatial.setLocalRotation(localRotationQuat);
+ } else {
+ wheelSpatial.setLocalTranslation(wheelWorldLocation);
+ wheelSpatial.setLocalRotation(wheelWorldRotation);
+ }
+ }
+
+ public long getWheelId() {
+ return wheelId;
+ }
+
+ public void setVehicleId(long vehicleId, int wheelIndex) {
+ this.wheelId = vehicleId;
+ this.wheelIndex = wheelIndex;
+ applyInfo();
+ }
+
+ public boolean isFrontWheel() {
+ return frontWheel;
+ }
+
+ public void setFrontWheel(boolean frontWheel) {
+ this.frontWheel = frontWheel;
+ applyInfo();
+ }
+
+ public Vector3f getLocation() {
+ return location;
+ }
+
+ public Vector3f getDirection() {
+ return direction;
+ }
+
+ public Vector3f getAxle() {
+ return axle;
+ }
+
+ public float getSuspensionStiffness() {
+ return suspensionStiffness;
+ }
+
+ /**
+ * the stiffness constant for the suspension. 10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
+ * @param suspensionStiffness
+ */
+ public void setSuspensionStiffness(float suspensionStiffness) {
+ this.suspensionStiffness = suspensionStiffness;
+ applyInfo();
+ }
+
+ public float getWheelsDampingRelaxation() {
+ return wheelsDampingRelaxation;
+ }
+
+ /**
+ * the damping coefficient for when the suspension is expanding.
+ * See the comments for setWheelsDampingCompression for how to set k.
+ * @param wheelsDampingRelaxation
+ */
+ public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) {
+ this.wheelsDampingRelaxation = wheelsDampingRelaxation;
+ applyInfo();
+ }
+
+ public float getWheelsDampingCompression() {
+ return wheelsDampingCompression;
+ }
+
+ /**
+ * the damping coefficient for when the suspension is compressed.
+ * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
+ * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
+ * 0.1 to 0.3 are good values
+ * @param wheelsDampingCompression
+ */
+ public void setWheelsDampingCompression(float wheelsDampingCompression) {
+ this.wheelsDampingCompression = wheelsDampingCompression;
+ applyInfo();
+ }
+
+ public float getFrictionSlip() {
+ return frictionSlip;
+ }
+
+ /**
+ * the coefficient of friction between the tyre and the ground.
+ * Should be about 0.8 for realistic cars, but can increased for better handling.
+ * Set large (10000.0) for kart racers
+ * @param frictionSlip
+ */
+ public void setFrictionSlip(float frictionSlip) {
+ this.frictionSlip = frictionSlip;
+ applyInfo();
+ }
+
+ public float getRollInfluence() {
+ return rollInfluence;
+ }
+
+ /**
+ * reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
+ * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
+ * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
+ * You should also try lowering the vehicle's centre of mass
+ * @param rollInfluence the rollInfluence to set
+ */
+ public void setRollInfluence(float rollInfluence) {
+ this.rollInfluence = rollInfluence;
+ applyInfo();
+ }
+
+ public float getMaxSuspensionTravelCm() {
+ return maxSuspensionTravelCm;
+ }
+
+ /**
+ * the maximum distance the suspension can be compressed (centimetres)
+ * @param maxSuspensionTravelCm
+ */
+ public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
+ this.maxSuspensionTravelCm = maxSuspensionTravelCm;
+ applyInfo();
+ }
+
+ public float getMaxSuspensionForce() {
+ return maxSuspensionForce;
+ }
+
+ /**
+ * The maximum suspension force, raise this above the default 6000 if your suspension cannot
+ * handle the weight of your vehcile.
+ * @param maxSuspensionTravelCm
+ */
+ public void setMaxSuspensionForce(float maxSuspensionForce) {
+ this.maxSuspensionForce = maxSuspensionForce;
+ applyInfo();
+ }
+
+ private void applyInfo() {
+ if (wheelId == 0) {
+ return;
+ }
+ applyInfo(wheelId, wheelIndex, suspensionStiffness, wheelsDampingRelaxation, wheelsDampingCompression, frictionSlip, rollInfluence, maxSuspensionTravelCm, maxSuspensionForce, radius, frontWheel, restLength);
+ }
+
+ private native void applyInfo(long wheelId, int wheelIndex,
+ float suspensionStiffness,
+ float wheelsDampingRelaxation,
+ float wheelsDampingCompression,
+ float frictionSlip,
+ float rollInfluence,
+ float maxSuspensionTravelCm,
+ float maxSuspensionForce,
+ float wheelsRadius,
+ boolean frontWheel,
+ float suspensionRestLength);
+
+ public float getRadius() {
+ return radius;
+ }
+
+ public void setRadius(float radius) {
+ this.radius = radius;
+ applyInfo();
+ }
+
+ public float getRestLength() {
+ return restLength;
+ }
+
+ public void setRestLength(float restLength) {
+ this.restLength = restLength;
+ applyInfo();
+ }
+
+ /**
+ * returns the object this wheel is in contact with or null if no contact
+ * @return the PhysicsCollisionObject (PhysicsRigidBody, PhysicsGhostObject)
+ */
+ public PhysicsCollisionObject getGroundObject() {
+// if (wheelInfo.raycastInfo.groundObject == null) {
+// return null;
+// } else if (wheelInfo.raycastInfo.groundObject instanceof RigidBody) {
+// System.out.println("RigidBody");
+// return (PhysicsRigidBody) ((RigidBody) wheelInfo.raycastInfo.groundObject).getUserPointer();
+// } else {
+ return null;
+// }
+ }
+
+ /**
+ * returns the location where the wheel collides with the ground (world space)
+ */
+ public Vector3f getCollisionLocation(Vector3f vec) {
+ getCollisionLocation(wheelId, wheelIndex, vec);
+ return vec;
+ }
+
+ private native void getCollisionLocation(long wheelId, int wheelIndex, Vector3f vec);
+
+ /**
+ * returns the location where the wheel collides with the ground (world space)
+ */
+ public Vector3f getCollisionLocation() {
+ Vector3f vec = new Vector3f();
+ getCollisionLocation(wheelId, wheelIndex, vec);
+ return vec;
+ }
+
+ /**
+ * returns the normal where the wheel collides with the ground (world space)
+ */
+ public Vector3f getCollisionNormal(Vector3f vec) {
+ getCollisionNormal(wheelId, wheelIndex, vec);
+ return vec;
+ }
+
+ private native void getCollisionNormal(long wheelId, int wheelIndex, Vector3f vec);
+
+ /**
+ * returns the normal where the wheel collides with the ground (world space)
+ */
+ public Vector3f getCollisionNormal() {
+ Vector3f vec = new Vector3f();
+ getCollisionNormal(wheelId, wheelIndex, vec);
+ return vec;
+ }
+
+ /**
+ * returns how much the wheel skids on the ground (for skid sounds/smoke etc.)<br>
+ * 0.0 = wheels are sliding, 1.0 = wheels have traction.
+ */
+ public float getSkidInfo() {
+ return getSkidInfo(wheelId, wheelIndex);
+ }
+
+ public native float getSkidInfo(long wheelId, int wheelIndex);
+
+ /**
+ * returns how many degrees the wheel has turned since the last physics
+ * step.
+ */
+ public float getDeltaRotation() {
+ return getDeltaRotation(wheelId, wheelIndex);
+ }
+
+ public native float getDeltaRotation(long wheelId, int wheelIndex);
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ InputCapsule capsule = im.getCapsule(this);
+ wheelSpatial = (Spatial) capsule.readSavable("wheelSpatial", null);
+ frontWheel = capsule.readBoolean("frontWheel", false);
+ location = (Vector3f) capsule.readSavable("wheelLocation", new Vector3f());
+ direction = (Vector3f) capsule.readSavable("wheelDirection", new Vector3f());
+ axle = (Vector3f) capsule.readSavable("wheelAxle", new Vector3f());
+ suspensionStiffness = capsule.readFloat("suspensionStiffness", 20.0f);
+ wheelsDampingRelaxation = capsule.readFloat("wheelsDampingRelaxation", 2.3f);
+ wheelsDampingCompression = capsule.readFloat("wheelsDampingCompression", 4.4f);
+ frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
+ rollInfluence = capsule.readFloat("rollInfluence", 1.0f);
+ maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
+ maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
+ radius = capsule.readFloat("wheelRadius", 0.5f);
+ restLength = capsule.readFloat("restLength", 1f);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ OutputCapsule capsule = ex.getCapsule(this);
+ capsule.write(wheelSpatial, "wheelSpatial", null);
+ capsule.write(frontWheel, "frontWheel", false);
+ capsule.write(location, "wheelLocation", new Vector3f());
+ capsule.write(direction, "wheelDirection", new Vector3f());
+ capsule.write(axle, "wheelAxle", new Vector3f());
+ capsule.write(suspensionStiffness, "suspensionStiffness", 20.0f);
+ capsule.write(wheelsDampingRelaxation, "wheelsDampingRelaxation", 2.3f);
+ capsule.write(wheelsDampingCompression, "wheelsDampingCompression", 4.4f);
+ capsule.write(frictionSlip, "frictionSlip", 10.5f);
+ capsule.write(rollInfluence, "rollInfluence", 1.0f);
+ capsule.write(maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
+ capsule.write(maxSuspensionForce, "maxSuspensionForce", 6000f);
+ capsule.write(radius, "wheelRadius", 0.5f);
+ capsule.write(restLength, "restLength", 1f);
+ }
+
+ /**
+ * @return the wheelSpatial
+ */
+ public Spatial getWheelSpatial() {
+ return wheelSpatial;
+ }
+
+ /**
+ * @param wheelSpatial the wheelSpatial to set
+ */
+ public void setWheelSpatial(Spatial wheelSpatial) {
+ this.wheelSpatial = wheelSpatial;
+ }
+
+ public boolean isApplyLocal() {
+ return applyLocal;
+ }
+
+ public void setApplyLocal(boolean applyLocal) {
+ this.applyLocal = applyLocal;
+ }
+
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java b/engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java
new file mode 100644
index 0000000..c60d6c5
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/infos/RigidBodyMotionState.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2009-2010 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.objects.infos;
+
+import com.jme3.bullet.objects.PhysicsVehicle;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Spatial;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * stores transform info of a PhysicsNode in a threadsafe manner to
+ * allow multithreaded access from the jme scenegraph and the bullet physicsspace
+ * @author normenhansen
+ */
+public class RigidBodyMotionState {
+ long motionStateId = 0;
+ private Vector3f worldLocation = new Vector3f();
+ private Matrix3f worldRotation = new Matrix3f();
+ private Quaternion worldRotationQuat = new Quaternion();
+ private Quaternion tmp_inverseWorldRotation = new Quaternion();
+ private PhysicsVehicle vehicle;
+ private boolean applyPhysicsLocal = false;
+// protected LinkedList<PhysicsMotionStateListener> listeners = new LinkedList<PhysicsMotionStateListener>();
+
+ public RigidBodyMotionState() {
+ this.motionStateId = createMotionState();
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created MotionState {0}", Long.toHexString(motionStateId));
+ }
+
+ private native long createMotionState();
+
+ /**
+ * applies the current transform to the given jme Node if the location has been updated on the physics side
+ * @param spatial
+ */
+ public synchronized boolean applyTransform(Spatial spatial) {
+ Vector3f localLocation = spatial.getLocalTranslation();
+ Quaternion localRotationQuat = spatial.getLocalRotation();
+ boolean physicsLocationDirty = applyTransform(motionStateId, localLocation, localRotationQuat);
+ if (!physicsLocationDirty) {
+ return false;
+ }
+ if (!applyPhysicsLocal && spatial.getParent() != null) {
+ localLocation.subtractLocal(spatial.getParent().getWorldTranslation());
+ localLocation.divideLocal(spatial.getParent().getWorldScale());
+ tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
+
+// localRotationQuat.set(worldRotationQuat);
+ tmp_inverseWorldRotation.mult(localRotationQuat, localRotationQuat);
+
+ spatial.setLocalTranslation(localLocation);
+ spatial.setLocalRotation(localRotationQuat);
+ } else {
+ spatial.setLocalTranslation(localLocation);
+ spatial.setLocalRotation(localRotationQuat);
+// spatial.setLocalTranslation(worldLocation);
+// spatial.setLocalRotation(worldRotationQuat);
+ }
+ if (vehicle != null) {
+ vehicle.updateWheels();
+ }
+ return true;
+ }
+
+ private synchronized native boolean applyTransform(long stateId, Vector3f location, Quaternion rotation);
+
+ /**
+ * @return the worldLocation
+ */
+ public Vector3f getWorldLocation() {
+ getWorldLocation(motionStateId, worldLocation);
+ return worldLocation;
+ }
+
+ private native void getWorldLocation(long stateId, Vector3f vec);
+
+ /**
+ * @return the worldRotation
+ */
+ public Matrix3f getWorldRotation() {
+ getWorldRotation(motionStateId, worldRotation);
+ return worldRotation;
+ }
+
+ private native void getWorldRotation(long stateId, Matrix3f vec);
+
+ /**
+ * @return the worldRotationQuat
+ */
+ public Quaternion getWorldRotationQuat() {
+ getWorldRotationQuat(motionStateId, worldRotationQuat);
+ return worldRotationQuat;
+ }
+
+ private native void getWorldRotationQuat(long stateId, Quaternion vec);
+
+ /**
+ * @param vehicle the vehicle to set
+ */
+ public void setVehicle(PhysicsVehicle vehicle) {
+ this.vehicle = vehicle;
+ }
+
+ public boolean isApplyPhysicsLocal() {
+ return applyPhysicsLocal;
+ }
+
+ public void setApplyPhysicsLocal(boolean applyPhysicsLocal) {
+ this.applyPhysicsLocal = applyPhysicsLocal;
+ }
+
+ public long getObjectId(){
+ return motionStateId;
+ }
+// public void addMotionStateListener(PhysicsMotionStateListener listener){
+// listeners.add(listener);
+// }
+//
+// public void removeMotionStateListener(PhysicsMotionStateListener listener){
+// listeners.remove(listener);
+// }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing MotionState {0}", Long.toHexString(motionStateId));
+ finalizeNative(motionStateId);
+ }
+
+ private native void finalizeNative(long objectId);
+}
diff --git a/engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java b/engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java
new file mode 100644
index 0000000..892c9bd
--- /dev/null
+++ b/engine/src/bullet/com/jme3/bullet/objects/infos/VehicleTuning.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2009-2010 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.objects.infos;
+
+/**
+ *
+ * @author normenhansen
+ */
+public class VehicleTuning {
+ public float suspensionStiffness = 5.88f;
+ public float suspensionCompression = 0.83f;
+ public float suspensionDamping = 0.88f;
+ public float maxSuspensionTravelCm = 500f;
+ public float maxSuspensionForce = 6000f;
+ public float frictionSlip = 10.5f;
+}