World: Use an integrator to integrate the bodies. Extracted the integration code...
[scd.git] / src / net / habraun / sd / World.scala
blob6fcbbc43c12c69c5238b49aacd2f123e97ed3667
1 /*
2 Copyright (c) 2009 Hanno Braun <hanno@habraun.net>
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
19 package net.habraun.sd
23 import math._
25 import scala.collection.mutable._
29 /**
30 * The central class for the physics simulation.
31 * World is basically a container for objects, whose attributes it updates every simulation step.
34 class World {
36 private[this] val _bodies = new HashSet[Body]
40 /**
41 * Returns a copy of the body set as an Iterable.
44 def bodies: Iterable[Body] = _bodies.clone
48 /**
49 * The integrator is used to integrate the bodies.
52 private[this] var integrate: Integrator = new EulerIntegrator
54 def integrator = integrate
56 def integrator_=(i: Integrator) {
57 if (i == null) throw new NullPointerException("Integrator must not be null.")
58 integrate = i
63 /**
64 * The broad phase is used for detecting which bodys can possible collide.
65 * This is done to cut down the time spent on doing detailed collision checks.
68 private[this] var _broadPhase: BroadPhase = new SimpleBroadPhase
70 def broadPhase = _broadPhase
72 def broadPhase_=(bp: BroadPhase) = {
73 if (bp == null) throw new NullPointerException
74 _broadPhase = bp
79 /**
80 * The narrow phase is used to perform detailed (and possibly expensive) collision testing on body pairs
81 * that made it through the broad phase.
84 private[this] var _narrowPhase: NarrowPhase = new SimpleNarrowPhase
86 def narrowPhase = _narrowPhase
88 def narrowPhase_=(np: NarrowPhase) = {
89 if (np == null) throw new NullPointerException
90 _narrowPhase = np
95 /**
96 * Adds a body to the world. The body will be simulated until it is removed.
99 def add(body: Body) {
100 _bodies.addEntry(body)
106 * Removes the body from the world. The body will no longer be simulated.
109 def remove(body: Body) {
110 _bodies.removeEntry(body)
116 * Steps the physics simulation.
117 * All bodies are moved, according to their velocity and the forces that are applied to them.
120 def step(delta: Double) {
121 // Check if delta is valid.
122 if (delta < 0.0) throw new IllegalArgumentException("Time delta must be 0 or greater.")
124 // Integrate bodies.
125 _bodies.foreach(integrate(delta, _))
127 // Collision detection.
128 val possibleCollisionPairs = broadPhase.detectPossibleCollisions(_bodies.toList)
129 val possibleCollisions = possibleCollisionPairs.map((pair) => {
130 narrowPhase.inspectCollision(pair._1, pair._2)
133 // Compute collision effects.
134 // This is a tricky construction. The "possibleCollision <- collision" part is like an outer for loop
135 // that iterates through all collisions. Collisions is a list of Option[Collision], this means,
136 // during each iteration possibleCollision is either Some(collision) or None.
137 // The "collision <- possibleCollision" part is technically an inner loop that iterates through the
138 // Option[Collision]. This works because because Option is like a collection that contains either one
139 // (if if is an instance of Some) or no (if it is None) elements.
140 // Despite the long explanation, what this does is actually pretty simple: We loop through the list
141 // of possible collisions. We execute the yield stuff only for actual collisions, not for None.
142 for ( possibleCollision <- possibleCollisions; collision <- possibleCollision ) yield {
143 // Get the bodies out of the contact, so we can access them easier.
144 val b1 = collision.contact1.b
145 val b2 = collision.contact2.b
147 // Compute the part of the velocities that points in the direction of the collision normals.
148 val v1 = b1.velocity.project(collision.contact1.normal)
149 val v2 = b2.velocity.project(collision.contact2.normal)
151 // Apply impulses along the collision normals.
152 val m1 = b1.mass
153 val m2 = b2.mass
154 if (m1 == Double.PositiveInfinity) {
155 val impulse = (v1 - v2) * 2 * m2
156 b2.applyImpulse(impulse)
158 else if (m2 == Double.PositiveInfinity) {
159 val impulse = (v2 - v1) * 2 * m1
160 b1.applyImpulse(impulse)
162 else {
163 val impulse = (v2 - v1) * 2 * m1 * m2 / (m1 + m2)
164 b1.applyImpulse(impulse)
165 b2.applyImpulse(-impulse)
168 // If the time of impact given by the collision is smaller than 1.0, the bodies would overlap
169 // after the movement has been carried out. We don't want that, we want the bodies to stop right
170 // at the point of impact. Let's set them back, so the regular movement will put them right where
171 // we want them.
172 b1.position -= b1.velocity * delta * (1.0 - collision.t + 0.0001)
173 b2.position -= b2.velocity * delta * (1.0 - collision.t + 0.0001)