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.jme3.bullet.PhysicsSpace;
     35 import com.jme3.bullet.collision.shapes.CollisionShape;
     36 import com.jme3.bullet.objects.infos.VehicleTuning;
     37 import com.jme3.export.InputCapsule;
     38 import com.jme3.export.JmeExporter;
     39 import com.jme3.export.JmeImporter;
     40 import com.jme3.export.OutputCapsule;
     41 import com.jme3.math.Vector3f;
     42 import com.jme3.scene.Geometry;
     43 import com.jme3.scene.Node;
     44 import com.jme3.scene.Spatial;
     45 import com.jme3.scene.debug.Arrow;
     46 import java.io.IOException;
     47 import java.util.ArrayList;
     48 import java.util.Iterator;
     49 import java.util.logging.Level;
     50 import java.util.logging.Logger;
     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 long vehicleId = 0;
     69     protected long rayCasterId = 0;
     70     protected VehicleTuning tuning = new VehicleTuning();
     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 (vehicleId != 0) {
     90             for (int i = 0; i < wheels.size(); i++) {
     91                 updateWheelTransform(vehicleId, i, true);
     92                 wheels.get(i).updatePhysicsState();
     93             }
     94         }
     95     }
     96 
     97     private native void updateWheelTransform(long vehicleId, int wheel, boolean interpolated);
     98 
     99     /**
    100      * used internally
    101      */
    102     public void applyWheelTransforms() {
    103         if (wheels != null) {
    104             for (int i = 0; i < wheels.size(); i++) {
    105                 wheels.get(i).applyWheelTransform();
    106             }
    107         }
    108     }
    109 
    110     @Override
    111     protected void postRebuild() {
    112         super.postRebuild();
    113         motionState.setVehicle(this);
    114         createVehicle(physicsSpace);
    115     }
    116 
    117     /**
    118      * Used internally, creates the actual vehicle constraint when vehicle is added to phyicsspace
    119      */
    120     public void createVehicle(PhysicsSpace space) {
    121         physicsSpace = space;
    122         if (space == null) {
    123             return;
    124         }
    125         if (space.getSpaceId() == 0) {
    126             throw new IllegalStateException("Physics space is not initialized!");
    127         }
    128         if (rayCasterId != 0) {
    129             Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing RayCaster {0}", Long.toHexString(rayCasterId));
    130             Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Clearing Vehicle {0}", Long.toHexString(vehicleId));
    131             finalizeNative(rayCasterId, vehicleId);
    132         }
    133         rayCasterId = createVehicleRaycaster(objectId, space.getSpaceId());
    134         Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created RayCaster {0}", Long.toHexString(rayCasterId));
    135         vehicleId = createRaycastVehicle(objectId, rayCasterId);
    136         Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Vehicle {0}", Long.toHexString(vehicleId));
    137         setCoordinateSystem(vehicleId, 0, 1, 2);
    138         for (VehicleWheel wheel : wheels) {
    139             wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
    140         }
    141     }
    142 
    143     private native long createVehicleRaycaster(long objectId, long physicsSpaceId);
    144 
    145     private native long createRaycastVehicle(long objectId, long rayCasterId);
    146 
    147     private native void setCoordinateSystem(long objectId, int a, int b, int c);
    148 
    149     private native int addWheel(long objectId, Vector3f location, Vector3f direction, Vector3f axle, float restLength, float radius, VehicleTuning tuning, boolean frontWheel);
    150 
    151     /**
    152      * Add a wheel to this vehicle
    153      * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
    154      * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
    155      * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
    156      * @param suspensionRestLength The current length of the suspension (metres)
    157      * @param wheelRadius the wheel radius
    158      * @param isFrontWheel sets if this wheel is a front wheel (steering)
    159      * @return the PhysicsVehicleWheel object to get/set infos on the wheel
    160      */
    161     public VehicleWheel addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
    162         return addWheel(null, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
    163     }
    164 
    165     /**
    166      * Add a wheel to this vehicle
    167      * @param spat the wheel Geometry
    168      * @param connectionPoint The starting point of the ray, where the suspension connects to the chassis (chassis space)
    169      * @param direction the direction of the wheel (should be -Y / 0,-1,0 for a normal car)
    170      * @param axle The axis of the wheel, pointing right in vehicle direction (should be -X / -1,0,0 for a normal car)
    171      * @param suspensionRestLength The current length of the suspension (metres)
    172      * @param wheelRadius the wheel radius
    173      * @param isFrontWheel sets if this wheel is a front wheel (steering)
    174      * @return the PhysicsVehicleWheel object to get/set infos on the wheel
    175      */
    176     public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) {
    177         VehicleWheel wheel = null;
    178         if (spat == null) {
    179             wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
    180         } else {
    181             wheel = new VehicleWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel);
    182         }
    183         wheel.setFrictionSlip(tuning.frictionSlip);
    184         wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm);
    185         wheel.setSuspensionStiffness(tuning.suspensionStiffness);
    186         wheel.setWheelsDampingCompression(tuning.suspensionCompression);
    187         wheel.setWheelsDampingRelaxation(tuning.suspensionDamping);
    188         wheel.setMaxSuspensionForce(tuning.maxSuspensionForce);
    189         wheels.add(wheel);
    190         if (vehicleId != 0) {
    191             wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel()));
    192         }
    193         if (debugShape != null) {
    194             updateDebugShape();
    195         }
    196         return wheel;
    197     }
    198 
    199     /**
    200      * This rebuilds the vehicle as there is no way in bullet to remove a wheel.
    201      * @param wheel
    202      */
    203     public void removeWheel(int wheel) {
    204         wheels.remove(wheel);
    205         rebuildRigidBody();
    206 //        updateDebugShape();
    207     }
    208 
    209     /**
    210      * You can get access to the single wheels via this method.
    211      * @param wheel the wheel index
    212      * @return the WheelInfo of the selected wheel
    213      */
    214     public VehicleWheel getWheel(int wheel) {
    215         return wheels.get(wheel);
    216     }
    217 
    218     public int getNumWheels() {
    219         return wheels.size();
    220     }
    221 
    222     /**
    223      * @return the frictionSlip
    224      */
    225     public float getFrictionSlip() {
    226         return tuning.frictionSlip;
    227     }
    228 
    229     /**
    230      * Use before adding wheels, this is the default used when adding wheels.
    231      * After adding the wheel, use direct wheel access.<br>
    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 frictionSlip the frictionSlip to set
    236      */
    237     public void setFrictionSlip(float frictionSlip) {
    238         tuning.frictionSlip = frictionSlip;
    239     }
    240 
    241     /**
    242      * The coefficient of friction between the tyre and the ground.
    243      * Should be about 0.8 for realistic cars, but can increased for better handling.
    244      * Set large (10000.0) for kart racers
    245      * @param wheel
    246      * @param frictionSlip
    247      */
    248     public void setFrictionSlip(int wheel, float frictionSlip) {
    249         wheels.get(wheel).setFrictionSlip(frictionSlip);
    250     }
    251 
    252     /**
    253      * Reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
    254      * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
    255      * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
    256      * You should also try lowering the vehicle's centre of mass
    257      */
    258     public void setRollInfluence(int wheel, float rollInfluence) {
    259         wheels.get(wheel).setRollInfluence(rollInfluence);
    260     }
    261 
    262     /**
    263      * @return the maxSuspensionTravelCm
    264      */
    265     public float getMaxSuspensionTravelCm() {
    266         return tuning.maxSuspensionTravelCm;
    267     }
    268 
    269     /**
    270      * Use before adding wheels, this is the default used when adding wheels.
    271      * After adding the wheel, use direct wheel access.<br>
    272      * The maximum distance the suspension can be compressed (centimetres)
    273      * @param maxSuspensionTravelCm the maxSuspensionTravelCm to set
    274      */
    275     public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
    276         tuning.maxSuspensionTravelCm = maxSuspensionTravelCm;
    277     }
    278 
    279     /**
    280      * The maximum distance the suspension can be compressed (centimetres)
    281      * @param wheel
    282      * @param maxSuspensionTravelCm
    283      */
    284     public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) {
    285         wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm);
    286     }
    287 
    288     public float getMaxSuspensionForce() {
    289         return tuning.maxSuspensionForce;
    290     }
    291 
    292     /**
    293      * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
    294      * handle the weight of your vehcile.
    295      * @param maxSuspensionForce
    296      */
    297     public void setMaxSuspensionForce(float maxSuspensionForce) {
    298         tuning.maxSuspensionForce = maxSuspensionForce;
    299     }
    300 
    301     /**
    302      * This vaue caps the maximum suspension force, raise this above the default 6000 if your suspension cannot
    303      * handle the weight of your vehcile.
    304      * @param wheel
    305      * @param maxSuspensionForce
    306      */
    307     public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) {
    308         wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce);
    309     }
    310 
    311     /**
    312      * @return the suspensionCompression
    313      */
    314     public float getSuspensionCompression() {
    315         return tuning.suspensionCompression;
    316     }
    317 
    318     /**
    319      * Use before adding wheels, this is the default used when adding wheels.
    320      * After adding the wheel, use direct wheel access.<br>
    321      * The damping coefficient for when the suspension is compressed.
    322      * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
    323      * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
    324      * 0.1 to 0.3 are good values
    325      * @param suspensionCompression the suspensionCompression to set
    326      */
    327     public void setSuspensionCompression(float suspensionCompression) {
    328         tuning.suspensionCompression = suspensionCompression;
    329     }
    330 
    331     /**
    332      * The damping coefficient for when the suspension is compressed.
    333      * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
    334      * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
    335      * 0.1 to 0.3 are good values
    336      * @param wheel
    337      * @param suspensionCompression
    338      */
    339     public void setSuspensionCompression(int wheel, float suspensionCompression) {
    340         wheels.get(wheel).setWheelsDampingCompression(suspensionCompression);
    341     }
    342 
    343     /**
    344      * @return the suspensionDamping
    345      */
    346     public float getSuspensionDamping() {
    347         return tuning.suspensionDamping;
    348     }
    349 
    350     /**
    351      * Use before adding wheels, this is the default used when adding wheels.
    352      * After adding the wheel, use direct wheel access.<br>
    353      * The damping coefficient for when the suspension is expanding.
    354      * See the comments for setSuspensionCompression for how to set k.
    355      * @param suspensionDamping the suspensionDamping to set
    356      */
    357     public void setSuspensionDamping(float suspensionDamping) {
    358         tuning.suspensionDamping = suspensionDamping;
    359     }
    360 
    361     /**
    362      * The damping coefficient for when the suspension is expanding.
    363      * See the comments for setSuspensionCompression for how to set k.
    364      * @param wheel
    365      * @param suspensionDamping
    366      */
    367     public void setSuspensionDamping(int wheel, float suspensionDamping) {
    368         wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping);
    369     }
    370 
    371     /**
    372      * @return the suspensionStiffness
    373      */
    374     public float getSuspensionStiffness() {
    375         return tuning.suspensionStiffness;
    376     }
    377 
    378     /**
    379      * Use before adding wheels, this is the default used when adding wheels.
    380      * After adding the wheel, use direct wheel access.<br>
    381      * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
    382      * @param suspensionStiffness
    383      */
    384     public void setSuspensionStiffness(float suspensionStiffness) {
    385         tuning.suspensionStiffness = suspensionStiffness;
    386     }
    387 
    388     /**
    389      * The stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
    390      * @param wheel
    391      * @param suspensionStiffness
    392      */
    393     public void setSuspensionStiffness(int wheel, float suspensionStiffness) {
    394         wheels.get(wheel).setSuspensionStiffness(suspensionStiffness);
    395     }
    396 
    397     /**
    398      * Reset the suspension
    399      */
    400     public void resetSuspension() {
    401         resetSuspension(vehicleId);
    402     }
    403 
    404     private native void resetSuspension(long vehicleId);
    405 
    406     /**
    407      * Apply the given engine force to all wheels, works continuously
    408      * @param force the force
    409      */
    410     public void accelerate(float force) {
    411         for (int i = 0; i < wheels.size(); i++) {
    412             accelerate(i, force);
    413         }
    414     }
    415 
    416     /**
    417      * Apply the given engine force, works continuously
    418      * @param wheel the wheel to apply the force on
    419      * @param force the force
    420      */
    421     public void accelerate(int wheel, float force) {
    422         applyEngineForce(vehicleId, wheel, force);
    423 
    424     }
    425 
    426     private native void applyEngineForce(long vehicleId, int wheel, float force);
    427 
    428     /**
    429      * Set the given steering value to all front wheels (0 = forward)
    430      * @param value the steering angle of the front wheels (Pi = 360deg)
    431      */
    432     public void steer(float value) {
    433         for (int i = 0; i < wheels.size(); i++) {
    434             if (getWheel(i).isFrontWheel()) {
    435                 steer(i, value);
    436             }
    437         }
    438     }
    439 
    440     /**
    441      * Set the given steering value to the given wheel (0 = forward)
    442      * @param wheel the wheel to set the steering on
    443      * @param value the steering angle of the front wheels (Pi = 360deg)
    444      */
    445     public void steer(int wheel, float value) {
    446         steer(vehicleId, wheel, value);
    447     }
    448 
    449     private native void steer(long vehicleId, int wheel, float value);
    450 
    451     /**
    452      * Apply the given brake force to all wheels, works continuously
    453      * @param force the force
    454      */
    455     public void brake(float force) {
    456         for (int i = 0; i < wheels.size(); i++) {
    457             brake(i, force);
    458         }
    459     }
    460 
    461     /**
    462      * Apply the given brake force, works continuously
    463      * @param wheel the wheel to apply the force on
    464      * @param force the force
    465      */
    466     public void brake(int wheel, float force) {
    467         brake(vehicleId, wheel, force);
    468     }
    469 
    470     private native void brake(long vehicleId, int wheel, float force);
    471 
    472     /**
    473      * Get the current speed of the vehicle in km/h
    474      * @return
    475      */
    476     public float getCurrentVehicleSpeedKmHour() {
    477         return getCurrentVehicleSpeedKmHour(vehicleId);
    478     }
    479 
    480     private native float getCurrentVehicleSpeedKmHour(long vehicleId);
    481 
    482     /**
    483      * Get the current forward vector of the vehicle in world coordinates
    484      * @param vector
    485      * @return
    486      */
    487     public Vector3f getForwardVector(Vector3f vector) {
    488         if (vector == null) {
    489             vector = new Vector3f();
    490         }
    491         getForwardVector(vehicleId, vector);
    492         return vector;
    493     }
    494 
    495     private native void getForwardVector(long objectId, Vector3f vector);
    496 
    497     /**
    498      * used internally
    499      */
    500     public long getVehicleId() {
    501         return vehicleId;
    502     }
    503 
    504     @Override
    505     protected Spatial getDebugShape() {
    506         Spatial shape = super.getDebugShape();
    507         Node node = null;
    508         if (shape instanceof Node) {
    509             node = (Node) shape;
    510         } else {
    511             node = new Node("DebugShapeNode");
    512             node.attachChild(shape);
    513         }
    514         int i = 0;
    515         for (Iterator<VehicleWheel> it = wheels.iterator(); it.hasNext();) {
    516             VehicleWheel physicsVehicleWheel = it.next();
    517             Vector3f location = physicsVehicleWheel.getLocation().clone();
    518             Vector3f direction = physicsVehicleWheel.getDirection().clone();
    519             Vector3f axle = physicsVehicleWheel.getAxle().clone();
    520             float restLength = physicsVehicleWheel.getRestLength();
    521             float radius = physicsVehicleWheel.getRadius();
    522 
    523             Arrow locArrow = new Arrow(location);
    524             Arrow axleArrow = new Arrow(axle.normalizeLocal().multLocal(0.3f));
    525             Arrow wheelArrow = new Arrow(direction.normalizeLocal().multLocal(radius));
    526             Arrow dirArrow = new Arrow(direction.normalizeLocal().multLocal(restLength));
    527             Geometry locGeom = new Geometry("WheelLocationDebugShape" + i, locArrow);
    528             Geometry dirGeom = new Geometry("WheelDirectionDebugShape" + i, dirArrow);
    529             Geometry axleGeom = new Geometry("WheelAxleDebugShape" + i, axleArrow);
    530             Geometry wheelGeom = new Geometry("WheelRadiusDebugShape" + i, wheelArrow);
    531             dirGeom.setLocalTranslation(location);
    532             axleGeom.setLocalTranslation(location.add(direction));
    533             wheelGeom.setLocalTranslation(location.add(direction));
    534             locGeom.setMaterial(debugMaterialGreen);
    535             dirGeom.setMaterial(debugMaterialGreen);
    536             axleGeom.setMaterial(debugMaterialGreen);
    537             wheelGeom.setMaterial(debugMaterialGreen);
    538             node.attachChild(locGeom);
    539             node.attachChild(dirGeom);
    540             node.attachChild(axleGeom);
    541             node.attachChild(wheelGeom);
    542             i++;
    543         }
    544         return node;
    545     }
    546 
    547     @Override
    548     public void read(JmeImporter im) throws IOException {
    549         InputCapsule capsule = im.getCapsule(this);
    550         tuning = new VehicleTuning();
    551         tuning.frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
    552         tuning.maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
    553         tuning.maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
    554         tuning.suspensionCompression = capsule.readFloat("suspensionCompression", 0.83f);
    555         tuning.suspensionDamping = capsule.readFloat("suspensionDamping", 0.88f);
    556         tuning.suspensionStiffness = capsule.readFloat("suspensionStiffness", 5.88f);
    557         wheels = capsule.readSavableArrayList("wheelsList", new ArrayList<VehicleWheel>());
    558         motionState.setVehicle(this);
    559         super.read(im);
    560     }
    561 
    562     @Override
    563     public void write(JmeExporter ex) throws IOException {
    564         OutputCapsule capsule = ex.getCapsule(this);
    565         capsule.write(tuning.frictionSlip, "frictionSlip", 10.5f);
    566         capsule.write(tuning.maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
    567         capsule.write(tuning.maxSuspensionForce, "maxSuspensionForce", 6000f);
    568         capsule.write(tuning.suspensionCompression, "suspensionCompression", 0.83f);
    569         capsule.write(tuning.suspensionDamping, "suspensionDamping", 0.88f);
    570         capsule.write(tuning.suspensionStiffness, "suspensionStiffness", 5.88f);
    571         capsule.writeSavableArrayList(wheels, "wheelsList", new ArrayList<VehicleWheel>());
    572         super.write(ex);
    573     }
    574 
    575     @Override
    576     protected void finalize() throws Throwable {
    577         super.finalize();
    578         Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing RayCaster {0}", Long.toHexString(rayCasterId));
    579         Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Finalizing Vehicle {0}", Long.toHexString(vehicleId));
    580         finalizeNative(rayCasterId, vehicleId);
    581     }
    582 
    583     private native void finalizeNative(long rayCaster, long vehicle);
    584 }
    585