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