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