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
25 import scala
.collection
.mutable
._
30 * The central class for the physics simulation.
31 * World is basically a container for objects, whose attributes it updates every simulation step.
36 private[this] val _bodies
= new HashSet
[Body
]
41 * Returns a copy of the body set as an Iterable.
44 def bodies
: Iterable
[Body
] = _bodies
.clone
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
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
81 * Adds a body to the world. The body will be simulated until it is removed.
85 _bodies
.addEntry(body
)
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
114 velocity
+= body
.appliedForce
/ body
.mass
* delta
118 velocity
+= body
.appliedImpulse
/ body
.mass
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
)
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.
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
)
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
175 b1
.position
-= b1
.velocity
* delta
* (1.0 - collision
.t
+ 0.0001)
176 b2
.position
-= b2
.velocity
* delta
* (1.0 - collision
.t
+ 0.0001)
180 _bodies
.foreach((body
) => {
182 body
.position
= body
.position
+ (body
.velocity
* delta
)