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 
PhysicsVehicle()74     public PhysicsVehicle() {
75     }
76 
PhysicsVehicle(CollisionShape shape)77     public PhysicsVehicle(CollisionShape shape) {
78         super(shape);
79     }
80 
PhysicsVehicle(CollisionShape shape, float mass)81     public PhysicsVehicle(CollisionShape shape, float mass) {
82         super(shape, mass);
83     }
84 
85     /**
86      * used internally
87      */
updateWheels()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      */
applyWheelTransforms()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
postRebuild()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      */
createVehicle(PhysicsSpace space)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      */
addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel)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      */
addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel)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      */
removeWheel(int wheel)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      */
getWheel(int wheel)204     public VehicleWheel getWheel(int wheel) {
205         return wheels.get(wheel);
206     }
207 
getNumWheels()208     public int getNumWheels() {
209         return wheels.size();
210     }
211 
212     /**
213      * @return the frictionSlip
214      */
getFrictionSlip()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      */
setFrictionSlip(float frictionSlip)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      */
setFrictionSlip(int wheel, float frictionSlip)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      */
setRollInfluence(int wheel, float rollInfluence)248     public void setRollInfluence(int wheel, float rollInfluence) {
249         wheels.get(wheel).setRollInfluence(rollInfluence);
250     }
251 
252     /**
253      * @return the maxSuspensionTravelCm
254      */
getMaxSuspensionTravelCm()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      */
setMaxSuspensionTravelCm(float maxSuspensionTravelCm)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      */
setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm)274     public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) {
275         wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm);
276     }
277 
getMaxSuspensionForce()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      */
setMaxSuspensionForce(float maxSuspensionForce)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      */
setMaxSuspensionForce(int wheel, float maxSuspensionForce)297     public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) {
298         wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce);
299     }
300 
301     /**
302      * @return the suspensionCompression
303      */
getSuspensionCompression()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      */
setSuspensionCompression(float suspensionCompression)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      */
setSuspensionCompression(int wheel, float suspensionCompression)329     public void setSuspensionCompression(int wheel, float suspensionCompression) {
330         wheels.get(wheel).setWheelsDampingCompression(suspensionCompression);
331     }
332 
333     /**
334      * @return the suspensionDamping
335      */
getSuspensionDamping()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      */
setSuspensionDamping(float suspensionDamping)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      */
setSuspensionDamping(int wheel, float suspensionDamping)357     public void setSuspensionDamping(int wheel, float suspensionDamping) {
358         wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping);
359     }
360 
361     /**
362      * @return the suspensionStiffness
363      */
getSuspensionStiffness()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      */
setSuspensionStiffness(float suspensionStiffness)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      */
setSuspensionStiffness(int wheel, float suspensionStiffness)383     public void setSuspensionStiffness(int wheel, float suspensionStiffness) {
384         wheels.get(wheel).setSuspensionStiffness(suspensionStiffness);
385     }
386 
387     /**
388      * Reset the suspension
389      */
resetSuspension()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      */
accelerate(float force)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      */
accelerate(int wheel, float force)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      */
steer(float value)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      */
steer(int wheel, float value)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      */
brake(float force)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      */
brake(int wheel, float force)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      */
getCurrentVehicleSpeedKmHour()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      */
getForwardVector(Vector3f vector)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      */
getVehicleId()478     public RaycastVehicle getVehicleId() {
479         return vehicle;
480     }
481 
482     @Override
destroy()483     public void destroy() {
484         super.destroy();
485     }
486 
487     @Override
getDebugShape()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
read(JmeImporter im)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
write(JmeExporter ex)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