Home | History | Annotate | Download | only in objects
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.bullet.objects;
     33 
     34 import com.bulletphysics.collision.dispatch.CollisionObject;
     35 import com.bulletphysics.dynamics.vehicle.*;
     36 import com.jme3.bullet.PhysicsSpace;
     37 import com.jme3.bullet.collision.shapes.CollisionShape;
     38 import com.jme3.bullet.util.Converter;
     39 import com.jme3.export.InputCapsule;
     40 import com.jme3.export.JmeExporter;
     41 import com.jme3.export.JmeImporter;
     42 import com.jme3.export.OutputCapsule;
     43 import com.jme3.math.Vector3f;
     44 import com.jme3.scene.Geometry;
     45 import com.jme3.scene.Node;
     46 import com.jme3.scene.Spatial;
     47 import com.jme3.scene.debug.Arrow;
     48 import java.io.IOException;
     49 import java.util.ArrayList;
     50 import java.util.Iterator;
     51 
     52 /**
     53  * <p>PhysicsVehicleNode - Special PhysicsNode that implements vehicle functions</p>
     54  * <p>
     55  * <i>From bullet manual:</i><br>
     56  * For most vehicle simulations, it is recommended to use the simplified Bullet
     57  * vehicle model as provided in btRaycastVehicle. Instead of simulation each wheel
     58  * and chassis as separate rigid bodies, connected by constraints, it uses a simplified model.
     59  * This simplified model has many benefits, and is widely used in commercial driving games.<br>
     60  * The entire vehicle is represented as a single rigidbody, the chassis.
     61  * The collision detection of the wheels is approximated by ray casts,
     62  * and the tire friction is a basic anisotropic friction model.
     63  * </p>
     64  * @author normenhansen
     65  */
     66 public class PhysicsVehicle extends PhysicsRigidBody {
     67 
     68     protected RaycastVehicle vehicle;
     69     protected VehicleTuning tuning;
     70     protected VehicleRaycaster rayCaster;
     71     protected ArrayList<VehicleWheel> wheels = new ArrayList<VehicleWheel>();
     72     protected PhysicsSpace physicsSpace;
     73 
     74     public PhysicsVehicle() {
     75     }
     76 
     77     public PhysicsVehicle(CollisionShape shape) {
     78         super(shape);
     79     }
     80 
     81     public PhysicsVehicle(CollisionShape shape, float mass) {
     82         super(shape, mass);
     83     }
     84 
     85     /**
     86      * used internally
     87      */
     88     public void updateWheels() {
     89         if (vehicle != null) {
     90             for (int i = 0; i < wheels.size(); i++) {
     91                 vehicle.updateWheelTransform(i, true);
     92                 wheels.get(i).updatePhysicsState();
     93             }
     94         }
     95     }
     96 
     97     /**
     98      * used internally
     99      */
    100     public void applyWheelTransforms() {
    101         if (wheels != null) {
    102             for (int i = 0; i < wheels.size(); i++) {
    103                 wheels.get(i).applyWheelTransform();
    104             }
    105         }
    106     }
    107 
    108     @Override
    109     protected void postRebuild() {
    110         super.postRebuild();
    111         if (tuning == null) {
    112             tuning = new VehicleTuning();
    113         }
    114         rBody.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
    115         motionState.setVehicle(this);
    116         if (physicsSpace != null) {
    117             createVehicle(physicsSpace);
    118         }
    119     }
    120 
    121     /**
    122      * Used internally, creates the actual vehicle constraint when vehicle is added to phyicsspace
    123      */
    124     public void createVehicle(PhysicsSpace space) {
    125         physicsSpace = space;
    126         if (space == null) {
    127             return;
    128         }
    129         rayCaster = new DefaultVehicleRaycaster(space.getDynamicsWorld());
    130         vehicle = new RaycastVehicle(tuning, rBody, rayCaster);
    131         vehicle.setCoordinateSystem(0, 1, 2);
    132         for (VehicleWheel wheel : wheels) {
    133             wheel.setWheelInfo(vehicle.addWheel(Converter.convert(wheel.getLocation()), Converter.convert(wheel.getDirection()), Converter.convert(wheel.getAxle()),
    134                     wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
    135         }
    136     }
    137 
    138     /**
    139      * Add a wheel to this vehicle
    140      * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
    141      * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
    142      * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
    143      * @param suspensionRestLength The current length of the suspension (metres)
    144      * @param wheelRadius the wheel radius
    145      * @param isFrontWheel sets if this wheel is a front wheel (steering)
    146      * @return the PhysicsVehicleWheel object to get/set infos on the wheel
    147      */
    148     public VehicleWheel addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
    149         return addWheel(null, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
    150     }
    151 
    152     /**
    153      * Add a wheel to this vehicle
    154      * @param spat the wheel Geometry
    155      * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
    156      * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
    157      * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
    158      * @param suspensionRestLength The current length of the suspension (metres)
    159      * @param wheelRadius the wheel radius
    160      * @param isFrontWheel sets if this wheel is a front wheel (steering)
    161      * @return the PhysicsVehicleWheel object to get/set infos on the wheel
    162      */
    163     public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
    164         VehicleWheel wheel = null;
    165         if (spat == null) {
    166             wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
    167         } else {
    168             wheel = new VehicleWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
    169         }
    170         if (vehicle != null) {
    171             WheelInfo info = vehicle.addWheel(Converter.convert(connectionPoint), Converter.convert(direction), Converter.convert(axle),
    172                     suspensionRestLength, wheelRadius, tuning, isFrontWheel);
    173             wheel.setWheelInfo(info);
    174         }
    175         wheel.setFrictionSlip(tuning.frictionSlip);
    176         wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm);
    177         wheel.setSuspensionStiffness(tuning.suspensionStiffness);
    178         wheel.setWheelsDampingCompression(tuning.suspensionCompression);
    179         wheel.setWheelsDampingRelaxation(tuning.suspensionDamping);
    180         wheel.setMaxSuspensionForce(tuning.maxSuspensionForce);
    181         wheels.add(wheel);
    182         if (debugShape != null) {
    183             detachDebugShape();
    184         }
    185 //        updateDebugShape();
    186         return wheel;
    187     }
    188 
    189     /**
    190      * This rebuilds the vehicle as there is no way in bullet to remove a wheel.
    191      * @param wheel
    192      */
    193     public void removeWheel(int wheel) {
    194         wheels.remove(wheel);
    195         rebuildRigidBody();
    196 //        updateDebugShape();
    197     }
    198 
    199     /**
    200      * You can get access to the single wheels via this method.
    201      * @param wheel the wheel index
    202      * @return the WheelInfo of the selected wheel
    203      */
    204     public VehicleWheel getWheel(int wheel) {
    205         return wheels.get(wheel);
    206     }
    207 
    208     public int getNumWheels() {
    209         return wheels.size();
    210     }
    211 
    212     /**
    213      * @return the frictionSlip
    214      */
    215     public float getFrictionSlip() {
    216         return tuning.frictionSlip;
    217     }
    218 
    219     /**
    220      * Use before adding wheels, this is the default used when adding wheels.
    221      * After adding the wheel, use direct wheel access.<br>
    222      * The coefficient of friction between the tyre and the ground.
    223      * Should be about 0.8 for realistic cars, but can increased for better handling.
    224      * Set large (10000.0) for kart racers
    225      * @param frictionSlip the frictionSlip to set
    226      */
    227     public void setFrictionSlip(float frictionSlip) {
    228         tuning.frictionSlip = frictionSlip;
    229     }
    230 
    231     /**
    232      * The coefficient of friction between the tyre and the ground.
    233      * Should be about 0.8 for realistic cars, but can increased for better handling.
    234      * Set large (10000.0) for kart racers
    235      * @param wheel
    236      * @param frictionSlip
    237      */
    238     public void setFrictionSlip(int wheel, float frictionSlip) {
    239         wheels.get(wheel).setFrictionSlip(frictionSlip);
    240     }
    241 
    242     /**
    243      * Reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
    244      * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
    245      * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
    246      * You should also try lowering the vehicle's centre of mass
    247      */
    248     public void setRollInfluence(int wheel, float rollInfluence) {
    249         wheels.get(wheel).setRollInfluence(rollInfluence);
    250     }
    251 
    252     /**
    253      * @return the maxSuspensionTravelCm
    254      */
    255     public float getMaxSuspensionTravelCm() {
    256         return tuning.maxSuspensionTravelCm;
    257     }
    258 
    259     /**
    260      * Use before adding wheels, this is the default used when adding wheels.
    261      * After adding the wheel, use direct wheel access.<br>
    262      * The maximum distance the suspension can be compressed (centimetres)
    263      * @param maxSuspensionTravelCm the maxSuspensionTravelCm to set
    264      */
    265     public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
    266         tuning.maxSuspensionTravelCm = maxSuspensionTravelCm;
    267     }
    268 
    269     /**
    270      * The maximum distance the suspension can be compressed (centimetres)
    271      * @param wheel
    272      * @param maxSuspensionTravelCm
    273      */
    274     public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) {
    275         wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm);
    276     }
    277 
    278     public float getMaxSuspensionForce() {
    279         return tuning.maxSuspensionForce;
    280     }
    281 
    282     /**
    283      * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
    284      * handle the weight of your vehcile.
    285      * @param maxSuspensionForce
    286      */
    287     public void setMaxSuspensionForce(float maxSuspensionForce) {
    288         tuning.maxSuspensionForce = maxSuspensionForce;
    289     }
    290 
    291     /**
    292      * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
    293      * handle the weight of your vehcile.
    294      * @param wheel
    295      * @param maxSuspensionForce
    296      */
    297     public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) {
    298         wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce);
    299     }
    300 
    301     /**
    302      * @return the suspensionCompression
    303      */
    304     public float getSuspensionCompression() {
    305         return tuning.suspensionCompression;
    306     }
    307 
    308     /**
    309      * Use before adding wheels, this is the default used when adding wheels.
    310      * After adding the wheel, use direct wheel access.<br>
    311      * The damping coefficient for when the suspension is compressed.
    312      * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
    313      * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
    314      * 0.1 to 0.3 are good values
    315      * @param suspensionCompression the suspensionCompression to set
    316      */
    317     public void setSuspensionCompression(float suspensionCompression) {
    318         tuning.suspensionCompression = suspensionCompression;
    319     }
    320 
    321     /**
    322      * The damping coefficient for when the suspension is compressed.
    323      * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
    324      * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
    325      * 0.1 to 0.3 are good values
    326      * @param wheel
    327      * @param suspensionCompression
    328      */
    329     public void setSuspensionCompression(int wheel, float suspensionCompression) {
    330         wheels.get(wheel).setWheelsDampingCompression(suspensionCompression);
    331     }
    332 
    333     /**
    334      * @return the suspensionDamping
    335      */
    336     public float getSuspensionDamping() {
    337         return tuning.suspensionDamping;
    338     }
    339 
    340     /**
    341      * Use before adding wheels, this is the default used when adding wheels.
    342      * After adding the wheel, use direct wheel access.<br>
    343      * The damping coefficient for when the suspension is expanding.
    344      * See the comments for setSuspensionCompression for how to set k.
    345      * @param suspensionDamping the suspensionDamping to set
    346      */
    347     public void setSuspensionDamping(float suspensionDamping) {
    348         tuning.suspensionDamping = suspensionDamping;
    349     }
    350 
    351     /**
    352      * The damping coefficient for when the suspension is expanding.
    353      * See the comments for setSuspensionCompression for how to set k.
    354      * @param wheel
    355      * @param suspensionDamping
    356      */
    357     public void setSuspensionDamping(int wheel, float suspensionDamping) {
    358         wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping);
    359     }
    360 
    361     /**
    362      * @return the suspensionStiffness
    363      */
    364     public float getSuspensionStiffness() {
    365         return tuning.suspensionStiffness;
    366     }
    367 
    368     /**
    369      * Use before adding wheels, this is the default used when adding wheels.
    370      * After adding the wheel, use direct wheel access.<br>
    371      * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
    372      * @param suspensionStiffness
    373      */
    374     public void setSuspensionStiffness(float suspensionStiffness) {
    375         tuning.suspensionStiffness = suspensionStiffness;
    376     }
    377 
    378     /**
    379      * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
    380      * @param wheel
    381      * @param suspensionStiffness
    382      */
    383     public void setSuspensionStiffness(int wheel, float suspensionStiffness) {
    384         wheels.get(wheel).setSuspensionStiffness(suspensionStiffness);
    385     }
    386 
    387     /**
    388      * Reset the suspension
    389      */
    390     public void resetSuspension() {
    391         vehicle.resetSuspension();
    392     }
    393 
    394     /**
    395      * Apply the given engine force to all wheels, works continuously
    396      * @param force the force
    397      */
    398     public void accelerate(float force) {
    399         for (int i = 0; i < wheels.size(); i++) {
    400             vehicle.applyEngineForce(force, i);
    401         }
    402     }
    403 
    404     /**
    405      * Apply the given engine force, works continuously
    406      * @param wheel the wheel to apply the force on
    407      * @param force the force
    408      */
    409     public void accelerate(int wheel, float force) {
    410         vehicle.applyEngineForce(force, wheel);
    411     }
    412 
    413     /**
    414      * Set the given steering value to all front wheels (0 = forward)
    415      * @param value the steering angle of the front wheels (Pi = 360deg)
    416      */
    417     public void steer(float value) {
    418         for (int i = 0; i < wheels.size(); i++) {
    419             if (getWheel(i).isFrontWheel()) {
    420                 vehicle.setSteeringValue(value, i);
    421             }
    422         }
    423     }
    424 
    425     /**
    426      * Set the given steering value to the given wheel (0 = forward)
    427      * @param wheel the wheel to set the steering on
    428      * @param value the steering angle of the front wheels (Pi = 360deg)
    429      */
    430     public void steer(int wheel, float value) {
    431         vehicle.setSteeringValue(value, wheel);
    432     }
    433 
    434     /**
    435      * Apply the given brake force to all wheels, works continuously
    436      * @param force the force
    437      */
    438     public void brake(float force) {
    439         for (int i = 0; i < wheels.size(); i++) {
    440             vehicle.setBrake(force, i);
    441         }
    442     }
    443 
    444     /**
    445      * Apply the given brake force, works continuously
    446      * @param wheel the wheel to apply the force on
    447      * @param force the force
    448      */
    449     public void brake(int wheel, float force) {
    450         vehicle.setBrake(force, wheel);
    451     }
    452 
    453     /**
    454      * Get the current speed of the vehicle in km/h
    455      * @return
    456      */
    457     public float getCurrentVehicleSpeedKmHour() {
    458         return vehicle.getCurrentSpeedKmHour();
    459     }
    460 
    461     /**
    462      * Get the current forward vector of the vehicle in world coordinates
    463      * @param vector
    464      * @return
    465      */
    466     public Vector3f getForwardVector(Vector3f vector) {
    467         if (vector == null) {
    468             vector = new Vector3f();
    469         }
    470         vehicle.getForwardVector(tempVec);
    471         Converter.convert(tempVec, vector);
    472         return vector;
    473     }
    474 
    475     /**
    476      * used internally
    477      */
    478     public RaycastVehicle getVehicleId() {
    479         return vehicle;
    480     }
    481 
    482     @Override
    483     public void destroy() {
    484         super.destroy();
    485     }
    486 
    487     @Override
    488     protected Spatial getDebugShape() {
    489         Spatial shape = super.getDebugShape();
    490         Node node = null;
    491         if (shape instanceof Node) {
    492             node = (Node) shape;
    493         } else {
    494             node = new Node("DebugShapeNode");
    495             node.attachChild(shape);
    496         }
    497         int i = 0;
    498         for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
    499             VehicleWheel physicsVehicleWheel = it.next();
    500             Vector3f location = physicsVehicleWheel.getLocation().clone();
    501             Vector3f direction = physicsVehicleWheel.getDirection().clone();
    502             Vector3f axle = physicsVehicleWheel.getAxle().clone();
    503             float restLength = physicsVehicleWheel.getRestLength();
    504             float radius = physicsVehicleWheel.getRadius();
    505 
    506             Arrow locArrow = new Arrow(location);
    507             Arrow axleArrow = new Arrow(axle.normalizeLocal().multLocal(0.3f));
    508             Arrow wheelArrow = new Arrow(direction.normalizeLocal().multLocal(radius));
    509             Arrow dirArrow = new Arrow(direction.normalizeLocal().multLocal(restLength));
    510             Geometry locGeom = new Geometry("WheelLocationDebugShape" + i, locArrow);
    511             Geometry dirGeom = new Geometry("WheelDirectionDebugShape" + i, dirArrow);
    512             Geometry axleGeom = new Geometry("WheelAxleDebugShape" + i, axleArrow);
    513             Geometry wheelGeom = new Geometry("WheelRadiusDebugShape" + i, wheelArrow);
    514             dirGeom.setLocalTranslation(location);
    515             axleGeom.setLocalTranslation(location.add(direction));
    516             wheelGeom.setLocalTranslation(location.add(direction));
    517             locGeom.setMaterial(debugMaterialGreen);
    518             dirGeom.setMaterial(debugMaterialGreen);
    519             axleGeom.setMaterial(debugMaterialGreen);
    520             wheelGeom.setMaterial(debugMaterialGreen);
    521             node.attachChild(locGeom);
    522             node.attachChild(dirGeom);
    523             node.attachChild(axleGeom);
    524             node.attachChild(wheelGeom);
    525             i++;
    526         }
    527         return node;
    528     }
    529 
    530     @Override
    531     public void read(JmeImporter im) throws IOException {
    532         InputCapsule capsule = im.getCapsule(this);
    533         tuning = new VehicleTuning();
    534         tuning.frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
    535         tuning.maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
    536         tuning.maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
    537         tuning.suspensionCompression = capsule.readFloat("suspensionCompression", 0.83f);
    538         tuning.suspensionDamping = capsule.readFloat("suspensionDamping", 0.88f);
    539         tuning.suspensionStiffness = capsule.readFloat("suspensionStiffness", 5.88f);
    540         wheels = capsule.readSavableArrayList("wheelsList", new ArrayList<VehicleWheel>());
    541         motionState.setVehicle(this);
    542         super.read(im);
    543     }
    544 
    545     @Override
    546     public void write(JmeExporter ex) throws IOException {
    547         OutputCapsule capsule = ex.getCapsule(this);
    548         capsule.write(tuning.frictionSlip, "frictionSlip", 10.5f);
    549         capsule.write(tuning.maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
    550         capsule.write(tuning.maxSuspensionForce, "maxSuspensionForce", 6000f);
    551         capsule.write(tuning.suspensionCompression, "suspensionCompression", 0.83f);
    552         capsule.write(tuning.suspensionDamping, "suspensionDamping", 0.88f);
    553         capsule.write(tuning.suspensionStiffness, "suspensionStiffness", 5.88f);
    554         capsule.writeSavableArrayList(wheels, "wheelsList", new ArrayList<VehicleWheel>());
    555         super.write(ex);
    556     }
    557 }
    558