From 42f7bc8f5be46b59b6944d8d660558bb312e1e54 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Mon, 23 Mar 2009 15:43:24 +0100 Subject: [PATCH] World: Use an integrator to integrate the bodies. Extracted the integration code into the new class EulerIntegrator. --- src/net/habraun/sd/EulerIntegrator.scala | 53 ++++++++++ src/net/habraun/sd/Integrator.scala | 38 +++++++ src/net/habraun/sd/World.scala | 40 +++---- test/net/habraun/sd/EulerIntegratorTest.scala | 145 ++++++++++++++++++++++++++ test/net/habraun/sd/WorldTest.scala | 144 ++++++------------------- 5 files changed, 285 insertions(+), 135 deletions(-) create mode 100644 src/net/habraun/sd/EulerIntegrator.scala create mode 100644 src/net/habraun/sd/Integrator.scala create mode 100644 test/net/habraun/sd/EulerIntegratorTest.scala diff --git a/src/net/habraun/sd/EulerIntegrator.scala b/src/net/habraun/sd/EulerIntegrator.scala new file mode 100644 index 0000000..53dab51 --- /dev/null +++ b/src/net/habraun/sd/EulerIntegrator.scala @@ -0,0 +1,53 @@ +/* + Copyright (c) 2009 Hanno Braun + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + + +package net.habraun.sd + + + +import math._ + + + +class EulerIntegrator extends Integrator { + + def apply(t: Double, body: Body) = { + var velocity = body.velocity + + // Apply forces. + velocity += body.appliedForce / body.mass * t + body.resetForce + + // Apply impulses. + velocity += body.appliedImpulse / body.mass + body.resetImpulse + + // Solve movement constraints. + val constrainedXVelocity = if (body.xMovementAllowed) velocity.x else 0.0 + val constrainedYVelocity = if (body.yMovementAllowed) velocity.y else 0.0 + val constrainedVelocity = Vec2D(constrainedXVelocity, constrainedYVelocity) + + // Set new velocity. + body.velocity = constrainedVelocity + + // Set new position. + body.position = body.position + (body.velocity * t) + + body + } +} diff --git a/src/net/habraun/sd/Integrator.scala b/src/net/habraun/sd/Integrator.scala new file mode 100644 index 0000000..82d9a12 --- /dev/null +++ b/src/net/habraun/sd/Integrator.scala @@ -0,0 +1,38 @@ +/* + Copyright (c) 2009 Hanno Braun + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + + +package net.habraun.sd + + + +/** + * Integrates a body's position and velocity. + */ + +trait Integrator extends Function2[Double, Body, Body] { + + /** + * Integrates a body. This function takes the following parameters: + * t: The time step. + * body: The body to integrate. + * + * The function returns the body it integrated. + */ + + def apply(t: Double, body: Body): Body +} diff --git a/src/net/habraun/sd/World.scala b/src/net/habraun/sd/World.scala index 6b44f25..6fcbbc4 100644 --- a/src/net/habraun/sd/World.scala +++ b/src/net/habraun/sd/World.scala @@ -43,6 +43,21 @@ class World { def bodies: Iterable[Body] = _bodies.clone + + + /** + * The integrator is used to integrate the bodies. + */ + + private[this] var integrate: Integrator = new EulerIntegrator + + def integrator = integrate + + def integrator_=(i: Integrator) { + if (i == null) throw new NullPointerException("Integrator must not be null.") + integrate = i + } + /** @@ -106,29 +121,8 @@ class World { // Check if delta is valid. if (delta < 0.0) throw new IllegalArgumentException("Time delta must be 0 or greater.") - // Apply forces and impulses, make sure speed constraints are fulfilled. - _bodies.foreach((body) => { - var velocity = body.velocity - - // Apply forces. - velocity += body.appliedForce / body.mass * delta - body.resetForce - - // Apply impulses. - velocity += body.appliedImpulse / body.mass - body.resetImpulse - - // Solve movement constraints. - val constrainedXVelocity = if (body.xMovementAllowed) velocity.x else 0.0 - val constrainedYVelocity = if (body.yMovementAllowed) velocity.y else 0.0 - val constrainedVelocity = Vec2D(constrainedXVelocity, constrainedYVelocity) - - // Set new velocity. - body.velocity = constrainedVelocity - - // Set new position. - body.position = body.position + (body.velocity * delta) - }) + // Integrate bodies. + _bodies.foreach(integrate(delta, _)) // Collision detection. val possibleCollisionPairs = broadPhase.detectPossibleCollisions(_bodies.toList) diff --git a/test/net/habraun/sd/EulerIntegratorTest.scala b/test/net/habraun/sd/EulerIntegratorTest.scala new file mode 100644 index 0000000..9359251 --- /dev/null +++ b/test/net/habraun/sd/EulerIntegratorTest.scala @@ -0,0 +1,145 @@ +/* + Copyright (c) 2009 Hanno Braun + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + + + +package net.habraun.sd + + + +import math._ + +import org.junit._ +import org.junit.Assert._ + + + +class EulerIntegratorTest { + + val t = 2.0 + + var integrate: Integrator = null + var body: Body = null + + + + @Before + def setup { + integrate = new EulerIntegrator + body = new Body + } + + + + @Test + def integrateCheckPosition { + body.velocity = Vec2D(1, 0) + body = integrate(t, body) + assertEquals(Vec2D(2, 0), body.position) + } + + + + @Test + def applyForceIntegrateCheckVelocity { + body.mass = 5 + body.applyForce(Vec2D(5, 0)) + body = integrate(t, body) + assertEquals(Vec2D(2, 0), body.velocity) + } + + + + @Test + def applyForceIntegrateTwiceCheckVelocity { + body.mass = 5 + body.applyForce(Vec2D(5, 0)) + body = integrate(t, body) + body = integrate(t, body) + assertEquals(Vec2D(2, 0), body.velocity) + } + + + + @Test + def applyForceIntegrateCheckPosition { + body.mass = 5 + body.applyForce(Vec2D(5, 0)) + body = integrate(t, body) + assertEquals(Vec2D(4, 0), body.position) + } + + + + @Test + def applyImpulseIntegrateCheckVelocity { + body.mass = 5 + body.velocity = Vec2D(3, 0) + body.applyImpulse(Vec2D(5, 0)) + body = integrate(t, body) + assertEquals(Vec2D(4, 0), body.velocity) + } + + + + @Test + def applyImpulseIntegrateCheckVelocity2 { + body.mass = 5 + body.velocity = Vec2D(3, 0) + body.applyImpulse(Vec2D(5, 0)) + body = integrate(5.0, body) + assertEquals(Vec2D(4, 0), body.velocity) + } + + + + @Test + def applyImpulseIntegrateCheckImpulse { + body.applyImpulse(Vec2D(10, 10)) + body = integrate(t, body) + assertEquals(Vec2D(0, 0), body.appliedImpulse) + } + + + + @Test + def applyImpulseToStaticBodyIntegrateCheckVelocity { + body.mass = Double.PositiveInfinity + body.applyImpulse(Vec2D(2, 0)) + body = integrate(t, body) + assertEquals(Vec2D(0, 0), body.velocity) + } + + + + @Test + def disallowXMovementIntegrateCheckPosition { + body.allowXMovement(false) + body.applyForce(Vec2D(1, 1)) + body = integrate(t, body) + assertEquals(Vec2D(0, 4), body.position) + } + + + + @Test + def disallowYMovementIntegrateCheckPosition { + body.allowYMovement(false) + body.applyForce(Vec2D(1, 1)) + body = integrate(t, body) + assertEquals(Vec2D(4, 0), body.position) + } +} diff --git a/test/net/habraun/sd/WorldTest.scala b/test/net/habraun/sd/WorldTest.scala index 7bf05f5..69b0a0a 100644 --- a/test/net/habraun/sd/WorldTest.scala +++ b/test/net/habraun/sd/WorldTest.scala @@ -33,147 +33,67 @@ import org.junit.Assert._ class WorldTest { @Test - def addBodyStepExpectBodyMoved { + def verifyInitialIntegrator { val world = new World - val body = new Body - body.velocity = Vec2D(1, 0) - world.add(body) - world.step(2.0) - assertEquals(Vec2D(2, 0), body.position) - } - - - - @Test - def addAndRemoveBodyExpectBodyNotMoved { - val world = new World - val body = new Body - body.velocity = Vec2D(1, 0) - world.add(body) - world.remove(body) - world.step(2.0) - assertEquals(Vec2D(0, 0), body.position) - } - - - - @Test - def addBodyApplyForceCheckVelocity { - val world = new World - val body = new Body - body.mass = 5 - body.applyForce(Vec2D(5, 0)) - world.add(body) - world.step(2.0) - assertEquals(Vec2D(2, 0), body.velocity) + assertTrue(world.integrator.isInstanceOf[EulerIntegrator]) } + - - @Test - def addBodyApplyForceStepTwiceCheckVelocity { + @Test { val expected = classOf[NullPointerException] } + def setIntegratorNullExpectException() { val world = new World - val body = new Body - body.mass = 5 - body.applyForce(Vec2D(5, 0)) - world.add(body) - world.step(2.0) - world.step(2.0) - assertEquals(Vec2D(2, 0), body.velocity) + world.integrator = null } @Test - def addBodyApplyForceStepCheckPosition { + def addBodyVerifyIsIntegrated { val world = new World - val body = new Body - body.mass = 5 - body.applyForce(Vec2D(5, 0)) - world.add(body) - world.step(2.0) - assertEquals(Vec2D(4, 0), body.position) - } - + val integrate = new Integrator { + var _t: Double = Double.NaN + var _body: Body = null + def apply(t: Double, body: Body) = { + _t = t + _body = body + body + } + } + world.integrator = integrate - @Test - def addBodyApplyImpulseCheckVelocity { - val world = new World val body = new Body - body.mass = 5 - body.velocity = Vec2D(3, 0) - body.applyImpulse(Vec2D(5, 0)) world.add(body) - world.step(2.0) - assertEquals(Vec2D(4, 0), body.velocity) - } + val t = 2.0 + world.step(t) - - @Test - def addBodyApplyImpulseCheckVelocity2 { - val world = new World - val body = new Body - body.mass = 5 - body.velocity = Vec2D(3, 0) - body.applyImpulse(Vec2D(5, 0)) - world.add(body) - world.step(5.0) - assertEquals(Vec2D(4, 0), body.velocity) + assertEquals(t, integrate._t, 0.0) + assertEquals(body, integrate._body) } @Test - def addBodyApplyImpulseCheckImpulse { + def addAndRemoveBodyVerifyIsNotIntegrated { val world = new World val body = new Body - body.applyImpulse(Vec2D(10, 10)) - world.add(body) - world.step(2.0) - assertEquals(Vec2D(0, 0), body.appliedImpulse) - } - - - - @Test - def applyImpulseToStaticBody { - val body = new Body - body.mass = Double.PositiveInfinity - body.applyImpulse(Vec2D(2, 0)) - - val world = new World - world.add(body) - world.step(2.0) - - assertEquals(Vec2D(0, 0), body.velocity) - } - + val integrate = new Integrator { + var integrated = false + def apply(t: Double, b: Body) = { + integrated = b == body + body + } + } + world.integrator = integrate - @Test - def addBodyDisallowXMovementStepCheckPosition { - val world = new World - val body = new Body - body.allowXMovement(false) - body.applyForce(Vec2D(1, 1)) world.add(body) + world.remove(body) world.step(2.0) - assertEquals(Vec2D(0, 4), body.position) - } - - - @Test - def addBodyDisallowYMovementStepCheckPosition { - val world = new World - val body = new Body - body.allowYMovement(false) - body.applyForce(Vec2D(1, 1)) - world.add(body) - world.step(2.0) - assertEquals(Vec2D(4, 0), body.position) + assertFalse(integrate.integrated) } -- 2.11.4.GIT