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