/* * Copyright (c) 2009-2018 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; /** * Information about one wheel of a PhysicsVehicle. * * @author normenhansen */ public class VehicleWheel implements Savable { /** * unique identifier of the btRaycastVehicle */ protected long wheelId = 0; /** * 0-origin index among the vehicle's wheels (≥0) */ protected int wheelIndex = 0; /** * copy of wheel type: true→front (steering) wheel, * false→non-front wheel */ protected boolean frontWheel; /** * location where the suspension connects to the chassis (in chassis * coordinates) */ protected Vector3f location = new Vector3f(); /** * suspension direction (in chassis coordinates, typically down/0,-1,0) */ protected Vector3f direction = new Vector3f(); /** * axis direction (in chassis coordinates, typically to the right/-1,0,0) */ protected Vector3f axle = new Vector3f(); /** * copy of suspension stiffness constant (10→off-road buggy, * 50→sports car, 200→Formula-1 race car, default=20) */ protected float suspensionStiffness = 20.0f; /** * copy of suspension damping when expanded (0→no damping, default=2.3) */ protected float wheelsDampingRelaxation = 2.3f; /** * copy of suspension damping when compressed (0→no damping, * default=4.4) */ protected float wheelsDampingCompression = 4.4f; /** * copy of coefficient of friction between tyre and ground * (0.8→realistic car, 10000→kart racer, default=10.5) */ protected float frictionSlip = 10.5f; /** * copy of roll-influence factor (0→no roll torque, 1→realistic * behavior, default=1) */ protected float rollInfluence = 1.0f; /** * copy of maximum suspension travel distance (in centimeters, default=500) */ protected float maxSuspensionTravelCm = 500f; /** * copy of maximum force exerted by the suspension (default=6000) */ protected float maxSuspensionForce = 6000f; /** * copy of wheel radius (in physics-space units, >0) */ protected float radius = 0.5f; /** * copy of rest length of the suspension (in physics-space units) */ protected float restLength = 1f; /** * wheel location in physics-space coordinates */ protected Vector3f wheelWorldLocation = new Vector3f(); /** * wheel orientation in physics-space coordinates */ protected Quaternion wheelWorldRotation = new Quaternion(); /** * associated spatial, or null if none */ protected Spatial wheelSpatial; protected Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f(); protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); /** * true → physics coordinates match local transform, false → * physics coordinates match world transform */ private boolean applyLocal = false; /** * No-argument constructor needed by SavableClassUtil. Do not invoke * directly! */ public VehicleWheel() { } /** * Instantiate a wheel. * * @param spat the associated spatial, or null if none * @param location the location where the suspension connects to the chassis * (in chassis coordinates, not null, unaffected) * @param direction the suspension direction (in chassis coordinates, not * null, unaffected, typically down/0,-1,0) * @param axle the axis direction (in chassis coordinates, not null, * unaffected, typically right/-1,0,0) * @param restLength the rest length of the suspension (in physics-space * units) * @param radius the wheel's radius (in physics-space units, ≥0) * @param frontWheel true→front (steering) wheel, false→non-front * wheel */ 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; } /** * Instantiate a wheel without an associated spatial. * * @param location the location where the suspension connects to the chassis * (in chassis coordinates, not null, unaffected) * @param direction the suspension direction (in chassis coordinates, not * null, unaffected, typically down/0,-1,0) * @param axle the axis direction (in chassis coordinates, not null, * unaffected, typically right/-1,0,0) * @param restLength the rest length of the suspension (in physics-space * units) * @param radius the wheel's radius (in physics-space units, ≥0) * @param frontWheel true→front (steering) wheel, false→non-front * wheel */ 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; } /** * Update this wheel's location and orientation in physics space. */ public 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); /** * Apply this wheel's physics location and orientation to its associated * spatial, if any. */ public 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); } } /** * Read the id of the btRaycastVehicle. * * @return the unique identifier (not zero) */ public long getWheelId() { return wheelId; } /** * Assign this wheel to a vehicle. * * @param vehicleId the id of the btRaycastVehicle (not zero) * @param wheelIndex index among the vehicle's wheels (≥0) */ public void setVehicleId(long vehicleId, int wheelIndex) { this.wheelId = vehicleId; this.wheelIndex = wheelIndex; applyInfo(); } /** * Test whether this wheel is a front wheel. * * @return true if front wheel, otherwise false */ public boolean isFrontWheel() { return frontWheel; } /** * Alter whether this wheel is a front (steering) wheel. * * @param frontWheel true→front wheel, false→non-front wheel */ public void setFrontWheel(boolean frontWheel) { this.frontWheel = frontWheel; applyInfo(); } /** * Access the location where the suspension connects to the chassis. * * @return the pre-existing location vector (in chassis coordinates, not * null) */ public Vector3f getLocation() { return location; } /** * Access this wheel's suspension direction. * * @return the pre-existing direction vector (in chassis coordinates, not * null) */ public Vector3f getDirection() { return direction; } /** * Access this wheel's axle direction. * * @return the pre-existing direction vector (not null) */ public Vector3f getAxle() { return axle; } /** * Read the stiffness constant for this wheel's suspension. * * @return the stiffness constant */ public float getSuspensionStiffness() { return suspensionStiffness; } /** * Alter the stiffness constant for this wheel's suspension. * * @param suspensionStiffness the desired stiffness constant * (10→off-road buggy, 50→sports car, 200→Formula-1 race car, * default=20) */ public void setSuspensionStiffness(float suspensionStiffness) { this.suspensionStiffness = suspensionStiffness; applyInfo(); } /** * Read this wheel's damping when the suspension is expanded. * * @return the damping */ public float getWheelsDampingRelaxation() { return wheelsDampingRelaxation; } /** * Alter this wheel's damping when the suspension is expanded. * <p> * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the * damping ratio: * <p> * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and * 0.3 are good values * * @param wheelsDampingRelaxation the desired damping (default=2.3) */ public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) { this.wheelsDampingRelaxation = wheelsDampingRelaxation; applyInfo(); } /** * Read this wheel's damping when the suspension is compressed. * * @return the damping */ public float getWheelsDampingCompression() { return wheelsDampingCompression; } /** * Alter this wheel's damping when the suspension is compressed. * <p> * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the * damping ratio: * <p> * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and * 0.3 are good values * * @param wheelsDampingCompression the desired damping (default=4.4) */ public void setWheelsDampingCompression(float wheelsDampingCompression) { this.wheelsDampingCompression = wheelsDampingCompression; applyInfo(); } /** * Read the friction between this wheel's tyre and the ground. * * @return the coefficient of friction */ public float getFrictionSlip() { return frictionSlip; } /** * Alter the friction between this wheel's tyre and the ground. * <p> * Should be about 0.8 for realistic cars, but can increased for better * handling. Set large (10000.0) for kart racers. * * @param frictionSlip the desired coefficient of friction (default=10.5) */ public void setFrictionSlip(float frictionSlip) { this.frictionSlip = frictionSlip; applyInfo(); } /** * Read this wheel's roll influence. * * @return the roll-influence factor */ public float getRollInfluence() { return rollInfluence; } /** * Alter this wheel's roll influence. * <p> * The roll-influence factor reduces (or magnifies) the torque contributed * by this wheel that tends to cause the vehicle to roll over. This is a bit * of a hack, but it's quite effective. * <p> * If the friction between the tyres and the ground is too high, you may * reduce this factor to prevent the vehicle from rolling over. You should * also try lowering the vehicle's centre of mass. * * @param rollInfluence the desired roll-influence factor (0→no roll * torque, 1→realistic behaviour, default=1) */ public void setRollInfluence(float rollInfluence) { this.rollInfluence = rollInfluence; applyInfo(); } /** * Read the travel distance for this wheel's suspension. * * @return the maximum travel distance (in centimeters) */ public float getMaxSuspensionTravelCm() { return maxSuspensionTravelCm; } /** * Alter the travel distance for this wheel's suspension. * * @param maxSuspensionTravelCm the desired maximum travel distance (in * centimetres, default=500) */ public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) { this.maxSuspensionTravelCm = maxSuspensionTravelCm; applyInfo(); } /** * Read the maximum force exerted by this wheel's suspension. * * @return the maximum force */ public float getMaxSuspensionForce() { return maxSuspensionForce; } /** * Alter the maximum force exerted by this wheel's suspension. * <p> * Increase this if your suspension cannot handle the weight of your * vehicle. * * @param maxSuspensionForce the desired maximum force (default=6000) */ 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); /** * Read the radius of this wheel. * * @return the radius (in physics-space units, ≥0) */ public float getRadius() { return radius; } /** * Alter the radius of this wheel. * * @param radius the desired radius (in physics-space units, ≥0, * default=0.5) */ public void setRadius(float radius) { this.radius = radius; applyInfo(); } /** * Read the rest length of this wheel. * * @return the length */ public float getRestLength() { return restLength; } /** * Alter the rest length of the suspension of this wheel. * * @param restLength the desired length (default=1) */ 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; // } } /** * Copy the location where the wheel touches the ground. * * @param vec storage for the result (not null, modified) * @return a new location vector (in physics-space coordinates, not null) */ public Vector3f getCollisionLocation(Vector3f vec) { getCollisionLocation(wheelId, wheelIndex, vec); return vec; } private native void getCollisionLocation(long wheelId, int wheelIndex, Vector3f vec); /** * Copy the location where the wheel collides with the ground. * * @return a new location vector (in physics-space coordinates) */ public Vector3f getCollisionLocation() { Vector3f vec = new Vector3f(); getCollisionLocation(wheelId, wheelIndex, vec); return vec; } /** * Copy the normal where the wheel touches the ground. * * @param vec storage for the result (not null, modified) * @return a unit vector (in physics-space coordinates, not null) */ public Vector3f getCollisionNormal(Vector3f vec) { getCollisionNormal(wheelId, wheelIndex, vec); return vec; } private native void getCollisionNormal(long wheelId, int wheelIndex, Vector3f vec); /** * Copy the normal where the wheel touches the ground. * * @return a new unit vector (in physics-space coordinates, not null) */ public Vector3f getCollisionNormal() { Vector3f vec = new Vector3f(); getCollisionNormal(wheelId, wheelIndex, vec); return vec; } /** * Calculate to what extent the wheel is skidding (for skid sounds/smoke * etc.) * * @return the relative amount of traction (0→wheel is sliding, * 1→wheel has full traction) */ public float getSkidInfo() { return getSkidInfo(wheelId, wheelIndex); } public native float getSkidInfo(long wheelId, int wheelIndex); /** * Calculate how much this wheel has turned since the last physics step. * * @return the rotation angle (in radians) */ public float getDeltaRotation() { return getDeltaRotation(wheelId, wheelIndex); } public native float getDeltaRotation(long wheelId, int wheelIndex); /** * De-serialize this wheel, for example when loading from a J3O file. * * @param im importer (not null) * @throws IOException from importer */ @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); } /** * Serialize this wheel, for example when saving to a J3O file. * * @param ex exporter (not null) * @throws IOException from exporter */ @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); } /** * Access the spatial associated with this wheel. * * @return the pre-existing instance, or null */ public Spatial getWheelSpatial() { return wheelSpatial; } /** * Alter which spatial is associated with this wheel. * * @param wheelSpatial the desired spatial, or null for none */ public void setWheelSpatial(Spatial wheelSpatial) { this.wheelSpatial = wheelSpatial; } /** * Test whether physics coordinates should match the local transform of the * Spatial. * * @return true if matching local transform, false if matching world * transform */ public boolean isApplyLocal() { return applyLocal; } /** * Alter whether physics coordinates should match the local transform of the * Spatial. * * @param applyLocal true→match local transform, false→match world * transform (default=false) */ public void setApplyLocal(boolean applyLocal) { this.applyLocal = applyLocal; } /** * Copy this wheel's physics-space orientation to the specified quaternion. * * @param store storage for the result (not null, modified) */ public void getWheelWorldRotation(final Quaternion store) { store.set(this.wheelWorldRotation); } /** * Copy this wheel's physics-space location to the specified vector. * * @param store storage for the result (not null, modified) */ public void getWheelWorldLocation(final Vector3f store) { store.set(this.wheelWorldLocation); } }