1/*
2 * Copyright (c) 2009-2012 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 */
32package com.jme3.bullet.objects;
33
34import com.bulletphysics.dynamics.RigidBody;
35import com.jme3.bullet.collision.PhysicsCollisionObject;
36import com.jme3.bullet.util.Converter;
37import com.jme3.export.*;
38import com.jme3.math.Quaternion;
39import com.jme3.math.Vector3f;
40import com.jme3.scene.Spatial;
41import java.io.IOException;
42
43/**
44 * Stores info about one wheel of a PhysicsVehicle
45 * @author normenhansen
46 */
47public class VehicleWheel implements Savable {
48
49    protected com.bulletphysics.dynamics.vehicle.WheelInfo wheelInfo;
50    protected boolean frontWheel;
51    protected Vector3f location = new Vector3f();
52    protected Vector3f direction = new Vector3f();
53    protected Vector3f axle = new Vector3f();
54    protected float suspensionStiffness = 20.0f;
55    protected float wheelsDampingRelaxation = 2.3f;
56    protected float wheelsDampingCompression = 4.4f;
57    protected float frictionSlip = 10.5f;
58    protected float rollInfluence = 1.0f;
59    protected float maxSuspensionTravelCm = 500f;
60    protected float maxSuspensionForce = 6000f;
61    protected float radius = 0.5f;
62    protected float restLength = 1f;
63    protected Vector3f wheelWorldLocation = new Vector3f();
64    protected Quaternion wheelWorldRotation = new Quaternion();
65    protected Spatial wheelSpatial;
66    protected com.jme3.math.Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f();
67    protected final Quaternion tmp_inverseWorldRotation = new Quaternion();
68    private boolean applyLocal = false;
69
70    public VehicleWheel() {
71    }
72
73    public VehicleWheel(Spatial spat, Vector3f location, Vector3f direction, Vector3f axle,
74            float restLength, float radius, boolean frontWheel) {
75        this(location, direction, axle, restLength, radius, frontWheel);
76        wheelSpatial = spat;
77    }
78
79    public VehicleWheel(Vector3f location, Vector3f direction, Vector3f axle,
80            float restLength, float radius, boolean frontWheel) {
81        this.location.set(location);
82        this.direction.set(direction);
83        this.axle.set(axle);
84        this.frontWheel = frontWheel;
85        this.restLength = restLength;
86        this.radius = radius;
87    }
88
89    public synchronized void updatePhysicsState() {
90        Converter.convert(wheelInfo.worldTransform.origin, wheelWorldLocation);
91        Converter.convert(wheelInfo.worldTransform.basis, tmp_Matrix);
92        wheelWorldRotation.fromRotationMatrix(tmp_Matrix);
93    }
94
95    public synchronized void applyWheelTransform() {
96        if (wheelSpatial == null) {
97            return;
98        }
99        Quaternion localRotationQuat = wheelSpatial.getLocalRotation();
100        Vector3f localLocation = wheelSpatial.getLocalTranslation();
101        if (!applyLocal && wheelSpatial.getParent() != null) {
102            localLocation.set(wheelWorldLocation).subtractLocal(wheelSpatial.getParent().getWorldTranslation());
103            localLocation.divideLocal(wheelSpatial.getParent().getWorldScale());
104            tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation);
105
106            localRotationQuat.set(wheelWorldRotation);
107            tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat);
108
109            wheelSpatial.setLocalTranslation(localLocation);
110            wheelSpatial.setLocalRotation(localRotationQuat);
111        } else {
112            wheelSpatial.setLocalTranslation(wheelWorldLocation);
113            wheelSpatial.setLocalRotation(wheelWorldRotation);
114        }
115    }
116
117    public com.bulletphysics.dynamics.vehicle.WheelInfo getWheelInfo() {
118        return wheelInfo;
119    }
120
121    public void setWheelInfo(com.bulletphysics.dynamics.vehicle.WheelInfo wheelInfo) {
122        this.wheelInfo = wheelInfo;
123        applyInfo();
124    }
125
126    public boolean isFrontWheel() {
127        return frontWheel;
128    }
129
130    public void setFrontWheel(boolean frontWheel) {
131        this.frontWheel = frontWheel;
132        applyInfo();
133    }
134
135    public Vector3f getLocation() {
136        return location;
137    }
138
139    public Vector3f getDirection() {
140        return direction;
141    }
142
143    public Vector3f getAxle() {
144        return axle;
145    }
146
147    public float getSuspensionStiffness() {
148        return suspensionStiffness;
149    }
150
151    /**
152     * the stiffness constant for the suspension.  10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car
153     * @param suspensionStiffness
154     */
155    public void setSuspensionStiffness(float suspensionStiffness) {
156        this.suspensionStiffness = suspensionStiffness;
157        applyInfo();
158    }
159
160    public float getWheelsDampingRelaxation() {
161        return wheelsDampingRelaxation;
162    }
163
164    /**
165     * the damping coefficient for when the suspension is expanding.
166     * See the comments for setWheelsDampingCompression for how to set k.
167     * @param wheelsDampingRelaxation
168     */
169    public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) {
170        this.wheelsDampingRelaxation = wheelsDampingRelaxation;
171        applyInfo();
172    }
173
174    public float getWheelsDampingCompression() {
175        return wheelsDampingCompression;
176    }
177
178    /**
179     * the damping coefficient for when the suspension is compressed.
180     * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.<br>
181     * k = 0.0 undamped & bouncy, k = 1.0 critical damping<br>
182     * 0.1 to 0.3 are good values
183     * @param wheelsDampingCompression
184     */
185    public void setWheelsDampingCompression(float wheelsDampingCompression) {
186        this.wheelsDampingCompression = wheelsDampingCompression;
187        applyInfo();
188    }
189
190    public float getFrictionSlip() {
191        return frictionSlip;
192    }
193
194    /**
195     * the coefficient of friction between the tyre and the ground.
196     * Should be about 0.8 for realistic cars, but can increased for better handling.
197     * Set large (10000.0) for kart racers
198     * @param frictionSlip
199     */
200    public void setFrictionSlip(float frictionSlip) {
201        this.frictionSlip = frictionSlip;
202        applyInfo();
203    }
204
205    public float getRollInfluence() {
206        return rollInfluence;
207    }
208
209    /**
210     * reduces the rolling torque applied from the wheels that cause the vehicle to roll over.
211     * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour.
212     * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over.
213     * You should also try lowering the vehicle's centre of mass
214     * @param rollInfluence the rollInfluence to set
215     */
216    public void setRollInfluence(float rollInfluence) {
217        this.rollInfluence = rollInfluence;
218        applyInfo();
219    }
220
221    public float getMaxSuspensionTravelCm() {
222        return maxSuspensionTravelCm;
223    }
224
225    /**
226     * the maximum distance the suspension can be compressed (centimetres)
227     * @param maxSuspensionTravelCm
228     */
229    public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) {
230        this.maxSuspensionTravelCm = maxSuspensionTravelCm;
231        applyInfo();
232    }
233
234    public float getMaxSuspensionForce() {
235        return maxSuspensionForce;
236    }
237
238    /**
239     * The maximum suspension force, raise this above the default 6000 if your suspension cannot
240     * handle the weight of your vehcile.
241     * @param maxSuspensionForce
242     */
243    public void setMaxSuspensionForce(float maxSuspensionForce) {
244        this.maxSuspensionForce = maxSuspensionForce;
245        applyInfo();
246    }
247
248    private void applyInfo() {
249        if (wheelInfo == null) {
250            return;
251        }
252        wheelInfo.suspensionStiffness = suspensionStiffness;
253        wheelInfo.wheelsDampingRelaxation = wheelsDampingRelaxation;
254        wheelInfo.wheelsDampingCompression = wheelsDampingCompression;
255        wheelInfo.frictionSlip = frictionSlip;
256        wheelInfo.rollInfluence = rollInfluence;
257        wheelInfo.maxSuspensionTravelCm = maxSuspensionTravelCm;
258        wheelInfo.maxSuspensionForce = maxSuspensionForce;
259        wheelInfo.wheelsRadius = radius;
260        wheelInfo.bIsFrontWheel = frontWheel;
261        wheelInfo.suspensionRestLength1 = restLength;
262    }
263
264    public float getRadius() {
265        return radius;
266    }
267
268    public void setRadius(float radius) {
269        this.radius = radius;
270        applyInfo();
271    }
272
273    public float getRestLength() {
274        return restLength;
275    }
276
277    public void setRestLength(float restLength) {
278        this.restLength = restLength;
279        applyInfo();
280    }
281
282    /**
283     * returns the object this wheel is in contact with or null if no contact
284     * @return the PhysicsCollisionObject (PhysicsRigidBody, PhysicsGhostObject)
285     */
286    public PhysicsCollisionObject getGroundObject() {
287        if (wheelInfo.raycastInfo.groundObject == null) {
288            return null;
289        } else if (wheelInfo.raycastInfo.groundObject instanceof RigidBody) {
290            System.out.println("RigidBody");
291            return (PhysicsRigidBody) ((RigidBody) wheelInfo.raycastInfo.groundObject).getUserPointer();
292        } else {
293            return null;
294        }
295    }
296
297    /**
298     * returns the location where the wheel collides with the ground (world space)
299     */
300    public Vector3f getCollisionLocation(Vector3f vec) {
301        Converter.convert(wheelInfo.raycastInfo.contactPointWS, vec);
302        return vec;
303    }
304
305    /**
306     * returns the location where the wheel collides with the ground (world space)
307     */
308    public Vector3f getCollisionLocation() {
309        return Converter.convert(wheelInfo.raycastInfo.contactPointWS);
310    }
311
312    /**
313     * returns the normal where the wheel collides with the ground (world space)
314     */
315    public Vector3f getCollisionNormal(Vector3f vec) {
316        Converter.convert(wheelInfo.raycastInfo.contactNormalWS, vec);
317        return vec;
318    }
319
320    /**
321     * returns the normal where the wheel collides with the ground (world space)
322     */
323    public Vector3f getCollisionNormal() {
324        return Converter.convert(wheelInfo.raycastInfo.contactNormalWS);
325    }
326
327    /**
328     * returns how much the wheel skids on the ground (for skid sounds/smoke etc.)<br>
329     * 0.0 = wheels are sliding, 1.0 = wheels have traction.
330     */
331    public float getSkidInfo() {
332        return wheelInfo.skidInfo;
333    }
334
335    /**
336     * returns how many degrees the wheel has turned since the last physics
337     * step.
338     */
339    public float getDeltaRotation() {
340        return wheelInfo.deltaRotation;
341    }
342
343    @Override
344    public void read(JmeImporter im) throws IOException {
345        InputCapsule capsule = im.getCapsule(this);
346        wheelSpatial = (Spatial) capsule.readSavable("wheelSpatial", null);
347        frontWheel = capsule.readBoolean("frontWheel", false);
348        location = (Vector3f) capsule.readSavable("wheelLocation", new Vector3f());
349        direction = (Vector3f) capsule.readSavable("wheelDirection", new Vector3f());
350        axle = (Vector3f) capsule.readSavable("wheelAxle", new Vector3f());
351        suspensionStiffness = capsule.readFloat("suspensionStiffness", 20.0f);
352        wheelsDampingRelaxation = capsule.readFloat("wheelsDampingRelaxation", 2.3f);
353        wheelsDampingCompression = capsule.readFloat("wheelsDampingCompression", 4.4f);
354        frictionSlip = capsule.readFloat("frictionSlip", 10.5f);
355        rollInfluence = capsule.readFloat("rollInfluence", 1.0f);
356        maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f);
357        maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f);
358        radius = capsule.readFloat("wheelRadius", 0.5f);
359        restLength = capsule.readFloat("restLength", 1f);
360    }
361
362    @Override
363    public void write(JmeExporter ex) throws IOException {
364        OutputCapsule capsule = ex.getCapsule(this);
365        capsule.write(wheelSpatial, "wheelSpatial", null);
366        capsule.write(frontWheel, "frontWheel", false);
367        capsule.write(location, "wheelLocation", new Vector3f());
368        capsule.write(direction, "wheelDirection", new Vector3f());
369        capsule.write(axle, "wheelAxle", new Vector3f());
370        capsule.write(suspensionStiffness, "suspensionStiffness", 20.0f);
371        capsule.write(wheelsDampingRelaxation, "wheelsDampingRelaxation", 2.3f);
372        capsule.write(wheelsDampingCompression, "wheelsDampingCompression", 4.4f);
373        capsule.write(frictionSlip, "frictionSlip", 10.5f);
374        capsule.write(rollInfluence, "rollInfluence", 1.0f);
375        capsule.write(maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f);
376        capsule.write(maxSuspensionForce, "maxSuspensionForce", 6000f);
377        capsule.write(radius, "wheelRadius", 0.5f);
378        capsule.write(restLength, "restLength", 1f);
379    }
380
381    /**
382     * @return the wheelSpatial
383     */
384    public Spatial getWheelSpatial() {
385        return wheelSpatial;
386    }
387
388    /**
389     * @param wheelSpatial the wheelSpatial to set
390     */
391    public void setWheelSpatial(Spatial wheelSpatial) {
392        this.wheelSpatial = wheelSpatial;
393    }
394
395    public boolean isApplyLocal() {
396        return applyLocal;
397    }
398
399    public void setApplyLocal(boolean applyLocal) {
400        this.applyLocal = applyLocal;
401    }
402}
403