NarrowPhase: Don't pass time delta to inspectCollision.
[scd.git] / src / net / habraun / sd / World.scala
blob026a54724a22f4f82515c4de721312f82b3daa58
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 broad phase is used for detecting which bodys can possible collide.
50 * This is done to cut down the time spent on doing detailed collision checks.
53 private[this] var _broadPhase: BroadPhase = new SimpleBroadPhase
55 def broadPhase = _broadPhase
57 def broadPhase_=(bp: BroadPhase) = {
58 if (bp == null) throw new NullPointerException
59 _broadPhase = bp
64 /**
65 * The narrow phase is used to perform detailed (and possibly expensive) collision testing on body pairs
66 * that made it through the broad phase.
69 private[this] var _narrowPhase: NarrowPhase = new SimpleNarrowPhase
71 def narrowPhase = _narrowPhase
73 def narrowPhase_=(np: NarrowPhase) = {
74 if (np == null) throw new NullPointerException
75 _narrowPhase = np
80 /**
81 * Adds a body to the world. The body will be simulated until it is removed.
84 def add(body: Body) {
85 _bodies.addEntry(body)
90 /**
91 * Removes the body from the world. The body will no longer be simulated.
94 def remove(body: Body) {
95 _bodies.removeEntry(body)
101 * Steps the physics simulation.
102 * All bodies are moved, according to their velocity and the forces that are applied to them.
105 def step(delta: Double) {
106 // Check if delta is valid.
107 if (delta < 0.0) throw new IllegalArgumentException("Time delta must be 0 or greater.")
109 // Apply forces and impulses, make sure speed constraints are fulfilled.
110 _bodies.foreach((body) => {
111 var velocity = body.velocity
113 // Apply forces.
114 velocity += body.appliedForce / body.mass * delta
115 body.resetForce
117 // Apply impulses.
118 velocity += body.appliedImpulse / body.mass
119 body.resetImpulse
121 // Solve movement constraints.
122 val constrainedXVelocity = if (body.xMovementAllowed) velocity.x else 0.0
123 val constrainedYVelocity = if (body.yMovementAllowed) velocity.y else 0.0
124 val constrainedVelocity = Vec2D(constrainedXVelocity, constrainedYVelocity)
126 // Set new velocity.
127 body.velocity = constrainedVelocity
130 // Collision detection.
131 val possibleCollisionPairs = broadPhase.detectPossibleCollisions(_bodies.toList)
132 val possibleCollisions = possibleCollisionPairs.map((pair) => {
133 narrowPhase.inspectCollision(pair._1, pair._2)
136 // Compute collision effects.
137 // This is a tricky construction. The "possibleCollision <- collision" part is like an outer for loop
138 // that iterates through all collisions. Collisions is a list of Option[Collision], this means,
139 // during each iteration possibleCollision is either Some(collision) or None.
140 // The "collision <- possibleCollision" part is technically an inner loop that iterates through the
141 // Option[Collision]. This works because because Option is like a collection that contains either one
142 // (if if is an instance of Some) or no (if it is None) elements.
143 // Despite the long explanation, what this does is actually pretty simple: We loop through the list
144 // of possible collisions. We execute the yield stuff only for actual collisions, not for None.
145 for ( possibleCollision <- possibleCollisions; collision <- possibleCollision ) yield {
146 // Get the bodies out of the contact, so we can access them easier.
147 val b1 = collision.contact1.b
148 val b2 = collision.contact2.b
150 // Compute the part of the velocities that points in the direction of the collision normals.
151 val v1 = b1.velocity.project(collision.contact1.normal)
152 val v2 = b2.velocity.project(collision.contact2.normal)
154 // Apply impulses along the collision normals.
155 val m1 = b1.mass
156 val m2 = b2.mass
157 if (m1 == Double.PositiveInfinity) {
158 val impulse = (v1 - v2) * 2 * m2
159 b2.applyImpulse(impulse)
161 else if (m2 == Double.PositiveInfinity) {
162 val impulse = (v2 - v1) * 2 * m1
163 b1.applyImpulse(impulse)
165 else {
166 val impulse = (v2 - v1) * 2 * m1 * m2 / (m1 + m2)
167 b1.applyImpulse(impulse)
168 b2.applyImpulse(-impulse)
171 // If the time of impact given by the collision is smaller than 1.0, the bodies would overlap
172 // after the movement has been carried out. We don't want that, we want the bodies to stop right
173 // at the point of impact. Let's set them back, so the regular movement will put them right where
174 // we want them.
175 b1.position -= b1.velocity * delta * (1.0 - collision.t + 0.0001)
176 b2.position -= b2.velocity * delta * (1.0 - collision.t + 0.0001)
179 // Apply speed.
180 _bodies.foreach((body) => {
181 // Move bodies.
182 body.position = body.position + (body.velocity * delta)