Warning: you can't modify the world inside this callback.
8 | */
9 | def violation(body: Body)
10 | }
11 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/TimeStep.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | /**
4 | * A holder for time step information.
5 | */
6 | class TimeStep(
7 | // time step
8 | var dt: Float
9 | ) {
10 | // inverse time step (0 if dt == 0).
11 | var invDt = if (dt > 0f) 1 / dt else 0f
12 | var dtRatio = 0f
13 | var warmStarting = false
14 | var positionCorrection = false
15 | var maxIterations = 0
16 | }
17 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/ConstantForceController.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.controllers
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /** Applies a force every frame */
7 | abstract class ConstantForceController extends Controller {
8 | /* The force to apply */
9 | var F = Vector2.Zero
10 |
11 | def step(step: TimeStep) =
12 | forAwakeBodies(b => b.applyForce(F, b.worldCenter))
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/NullContact.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | import collision.Manifold
4 |
5 | // This lets us provide broadphase proxy pair user data for
6 | // contacts that shouldn't exist.
7 | object NullContact extends Contact(null, null) {
8 | def evaluate(listener: ContactListener) {}
9 | val manifolds = Array[Manifold]()
10 |
11 | override def computeTOI(sweep1: Sweep, sweep2: Sweep) = 1f
12 | }
13 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/ConstantAccelController.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.controllers
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /** Applies acceleration every frame */
7 | abstract class ConstantAccelController extends Controller {
8 | /* The acceleration to apply */
9 | var A = Vector2.Zero
10 |
11 | def step(step: TimeStep) =
12 | forAwakeBodies(_.linearVelocity += step.dt * A)
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/Readme.txt:
--------------------------------------------------------------------------------
1 | ScalaBox2D version 2.0.1
2 |
3 | ScalaBox2D is a 2D physics engine for games.
4 | It is a Scala port of JBox2D, which is a Java port of Box2D.
5 |
6 | Copyright (c) 2009, Erkki Lindpere
7 | All rights reserved.
8 |
9 | This software is distributed under the zlib license. See the file License.txt for the full text.
10 |
11 | JBox2D is a port of Box2D
12 | http://www.jbox2d.org/
13 |
14 | Box2D is a 2D physics engine for games created by Erin Catto
15 | http://www.box2d.org
16 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/SVGApp.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed
2 |
3 | import svg._
4 | import draw.DrawFlags._
5 |
6 | object SVGApp {
7 | val scale = 15
8 |
9 | def main(args: Array[String]) {
10 | val loader = new SlickSVGSceneLoader("C:/drawing-1.svg", scale)
11 | val world = loader.create
12 | println("Done!")
13 | val flags = Shapes
14 | slick.SlickDisplayWorld.runWithSimulation(world, false, flags)
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Insert title here
6 |
7 |
8 | This package contains broad and narrow phase collision detection.
9 |
10 | In the Java version, this also contained shapes, which are now in a separate package.
11 |
12 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/Steppable.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | /**
4 | * This interface allows registration within a JBox2d World
5 | * to be run immediately after the physics step. This is
6 | * useful if you need to do something every step, but would
7 | * prefer not to have to manually code your step routine
8 | * differently, instead letting the engine handle the calling.
9 | */
10 | trait Steppable {
11 | def step(dt: Float, iterations: Int)
12 | }
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/Jacobian.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath.Vector2
4 |
5 | object Jacobian {
6 | val Zero = Jacobian(Vector2.Zero, 0, Vector2.Zero, 0)
7 | }
8 |
9 | case class Jacobian(
10 | linear1: Vector2,
11 | angular1: Float,
12 | linear2: Vector2,
13 | angular2: Float
14 | ) {
15 | def compute(x1: Vector2, a1: Float, x2: Vector2, a2: Float) =
16 | linear1 ∙ x1 + angular1 * a1 + linear2 ∙ x2 + angular2 * a2
17 | }
18 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/Mass.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath.Vector2
4 |
5 | object Mass {
6 | val Zero = Mass(0f, Vector2.Zero, 0f)
7 | }
8 |
9 | /** This holds the mass data computed for a shape. */
10 | case class Mass(
11 | /** The mass of the shape, usually in kilograms. */
12 | mass: Float,
13 | /** The position of the shape's centroid relative to the shape's origin. */
14 | center: Vector2,
15 | /** The rotational inertia of the shape. */
16 | I: Float
17 | )
18 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/package.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Insert title here
6 |
7 |
8 | This package performs contact handling, and is essentially internal to JBox2d.
9 |
10 | The only reason you would ever need to dip into the package is to add additional shapes or object types to JBox2d.
11 |
12 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/Proxy.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision.broadphase
2 |
3 | class Proxy {
4 | val lowerBounds = Array(0,0)
5 | val upperBounds = Array(0,0)
6 | var overlapCount = BroadPhase.Invalid
7 | var timeStamp = 0
8 | var categoryBits = 0
9 | var maskBits = 0
10 | var groupIndex = 0
11 | var userData: AnyRef = null
12 |
13 | def next = lowerBounds(0)
14 | def next_=(next: Int) = lowerBounds(0) = next
15 |
16 | def isValid = overlapCount != BroadPhase.Invalid
17 | }
18 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/ExampleContactPoint.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed
2 |
3 | import box2d.collision.ContactID;
4 | import box2d.shapes.Shape;
5 | import vecmath.Vector2;
6 |
7 | /**
8 | * Holder for storing contact information.
9 | */
10 | class ExampleContactPoint {
11 | var shape1: Shape = null
12 | var shape2: Shape = null
13 | var normal = Vector2.Zero
14 | var position = Vector2.Zero
15 | var velocity = Vector2.Zero
16 | var id = ContactID.Zero
17 | var state = 0 // 0-add, 1-persist, 2-remove
18 | }
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/Vector2Times.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | /**
4 | * TODO inclusive / exclusive
5 | */
6 | class Vector2Times(start: Vector2, times: Int, step: Vector2)
7 | extends Iterable[Vector2] {
8 |
9 | def elements = new Iterator[Vector2] {
10 | var v = start
11 | var n = times
12 | def hasNext = n >= 0
13 | def next = {
14 | var ov = v
15 | v += step
16 | n -= 1
17 | ov
18 | }
19 | }
20 |
21 | def by(step: Vector2) = new Vector2Times(start, times, step)
22 | }
23 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/TestbedScene.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes._
6 | import dynamics._
7 | import dsl._
8 |
9 | trait TestbedScene extends SceneFactory {
10 | final def create = {
11 | val world = createWorld
12 | createScene(world)
13 | world
14 | }
15 |
16 | def createScene(implicit world: World)
17 |
18 | protected def createWorld = new World(
19 | AABB((-200, -100), (200, 200)),
20 | (0, -10),
21 | true
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/PairListener.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision.broadphase
2 |
3 | trait PairListener {
4 | // This should return the new pair user data. It is okay if the
5 | // user data is null.
6 | def pairAdded(proxyUserData1: AnyRef, proxyUserData2: AnyRef): AnyRef
7 |
8 | // This should free the pair's user data. In extreme circumstances, it is
9 | // possible
10 | // this will be called with null pairUserData because the pair never
11 | // existed.
12 | def pairRemoved(proxyUserData1: AnyRef, proxyUserData2: AnyRef, pairUserData: AnyRef)
13 | }
14 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/ContactFilter.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import shapes.Shape
4 |
5 | /**
6 | * Implement this class to provide collision filtering. In other words, you can implement
7 | * this class if you want finer control over contact creation.
8 | */
9 | trait ContactFilter {
10 | /**
11 | * Return true if contact calculations should be performed between these two shapes.
12 | * @warning for performance reasons this is only called when the AABBs begin to overlap.
13 | */
14 | def shouldCollide(fixture1: Fixture, fixture2: Fixture): Boolean
15 | }
16 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactEdge.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | /**
4 | * A contact edge is used to connect bodies and contacts together
5 | * in a contact graph where each body is a node and each contact
6 | * is an edge. A contact edge belongs to a doubly linked list
7 | * maintained in each attached body. Each contact has two contact
8 | * nodes, one for each attached body.
9 | */
10 | case class ContactEdge(
11 | /** Provides quick access to the other body attached. */
12 | other: Body,
13 | /** The contact. */
14 | contact: Contact
15 | )
16 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/Material.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | object Material {
4 | val Default = Material(0.2f, 0f, 0f)
5 | }
6 |
7 | /**
8 | * A material that can be applied to a fixture definition.
9 | */
10 | case class Material(
11 | /** The friction coefficient, usually in the range [0,1]. */
12 | friction: Float,
13 | /** The restitution (elasticity) usually in the range [0,1]. */
14 | restitution: Float,
15 | /** The density, usually in kg/m^2. */
16 | density: Float
17 | ) {
18 | def depth(z: Float) = Material(friction, restitution, density * z)
19 | }
20 |
--------------------------------------------------------------------------------
/scalabox2d/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.villane.box2d
6 | scalabox2d-parent
7 | 2.0.1-SNAPSHOT
8 |
9 | org.villane.box2d
10 | scalabox2d
11 | ScalaBox2D - 2D Physics Engine for Games
12 | 2.0.1-SNAPSHOT
13 |
14 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/SupportsGenericDistance.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 |
5 | /**
6 | * A shape that implements this interface can be used in distance calculations
7 | * for continuous collision detection. This does not remove the necessity of
8 | * specialized penetration calculations when CCD is not in effect, however.
9 | */
10 | trait SupportsGenericDistance {
11 | def support(xf: Transform2, v: Vector2): Vector2
12 | def getFirstVertex(xf: Transform2): Vector2
13 | }
14 |
15 | trait SupportsNewDistance extends Shape {
16 | def vertex(index: Int) = Vector2.Zero
17 | def support(v: Vector2) = 0
18 | }
19 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/Bound.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision.broadphase
2 |
3 | case class Bound(
4 | var value: Int,
5 | var proxyId: Int
6 | ) {
7 |
8 | def this(b: Bound) = {
9 | this(b.value, b.proxyId)
10 | stabbingCount = b.stabbingCount
11 | }
12 |
13 | var stabbingCount = 0
14 |
15 | def isLower = (value & 1) == 0
16 |
17 | def isUpper = (value & 1) == 1
18 |
19 | override def toString = {
20 | var ret = "Bound variable:\n";
21 | ret += "value: " + value + "\n";
22 | ret += "proxyId: " + proxyId + "\n";
23 | ret += "stabbing count: " + stabbingCount + "\n";
24 | ret
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/VaryingRestitution.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object VaryingRestitution extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | body {
11 | pos(0.0f, -10.0f)
12 | box(50.0f, 10.0f)
13 | }
14 |
15 | val sd = circle(0.6f) density 5.0f
16 | val restitution = Array(0.0f, 0.1f, 0.3f, 0.5f, 0.75f, 0.9f, 1.0f)
17 |
18 | for (i <- 0 until restitution.length) body {
19 | pos(-10.0f + 3.0f * i, 10.0f)
20 | fixture(sd) restitution restitution(i)
21 | massFromShapes
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/IslandSolverWorker.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import java.util.concurrent._
4 |
5 | object IslandSolverWorker {
6 | // Single work queue
7 | val workQueue = new LinkedBlockingQueue[FutureTask[Int]]
8 |
9 | var workers: List[IslandSolverWorker] = Nil
10 | for (i <- 1 until Settings.numThreads) {
11 | val worker = new IslandSolverWorker()
12 | workers ::= worker
13 | worker.start
14 | }
15 | def stopWorkers = workers foreach { w => w.shouldStop = true; w.interrupt }
16 | }
17 |
18 | class IslandSolverWorker extends Thread {
19 | var shouldStop = false
20 | override def run = while (!shouldStop) IslandSolverWorker.workQueue.take.run
21 | }
22 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/draw/Color3f.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.draw
2 |
3 | // make sure we use actual Float even if Float is replaced with Double
4 | import scala.Float
5 |
6 | /** TODO rename to Color4 ?*/
7 | object Color3f {
8 | def apply(r: Float, g: Float, b: Float): Color3f = Color3f(r, g, b, 255)
9 | /** Create a color specifying RGB components from 0.0f to 1.0f */
10 | def ratio(r: Float, g: Float, b: Float) = Color3f(255 * r, 255 * g, 255 * b)
11 | def ratio(r: Float, g: Float, b: Float, a: Float) = Color3f(255 * r, 255 * g, 255 * b, 255 * a)
12 | }
13 |
14 | /**
15 | * Color of RGB components where white is (255,255,255) and black is (0,0,0)
16 | */
17 | case class Color3f(r: Float, g: Float, b: Float, a: Float)
18 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/FilterData.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | object FilterData {
4 | val Default = FilterData(0x0001, 0xFFFF, 0)
5 | }
6 |
7 | /** This holds contact filtering data. */
8 | case class FilterData(
9 | /** The collision category bits. Normally you would just set one bit. */
10 | categoryBits: Int,
11 | /**
12 | * The collision mask bits. This states the categories that this
13 | * shape would accept for collision.
14 | */
15 | maskBits: Int,
16 | /**
17 | * Collision groups allow a certain group of objects to never collide (negative)
18 | * or always collide (positive). Zero means no collision group. Non-zero group
19 | * filtering always wins against the mask bits.
20 | */
21 | groupIndex: Int
22 | )
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactResult.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | import vecmath.Vector2
4 | import shapes.Shape
5 | import collision.ContactID
6 |
7 | /** This structure is used to report contact point results. */
8 | case class ContactResult(
9 | /** The first shape */
10 | fixture1: Fixture,
11 | /** The second shape */
12 | fixture2: Fixture,
13 | /** Position in world coordinates */
14 | pos: Vector2,
15 | /** Points from shape1 to shape2 */
16 | normal: Vector2,
17 | /** The normal impulse applied to body2 */
18 | normalImpulse: Float,
19 | /** The tangent impulse applied to body2 */
20 | tangentImpulse: Float,
21 | /** The contact id identifies the features in contact */
22 | id: ContactID
23 | )
24 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Pyramid.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Pyramid extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | body {
11 | pos(0, -10)
12 | box(50, 10)
13 | }
14 |
15 | val box1 = box(0.5f, 0.5f) density 5 restitution 0 friction 0.9f
16 |
17 | val sx = (-10.0f, 0.75f)
18 | val Δx = (0.5625f, 2.0f)
19 | val ex = sx + Δx * 25
20 | val Δy = (1.125f, 0.0f)
21 |
22 | for {
23 | x <- sx to ex by Δx
24 | y <- x to (x + Δy * (ex.y - x.y) / Δx.y) by Δy
25 | } body {
26 | pos(y)
27 | fixture(box1)
28 | massFromShapes
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/tests/VerticalStack.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.tests
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | class VerticalStack(_parent: TestbedMain) extends AbstractExample(_parent) {
7 | var firstTime = true
8 | val name = "Vertical Stack"
9 | override val getExampleInstructions = "Press , to shoot sideways bullet\n"
10 |
11 | override def postStep() {
12 | if (newKeyDown(0x33)) {
13 | val rnd = new scala.util.Random
14 | launchBomb((-20.0f, 1f + rnd.nextFloat * 9),
15 | (750.0f, rnd.nextFloat * 10 - 5))
16 | }
17 | }
18 |
19 | def create = {
20 | if (firstTime) {
21 | setCamera(0f, 10f, 15f)
22 | firstTime = false
23 | }
24 |
25 | scenes.VerticalStack.create
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/FloatExtensions.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | import Preamble._
4 |
5 | /**
6 | * Extension methods for Float, mostly binary operators taking a float and a vector.
7 | *
8 | * This class is not meant to be used explicitly.
9 | */
10 | final class FloatExtensions(a: Float) {
11 | final def +(v: Vector2) = v + a
12 | final def -(v: Vector2) = Vector2(a - v.x, a - v.y)
13 | final def *(v: Vector2) = v * a
14 | final def /(v: Vector2) = Vector2(a / v.x, a / v.y)
15 |
16 | final def cross(v: Vector2) = Vector2(-a * v.y, a * v.x)
17 | final def ×(v: Vector2) = Vector2(-a * v.y, a * v.x)
18 |
19 | final def clamp(low: Float, high: Float) = Preamble.clamp(a, low, high)
20 |
21 | final def isValid = a != Float.NaN && a != Float.NegativeInfinity && a != Float.PositiveInfinity
22 | }
23 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Overhang.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Overhang extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | body {
11 | pos(0.0f, -10.0f)
12 | box(50.0f, 10.0f)
13 | }
14 |
15 | val w = 4.0f
16 | val h = 0.25f
17 | val sd = box(w, h) density 1 friction 0.3f restitution 0
18 |
19 | val numSlats = 8
20 | val eps = 0.14f
21 | var lastCMX = 0.0f
22 | for (i <- 0 until numSlats) body {
23 | val newX = lastCMX + w - eps
24 | lastCMX = (i * lastCMX + newX) / (i + 1)
25 | pos(newX, 0.25f + 2 * h * (numSlats - i - 1))
26 | fixture(sd)
27 | massFromShapes
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactConstraint.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | import vecmath.Vector2
4 | import collision.Manifold
5 | import dynamics.Body
6 |
7 | class ContactConstraint(
8 | val points: Array[ContactConstraintPoint],
9 | val normal: Vector2,
10 | val manifold: Manifold,
11 | val body1: Body,
12 | val body2: Body,
13 | val friction: Float,
14 | val restitution: Float
15 | )
16 |
17 | class ContactConstraintPoint {
18 | var localAnchor1 = Vector2.Zero
19 | var localAnchor2 = Vector2.Zero
20 | var r1 = Vector2.Zero
21 | var r2 = Vector2.Zero
22 | var normalImpulse = 0f
23 | var tangentImpulse = 0f
24 | var positionImpulse = 0f
25 | var normalMass = 0f
26 | var tangentMass = 0f
27 | var equalizedMass = 0f
28 | var separation = 0f
29 | var velocityBias = 0f
30 | }
31 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/PointingDeviceJointDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath.Vector2
4 |
5 | class PointingDeviceJointDef extends JointDef {
6 | /**
7 | * The initial world target point. This is assumed
8 | * to coincide with the body anchor initially.
9 | */
10 | var target = Vector2.Zero
11 |
12 | /**
13 | * The maximum constraint force that can be exerted
14 | * to move the candidate body. Usually you will express
15 | * as some multiple of the weight (multiplier * mass * gravity).
16 | */
17 | var maxForce = 0f
18 |
19 | /** The response speed. */
20 | var frequencyHz = 5f
21 |
22 | /** The damping ratio. 0 = no damping, 1 = critical damping. */
23 | var dampingRatio = 0.7f
24 |
25 | /** The time step used in the simulation. */
26 | var timeStep = 1f / 60f
27 | }
28 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactPoint.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | import vecmath.Vector2
4 | import shapes.Shape
5 | import collision.ContactID
6 |
7 | case class ContactPoint(
8 | /** The first shape */
9 | fixture1: Fixture,
10 | /** The second shape */
11 | fixture2: Fixture,
12 | /** Position in world coordinates */
13 | pos: Vector2,
14 | /** Velocity of point on body2 relative to point on body1 (pre-solver) */
15 | velocity: Vector2,
16 | /** Points from shape1 to shape2 */
17 | normal: Vector2,
18 | /** The separation is negative when shapes are touching */
19 | separation: Float,
20 | /** The combined friction coefficient */
21 | friction: Float,
22 | /** The combined restitution coefficient */
23 | restitution: Float,
24 | /** The contact id identifies the features in contact */
25 | id: ContactID
26 | )
27 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Chain.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Chain extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | val ground = body {
11 | pos(0.0f, -10.0f)
12 | box(50.0f, 10.0f)
13 | }
14 |
15 | val sd = box(0.6f, 0.125f) density 20.0f friction 0.2f
16 | body {
17 | val y = 25.0f
18 | var prevBody = ground
19 | for (i <- 0 until 30) {
20 | val bd = body {
21 | pos(0.5f + i, y)
22 | fixture(sd)
23 | massFromShapes
24 | }
25 |
26 | joint (
27 | revolute(prevBody -> bd)
28 | anchor(i, y)
29 | collideConnected(false)
30 | )
31 | prevBody = bd
32 | }
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/Transform2.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | object Transform2 {
4 | val Identity = Transform2(Vector2.Zero, Matrix22.Identity)
5 |
6 | /**
7 | * Creates a transform from a position and an angle.
8 | */
9 | def apply(pos: Vector2, angle: Float): Transform2 = Transform2(pos, Matrix22.rotation(angle))
10 | }
11 |
12 | case class Transform2(
13 | /** Translation component of the transformation */
14 | pos: Vector2,
15 | /** Rotation component of the transformation */
16 | rot: Matrix22
17 | ) {
18 | def *(v: Vector2) = Vector2(
19 | pos.x + rot.a11 * v.x + rot.a12 * v.y,
20 | pos.y + rot.a21 * v.x + rot.a22 * v.y
21 | ) // = pos + (rot * v)
22 | def **(v: Vector2) = {
23 | val v1x = v.x - pos.x
24 | val v1y = v.y - pos.y
25 | Vector2(v1x * rot.a11 + v1y * rot.a21, v1x * rot.a12 + v1y * rot.a22)
26 | } // = rot ** (v - pos)
27 | }
28 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/DefaultContactFilter.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import shapes._
4 |
5 | /**
6 | * Default contact filter, using groupIndex, maskBits and categoryBits as detailed
7 | * in Box2d manual.
8 | */
9 | object DefaultContactFilter extends ContactFilter {
10 | /**
11 | * Return true if contact calculations should be performed between these two shapes.
12 | * If you implement your own collision filter you may want to build from this implementation.
13 | */
14 | def shouldCollide(fixture1: Fixture, fixture2: Fixture) = {
15 | val filter1 = fixture1.filter
16 | val filter2 = fixture2.filter
17 |
18 | if (filter1.groupIndex == filter2.groupIndex && filter1.groupIndex != 0)
19 | filter1.groupIndex > 0
20 | else
21 | (filter1.maskBits & filter2.categoryBits) != 0 && (filter1.categoryBits & filter2.maskBits) != 0
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com
2 |
3 | This software is provided 'as-is', without any express or implied
4 | warranty. In no event will the authors be held liable for any damages
5 | arising from the use of this software.
6 |
7 | Permission is granted to anyone to use this software for any purpose,
8 | including commercial applications, and to alter it and redistribute it
9 | freely, subject to the following restrictions:
10 |
11 | 1. The origin of this software must not be misrepresented; you must not
12 | claim that you wrote the original software. If you use this software
13 | in a product, an acknowledgment in the product documentation would be
14 | appreciated but is not required.
15 | 2. Altered source versions must be plainly marked as such, and must not be
16 | misrepresented as being the original software.
17 | 3. This notice may not be removed or altered from any source distribution.
18 |
19 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/DestructionListener.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import shapes.Shape
4 | import joints.Joint
5 | import controllers.Controller
6 |
7 | /**
8 | * Joints and shapes are destroyed when their associated
9 | * body is destroyed. Implement this listener so that you
10 | * may nullify references to these joints and shapes.
11 | */
12 | trait DestructionListener {
13 | /**
14 | * Called when any joint is about to be destroyed due
15 | * to the destruction of one of its attached bodies.
16 | */
17 | def sayGoodbye(joint: Joint)
18 |
19 | /**
20 | * Called when any fixture is about to be destroyed due
21 | * to the destruction of its parent body.
22 | */
23 | def sayGoodbye(fixture: Fixture)
24 |
25 | /**
26 | * Called when any controller is about to be destroyed due
27 | * to the destruction of its parent body.
28 | */
29 | def sayGoodbye(controller: Controller)
30 | }
31 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/broadphase/Pair.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision.broadphase
2 |
3 | object Pair {
4 | val Buffered = 0x0001
5 | val Removed = 0x0002
6 | val Final = 0x0004
7 | }
8 |
9 | case class Pair(var proxyId1: Int, var proxyId2: Int) extends Ordered[Pair] {
10 | var userData: AnyRef = null
11 | var status = 0
12 |
13 | def setBuffered() = status |= Pair.Buffered
14 | def clearBuffered() = status &= ~Pair.Buffered
15 | def isBuffered = (status & Pair.Buffered) == Pair.Buffered
16 |
17 | def setRemoved() = status |= Pair.Removed
18 | def clearRemoved() = status &= ~Pair.Removed
19 | def isRemoved = (status & Pair.Removed) == Pair.Removed
20 |
21 | def setFinal() = status |= Pair.Final
22 | def isFinal = (status & Pair.Final) == Pair.Final
23 |
24 | def compare(p: Pair) = {
25 | val d = proxyId1 - p.proxyId1
26 | if (d != 0)
27 | d
28 | else
29 | proxyId2 - p.proxyId2
30 | }
31 | }
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/Point.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import Settings.ε
6 |
7 | /**
8 | * Like a circle shape of zero radius, except that it has a finite mass.
9 | */
10 | class Point(val pos: Vector2, val mass: Float) extends Shape {
11 | // TODO Erkki is this good? Should ask on forums
12 | val radius = Settings.polygonRadius
13 |
14 | def testPoint(t: Transform2, p: Vector2) = false
15 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float) =
16 | SegmentCollide.Miss
17 |
18 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2) =
19 | (0f,Vector2.Zero)
20 |
21 | def computeAABB(t: Transform2) = {
22 | val p = t * pos
23 | AABB(p - ε, p + ε)
24 | }
25 |
26 | def computeSweepRadius(pivot: Vector2) = (pos - pivot).length + Settings.toiSlop
27 |
28 | def computeMass(density: Float) = Mass(mass, pos, 0)
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/GravityController.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.controllers
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /** Applies simplified gravity between every pair of bodies */
7 | abstract class GravityController extends Controller {
8 | /** Specifies the strength of the gravitiation force */
9 | var G = 0f
10 |
11 | /** If true, gravity is proportional to r^-2, otherwise r^-1 */
12 | var invSqr = false
13 |
14 | def step(step: TimeStep) =
15 | for (b1 <- bodies; b2 <- bodies) if (b1 != b2) {
16 | val d = b2.worldCenter - b1.worldCenter
17 | val r2 = d.lengthSquared
18 | if (r2 >= Settings.Epsilon) {
19 | val f = if (invSqr)
20 | G / r2 / sqrt(r2) * b1.mass * b2.mass * d
21 | else
22 | G / r2 * b1.mass * b2.mass * d
23 | b1.applyForce(f, b1.worldCenter)
24 | b2.applyForce(-1.0f * f, b2.worldCenter)
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/ContactID.scala:
--------------------------------------------------------------------------------
1 | /* TODO HEADER */
2 | package org.villane.box2d.collision
3 |
4 | object ContactID {
5 | val Zero = ContactID(0, 0, 0, false)
6 | val NullFeature = Int.MaxValue
7 | }
8 |
9 | /**
10 | * Contact ids to facilitate warm starting.
11 | * The features that intersect to form the contact point
12 | */
13 | case class ContactID(
14 | /** The edge that defines the outward contact normal. */
15 | referenceEdge: Int,
16 | /** The edge most anti-parallel to the reference edge. */
17 | incidentEdge: Int,
18 | /** The vertex (0 or 1) on the incident edge that was clipped. */
19 | incidentVertex: Int,
20 | /** True indicates that the reference edge is on shape2. */
21 | flip: Boolean
22 | ) {
23 | /** Returns a new ContactID based on this one, but with the features.flip value changed. */
24 | def withFlip(flip: Boolean) = ContactID(
25 | this.referenceEdge,
26 | this.incidentEdge,
27 | this.incidentVertex,
28 | flip
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/PlanetGravityController.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.controllers
2 |
3 | import vecmath.Preamble._
4 | import dsl.DSL._
5 |
6 | class PlanetGravityController extends Controller with SensorManagedBodies {
7 | /** Specifies the strength of the gravitiation force */
8 | var G = 0f
9 |
10 | /** If true, gravity is proportional to r^-2, otherwise r^-1 */
11 | var invSqr = false
12 |
13 | def createSensor(body: Body, radius: Float) = {
14 | body.createFixture(circle(radius) sensor true)
15 | sensor = body
16 | }
17 |
18 | def step(step: TimeStep) = forAwakeBodies { body =>
19 | val d = sensor.worldCenter - body.worldCenter
20 | val r2 = d.lengthSquared
21 | if (r2 >= Settings.Epsilon) {
22 | val sm = if (sensor.isDynamic) sensor.mass else 1f
23 | val f = if (invSqr)
24 | G / r2 / sqrt(r2) * body.mass * sm * d
25 | else
26 | G / r2 * body.mass * sm * d
27 | body.applyForce(f, body.worldCenter)
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/Vector2Range.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | /**
4 | * TODO inclusive / exclusive, negative step
5 | */
6 | class Vector2Range(start: Vector2, end: Vector2, step: Vector2)
7 | extends Iterable[Vector2] {
8 |
9 | def elements = new Iterator[Vector2] {
10 | var v = start
11 | def hasNext = inInterval(v)
12 | def next = {
13 | var n = v
14 | v += step
15 | n
16 | }
17 | }
18 |
19 | def by(step: Vector2) = new Vector2Range(start, end, step)
20 |
21 | /*override def foreach(f: Vector2 => Unit) {
22 | var i = this.start
23 | val until = if (inInterval(end)) end + 1 else end
24 |
25 | while (i.x < until.x || i.y < until.y) {
26 | f(i)
27 | i += step
28 | }
29 | }*/
30 |
31 | /** Is the argument inside the interval defined by `start' and `end'?
32 | * Returns true if `v' is inside [start, end).
33 | */
34 | protected def inInterval(v: Vector2): Boolean =
35 | (v.x >= start.x && v.x < end.x) ||
36 | (v.y >= start.y && v.y < end.y)
37 | }
38 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/tests/SimpleTest.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.tests
2 |
3 | import vecmath._
4 | import scenes._
5 |
6 | object SimpleTest {
7 | def apply(parent: TestbedMain,
8 | name: String,
9 | sf: TestbedScene,
10 | offset: Vector2, scale: Float) =
11 | new SimpleTest(parent, name, sf, offset, scale)
12 |
13 | def apply(parent: TestbedMain, name: String, sf: TestbedScene, scale: Float) =
14 | new SimpleTest(parent, name, sf, Vector2.Zero, scale)
15 | }
16 |
17 | /**
18 | * Simple test without custom instructions or custom key mapping.
19 | * Can be created given a scene.
20 | */
21 | class SimpleTest(
22 | _parent: TestbedMain,
23 | val name: String,
24 | scene: TestbedScene,
25 | offset: Vector2,
26 | scale: Float
27 | ) extends AbstractExample(_parent) {
28 | var firstTime = true
29 |
30 | def create = {
31 | if (firstTime) {
32 | setCamera(offset.x, offset.y, scale)
33 | firstTime = false
34 | }
35 |
36 | scene.create
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/Pyramid4PerfTest.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Pyramid4PerfTest extends scenes.TestbedScene {
8 |
9 | def main(args: Array[String]) {
10 | val test = new HeadlessPerformanceTest(this)
11 | test.run(true, 1000, 100, false)
12 | }
13 |
14 | def createScene(implicit world: dynamics.World) {
15 | body {
16 | pos(0, -10)
17 | box(50, 10)
18 | }
19 |
20 | val box1 = box(0.5f, 0.5f) density 5f restitution 0f friction 0.9f
21 | var x = Vector2(-30.0f, 0.75f);
22 | var y = Vector2.Zero
23 | val deltaX = Vector2(0.5625f, 2.0f);
24 | val deltaY = Vector2(1.125f, 0.0f);
25 |
26 | val num = 17
27 | def loop = for (i <- 0 until num) {
28 | y = x
29 | for (j <- i until num) body {
30 | pos(y)
31 | fixture(box1)
32 | massFromShapes
33 |
34 | y += deltaY
35 | }
36 | x += deltaX
37 | }
38 | loop
39 | x = (-10f, 0.75f)
40 | loop
41 | x = (10f, 0.75f)
42 | loop
43 | x = (30f, 0.75f)
44 | loop
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/FixtureDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import shapes.ShapeDef
4 |
5 | /**
6 | * A fixture definition is used to create a fixture. This class defines an
7 | * abstract fixture definition. You can reuse fixture definitions safely.
8 | */
9 | case class FixtureDef(var shapeDef: ShapeDef) {
10 | /** The friction coefficient, usually in the range [0,1]. */
11 | var friction = 0.2f
12 |
13 | /** The restitution (elasticity) usually in the range [0,1]. */
14 | var restitution = 0f
15 |
16 | /** The density, usually in kg/m^2. */
17 | var density = 0f
18 |
19 | /** A sensor collects contact information but never generates a collision response. */
20 | var isSensor = false
21 |
22 | /** Contact filtering data. */
23 | var filter = FilterData.Default
24 |
25 | /** Use this to store application specific fixture data. */
26 | var userData: AnyRef = null
27 |
28 | /** Apply the given material to this fixture definition. */
29 | def apply(m: Material) = {
30 | friction = m.friction
31 | density = m.density
32 | restitution = m.restitution
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/PlanetGravity.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes.AABB
6 | import dsl.DSL._
7 | import dynamics._
8 | import controllers._
9 |
10 | object PlanetGravity extends TestbedScene {
11 |
12 | var planet: Body = null
13 | var player: Body = null
14 |
15 | def createScene(implicit world: dynamics.World) {
16 | planet = body {
17 | circle(5)
18 | }
19 |
20 | player = body {
21 | pos(0f, 7f)
22 | box(0.4f, 1.2f) density 1
23 | massFromShapes
24 | }
25 |
26 | val satellite = body {
27 | pos(10, 10)
28 | circle(0.5f) density 2
29 | massFromShapes
30 | }
31 | satellite.applyForce((150f, -150f), satellite.worldCenter)
32 | satellite.angularVelocity = -0.4f
33 |
34 | val gc = new PlanetGravityController
35 | gc.createSensor(planet, 15)
36 | gc.G = 9.81f
37 | world.addController(gc)
38 | }
39 |
40 | override protected def createWorld = new World(
41 | AABB((-200, -100), (200, 200)),
42 | (0, 0),
43 | true
44 | )
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/PointCircleCollider.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes._
6 |
7 | object PointCircleCollider extends Collider[Point, Circle] {
8 |
9 | def collide(point: Point, xf1: Transform2,
10 | circle: Circle, xf2: Transform2): Option[Manifold] = {
11 | // TODO reuse Circle.testPoint?
12 | var p1 = xf1 * point.pos
13 | var p2 = xf2 * circle.pos
14 | val d = p2 - p1
15 | val distSqr = d dot d
16 |
17 | val r = circle.radius
18 |
19 | if (distSqr > r * r) return None
20 |
21 | var separation = 0f
22 | var normal: Vector2 = null
23 | if (distSqr < Settings.Epsilon) {
24 | separation = -r
25 | normal = Vector2.YUnit
26 | } else {
27 | val dist = sqrt(distSqr)
28 | separation = dist - r
29 | val a = 1 / dist
30 | normal = d * a
31 | }
32 | val points = new Array[ManifoldPoint](1)
33 | val id = ContactID.Zero
34 | val p = p2 - normal * r
35 | points(0) = ManifoldPoint(xf1 ** p, xf2 ** p, separation, id)
36 | Some(Manifold(points, normal))
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Domino.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Domino extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | // Floor
11 | body {
12 | pos(0, -10)
13 | box(50.0f, 10.0f)
14 | }
15 |
16 | // Platforms
17 | for (i <- 0 to 3) body {
18 | pos(0.0f, 5f + 5f * i)
19 | box(15.0f, 0.125f)
20 | }
21 |
22 | val sd = box(0.125f, 2f) density 25.0f friction 0.5f
23 | var numPerRow = 25
24 |
25 | for (i <- 0 to 3) {
26 | for (j <- 0 until numPerRow) body {
27 | var p = Vector2(-14.75f + j * (29.5f / (numPerRow - 1)),
28 | 7.3f + 5f * i)
29 | if (i == 2 && j == 0) {
30 | angle(-0.1f)
31 | p += (0.1f, 0)
32 | } else if (i == 3 && j == numPerRow - 1) {
33 | angle(0.1f)
34 | p -= (0.1f, 0)
35 | } else {
36 | angle(0f)
37 | }
38 | pos(p)
39 | fixture(sd)
40 | massFromShapes
41 | }
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/scalabox2d-svg/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.villane.box2d
7 | scalabox2d-parent
8 | 2.0.1-SNAPSHOT
9 |
10 | org.villane.box2d
11 | scalabox2d-svg
12 | ScalaBox2D - 2D Physics Engine for Games (SVG Module)
13 | 2.0.1-SNAPSHOT
14 |
15 |
16 |
17 | slick-repo
18 | http://slick.cokeandcode.com/mavenrepo/
19 |
20 |
21 |
22 |
23 |
24 | org.villane.box2d
25 | scalabox2d
26 | 2.0.1-SNAPSHOT
27 |
28 |
29 | slick
30 | slick
31 | 264
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/VaryingFriction.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object VaryingFriction extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | body {
11 | pos(0.0f, -20.0f)
12 | box(100.0f, 20.0f)
13 | }
14 |
15 | body {
16 | pos(-4.0f, 22.0f)
17 | angle(-0.25f)
18 | box(13.0f, 0.25f)
19 | }
20 |
21 | body {
22 | pos(10.5f, 19.0f)
23 | box(0.25f, 1.0f)
24 | }
25 |
26 | body {
27 | pos(4.0f, 14.0f)
28 | angle(0.25f)
29 | box(13.0f, 0.25f)
30 | }
31 |
32 | body {
33 | pos(-10.5f, 11.0f)
34 | box(0.25f, 1.0f)
35 | }
36 |
37 | body {
38 | pos(-4.0f, 6.0f)
39 | angle(-0.25f)
40 | box(13.0f, 0.25f)
41 | }
42 |
43 | val sd = box(0.5f, 0.5f) density 25.0f
44 | val friction = Array(0.75f, 0.5f, 0.35f, 0.1f, 0.0f)
45 |
46 | for (i <- 0 until 5) body {
47 | pos(-15.0f + 4.0f * i, 28.0f)
48 | fixture(sd) friction friction(i)
49 | massFromShapes
50 | }
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Bridge.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Bridge extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | val ground = body {
11 | pos(0,0)
12 | box(50.0f, 0.2f)
13 | }
14 |
15 | val sd = box(0.65f, 0.125f) density 20.0f friction 0.2f
16 |
17 | val numPlanks = 30
18 |
19 | var prevBody = ground
20 | for (i <- 0 until numPlanks) {
21 | val bd = body {
22 | pos(-14.5f + 1.0f * i, 5.0f)
23 | fixture(sd)
24 | massFromShapes
25 | }
26 | joint(revolute(prevBody -> bd) anchor(-15.0f + 1.0f * i, 5.0f))
27 | prevBody = bd
28 | }
29 |
30 | joint(revolute(prevBody -> ground) anchor(-15.0f + 1.0f * numPlanks, 5.0f))
31 |
32 | body {
33 | pos(0, 10)
34 | box(1,1) density 5 friction 0.2f restitution 0.1f
35 | massFromShapes
36 | }
37 |
38 | body {
39 | pos(0, 12)
40 | circle((0, 0), 0.9f) density 5 friction 0.2f
41 | circle((0, 1), 0.9f) density 5 friction 0.2f
42 | massFromShapes
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/TestSettings.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed
2 |
3 | import org.villane.vecmath.Vector2
4 |
5 | /**
6 | * Settings for the current test. Mostly self-explanatory.
7 | *
8 | * The settings from here are applied during AbstractExample::step().
9 | *
10 | */
11 | class TestSettings {
12 | var hz = 60
13 | var iterationCount = 10
14 | var gravity = Vector2(0f, -9.81f)
15 |
16 | var enableWarmStarting = true
17 | var enablePositionCorrection = true
18 | var enableTOI = true
19 | var enableSleeping = true
20 |
21 | /** Pause the simulation */
22 | var pause = false
23 | /** Take a single timestep */
24 | var singleStep = false
25 | /** True if we should reset the demo for the next frame. */
26 | var reset = false
27 |
28 | var drawShapes = true
29 | var drawSensors = true
30 | var drawJoints = true
31 | var drawCoreShapes = false
32 | var drawCOMs = false
33 | var drawStats = false
34 | var drawImpulses = false
35 | var drawAABBs = false
36 | var drawPairs = false
37 | var drawContactPoints = false
38 | var drawContactNormals = false
39 | var drawContactForces = false
40 | var drawFrictionForces = false
41 |
42 | var testIndex = 0
43 | }
44 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/Manifold.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import vecmath.Vector2
4 |
5 | /** A manifold for two touching convex shapes. */
6 | case class Manifold(
7 | /** The points of contact. */
8 | points: Seq[ManifoldPoint],
9 | /** The shared unit normal vector. */
10 | normal: Vector2
11 | ) {
12 | assert(points.length > 0, "Can't create a manifold without points!")
13 | }
14 |
15 | /**
16 | * A manifold point is a contact point belonging to a contact
17 | * manifold. It holds details related to the geometry and dynamics
18 | * of the contact points.
19 | * The point is stored in local coordinates because CCD
20 | * requires sub-stepping in which the separation is stale.
21 | */
22 | case class ManifoldPoint(
23 | /** Local position of the contact point in body1 */
24 | localPoint1: Vector2,
25 | /** Local position of the contact point in body2 */
26 | localPoint2: Vector2,
27 | /** The separation of the shapes along the normal vector */
28 | separation: Float,
29 | /** Uniquely identifies a contact point between two shapes */
30 | id: ContactID
31 | ) {
32 | /** The non-penetration force */
33 | var normalImpulse = 0f
34 | /** The friction force */
35 | var tangentImpulse = 0f
36 | }
37 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/VerticalStack.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | import scala.util.Random
8 |
9 | object VerticalStack extends TestbedScene {
10 |
11 | def createScene(implicit world: dynamics.World) {
12 | body {
13 | pos(0.0f, 0.0f)
14 | box(50.0f, 10.0f, (0.0f, -10.0f), 0.0f)
15 | box(0.1f, 10.0f, (20.0f, 10.0f), 0.0f)
16 | }
17 |
18 | val xs = Array(0.0f, -10.0f, -5.0f, 5.0f, 10.0f)
19 | val sd = box(0.5f, 0.5f) density 1.0f friction 0.3f
20 |
21 | for {
22 | j <- 0 until xs.length
23 | i <- 0 until 12
24 | } body {
25 | //float32 x = b2Random(-0.1f, 0.1f);
26 | //float32 x = i % 2 == 0 ? -0.025f : 0.025f;
27 | //bd.position.Set(xs[j], 2.51f + 4.02f * i);
28 | pos(xs(j) + (new Random().nextFloat * 0.1f - 0.05f),
29 | 0.752f + 1.54f * i)
30 | fixture(sd)
31 | // For this test we are using continuous physics for all boxes.
32 | // This is a stress test, you normally wouldn't do this for
33 | // performance reasons.
34 | bullet(true)
35 | sleepingAllowed(true)
36 | massFromShapes
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/DistanceJointDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath.Vector2
4 |
5 | /**
6 | * Definition for a distance joint. A distance joint
7 | * keeps two points on two bodies at a constant distance
8 | * from each other.
9 | */
10 | class DistanceJointDef extends JointDef {
11 | /** The local anchor point relative to body1's origin. */
12 | var localAnchor1 = Vector2.Zero
13 |
14 | /** The local anchor point relative to body2's origin. */
15 | var localAnchor2 = Vector2.Zero
16 |
17 | /** The equilibrium length between the anchor points. */
18 | var length = 1.0f
19 |
20 | var frequencyHz = 0f
21 |
22 | var dampingRatio = 0f
23 |
24 | /**
25 | * Initialize the bodies, anchors, and length using the world
26 | * anchors.
27 | * @param b1 First body
28 | * @param b2 Second body
29 | * @param anchor1 World anchor on first body
30 | * @param anchor2 World anchor on second body
31 | */
32 | def this(b1: Body, b2: Body, anchor1: Vector2, anchor2: Vector2) = {
33 | this()
34 | body1 = b1
35 | body2 = b2
36 | localAnchor1 = body1.toLocalPoint(anchor1)
37 | localAnchor2 = body2.toLocalPoint(anchor2)
38 | length = (anchor2 - anchor1).length
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/CCDTest.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | import scala.util.Random
8 |
9 | object CCDTest extends TestbedScene {
10 |
11 | def createScene(implicit world: dynamics.World) {
12 | val k_restitution = 1.4f
13 |
14 | body {
15 | pos(0, 20)
16 | box(0.1f, 10.0f, (-10.0f, 0.0f), 0.0f) density 0 restitution k_restitution
17 | box(0.1f, 10.0f, (10.0f, 0.0f), 0.0f) density 0 restitution k_restitution
18 | box(0.1f, 10.0f, (0.0f, -10.0f), 0.5f * π) density 0 restitution k_restitution
19 | box(0.1f, 10.0f, (0.0f, 10.0f), -0.5f * π) density 0 restitution k_restitution
20 | }
21 |
22 | body {
23 | pos(0.0f, 15.0f)
24 | // bottom
25 | box(1.5f, 0.15f) density 4.0f
26 | // left
27 | box(0.15f, 2.7f, (-1.45f, 2.35f), 0.2f) density 4.0f
28 | // right
29 | box(0.15f, 2.7f, (1.45f, 2.35f), -0.2f) density 4.0f
30 | bullet(true)
31 | massFromShapes
32 | }
33 |
34 | for (i <- 0 until 10) body {
35 | pos(0.0f, 15.5f + i)
36 | circle(0.25f) density 1.0f restitution 0.0f friction 0.05f
37 | bullet(true)
38 | massFromShapes
39 | } angularVelocity = new Random().nextFloat * 100 - 50
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/CircleCollider.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes.Circle
6 |
7 | /**
8 | * Circle/circle overlap solver - for internal use only.
9 | */
10 | object CircleCollider extends Collider[Circle, Circle] {
11 |
12 | def collide(circle1: Circle, xf1: Transform2,
13 | circle2: Circle, xf2: Transform2): Option[Manifold] = {
14 | var p1 = xf1 * circle1.pos
15 | var p2 = xf2 * circle2.pos
16 | val d = p2 - p1
17 |
18 | val distSqr = d dot d
19 |
20 | val r1 = circle1.radius
21 | val r2 = circle2.radius
22 | val radiusSum = r1 + r2
23 |
24 | if (distSqr > radiusSum * radiusSum) return None
25 |
26 | var separation = 0f
27 | var normal: Vector2 = null
28 | if (distSqr < Settings.Epsilon) {
29 | separation = -radiusSum
30 | normal = Vector2.YUnit
31 | } else {
32 | val dist = sqrt(distSqr)
33 | separation = dist - radiusSum
34 | val a = 1 / dist
35 | normal = d * a
36 | }
37 |
38 | p1 += (normal * r1)
39 | p2 -= (normal * r2)
40 | val p = (p1 + p2) * 0.5f
41 |
42 | val points = new Array[ManifoldPoint](1)
43 | val id = ContactID.Zero
44 | points(0) = ManifoldPoint(xf1 ** p, xf2 ** p, separation, id)
45 | Some(Manifold(points, normal))
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/AABB.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | object AABB {
7 | def centered(center: Vector2, size: Vector2) = AABB(
8 | center - size / 2, center + size / 2
9 | )
10 | }
11 |
12 | /**
13 | * An axis-aligned bounding box
14 | */
15 | case class AABB(lowerBound: Vector2, upperBound: Vector2) {
16 | def size = upperBound - lowerBound
17 | def center = lowerBound + (upperBound - lowerBound) / 2
18 |
19 | def combineWith(other: AABB) = AABB(
20 | min(lowerBound, other.lowerBound),
21 | max(upperBound, other.upperBound)
22 | )
23 |
24 | /** Does this aabb contain the provided AABB. */
25 | def contains(other: AABB) = {
26 | lowerBound.x <= other.lowerBound.x &&
27 | lowerBound.y <= other.lowerBound.y &&
28 | upperBound.x >= other.upperBound.x &&
29 | upperBound.y >= other.upperBound.y
30 | }
31 |
32 | /** Check if AABBs overlap. */
33 | def overlaps(other: AABB) = {
34 | val d1 = other.lowerBound - upperBound
35 | val d2 = lowerBound - other.upperBound
36 | ! (d1.x > 0.0f || d1.y > 0.0f || d2.x > 0.0f || d2.y > 0.0f)
37 | }
38 |
39 | /** Verify that the bounds are sorted. */
40 | def isValid = {
41 | val d = upperBound - lowerBound
42 | val valid = (d.x >= 0 && d.y >= 0)
43 | valid && lowerBound.isValid && upperBound.isValid
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/tests/PlanetGravity.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.tests
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | class PlanetGravity(_parent: TestbedMain) extends AbstractExample(_parent) {
7 | var firstTime = true
8 | val name = "Planet Gravity"
9 |
10 | val scene = scenes.PlanetGravity
11 |
12 | override def postStep() {
13 | /*if (newKeyDown(0x24)) { // J
14 |
15 | }
16 | val force = 3f
17 | if (keyDown(0xCB)) { // Left
18 | if (!scene.player.contactList.isEmpty) {
19 | val c = scene.player.contactList.first.contact
20 | if (!c.manifolds.isEmpty) {
21 | val m = c.manifolds.first
22 | if (m.points.size == 2) {
23 | val f = force * m.normal.normal
24 | scene.player.applyForce(f, scene.player.worldCenter)
25 | }
26 | }
27 | }
28 | }
29 | if (keyDown(0xCD)) { // Right
30 | if (!scene.player.contactList.isEmpty) {
31 | val c = scene.player.contactList.first.contact
32 | if (!c.manifolds.isEmpty) {
33 | val m = c.manifolds.first
34 | val f = -force * m.normal.normal
35 | scene.player.applyForce(f, scene.player.worldCenter)
36 | }
37 | }
38 | }*/
39 | }
40 |
41 | def create = {
42 | if (firstTime) {
43 | setCamera(0f, 0f, 15f)
44 | firstTime = false
45 | }
46 |
47 | scenes.PlanetGravity.create
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/ContactListener.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | /**
4 | * Implement this class to get collision results. You can use these results for
5 | * things like sounds and game logic. You can also get contact results by
6 | * traversing the contact lists after the time step. However, you might miss
7 | * some contacts because continuous physics leads to sub-stepping.
8 | * Additionally you may receive multiple callbacks for the same contact in a
9 | * single time step.
10 | * You should strive to make your callbacks efficient because there may be
11 | * many callbacks per time step.
12 | *
Warning: The contact separation is the last computed value.
13 | *
Warning: You cannot create/destroy Box2D entities inside these callbacks.
14 | * Buffer any such events and apply them at the end of the time step.
15 | */
16 | trait ContactListener {
17 | /**
18 | * Called when a contact point is added. This includes the geometry
19 | * and the forces.
20 | */
21 | def add(point: ContactPoint)
22 |
23 | /**
24 | * Called when a contact point persists. This includes the geometry
25 | * and the forces.
26 | */
27 | def persist(point: ContactPoint)
28 |
29 | /**
30 | * Called when a contact point is removed. This includes the last
31 | * computed geometry and forces.
32 | */
33 | def remove(point: ContactPoint)
34 |
35 | def result(point: ContactResult)
36 | }
37 |
--------------------------------------------------------------------------------
/scalabox2d-svg/src/main/scala/org/villane/box2d/svg/ConvexHull.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.svg
2 |
3 | import vecmath.Vector2
4 |
5 | /**
6 | * @author Mason Green
7 | */
8 | object ConvexHull {
9 | /**
10 | * Melkman's Algorithm
11 | * http://www.ams.sunysb.edu/~jsbm/courses/345/melkman.pdf
12 | * Returns a convex hull in CCW order
13 | *
14 | * The input has to be a polygon in either CCW or CW order.
15 | */
16 | def convexHull(V: Array[Vector2]) = {
17 | def left(a: Vector2, b: Vector2, c: Vector2) =
18 | (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y) > 0
19 |
20 | val n = V.length
21 | val D = new Array[Vector2](2 * n + 1)
22 | var bot = n - 2
23 | var top = bot + 3
24 |
25 | D(bot) = V(2)
26 | D(top) = V(2)
27 |
28 | if (left(V(0), V(1), V(2))) {
29 | D(bot+1) = V(0)
30 | D(bot+2) = V(1)
31 | } else {
32 | D(bot+1) = V(1)
33 | D(bot+2) = V(0)
34 | }
35 |
36 | var i = 3
37 | while(i < n) {
38 | while (left(D(bot), D(bot+1), V(i)) && left(D(top-1), D(top), V(i))) {
39 | i += 1
40 | }
41 | while (!left(D(top-1), D(top), V(i))) top -= 1
42 | top += 1; D(top) = V(i)
43 | while (!left(D(bot), D(bot+1), V(i))) bot += 1
44 | bot -= 1; D(bot) = V(i)
45 | i += 1
46 | }
47 |
48 | val H = new Array[Vector2](top - bot)
49 | var h = 0
50 | while(h < (top - bot)) {
51 | H(h) = D(bot + h)
52 | h += 1
53 | }
54 | H
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/DoubleMath.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | /**
4 | * Mirrors FloatMath, use when replacing Float with Double
5 | */
6 | trait DoubleMath {
7 | type Float = Double
8 | implicit def double2float(d: Double) = d.toFloat
9 |
10 | final val Pi = Math.Pi
11 | final val π = Pi
12 |
13 | final val Infinity = Double.PositiveInfinity
14 | final val ∞ = Infinity
15 |
16 | // Max/min rewritten here because for some reason Math.max/min
17 | // can run absurdly slow for such simple functions...
18 | // TODO: profile, see if this just seems to be the case or is actually causing issues...
19 | final def max(a: Double, b: Double) = if (a > b) a else b
20 | final def max(as: Double*): Double = as reduceLeft max
21 | final def min(a: Double, b: Double) = if (a < b) a else b
22 | final def min(as: Double*): Double = as reduceLeft min
23 | final def clamp(a: Double, low: Double, high: Double) =
24 | if (a < low) low
25 | else if (a > high) high
26 | else a
27 |
28 | final def sqrt(a: Double) = Math.sqrt(a)
29 | final def √(a: Double) = sqrt(a)
30 |
31 | final def sin(a: Double) = Math.sin(a)
32 | final def cos(a: Double) = Math.cos(a)
33 | final def tan(a: Double) = Math.tan(a)
34 | final def asin(a: Double) = Math.asin(a)
35 | final def acos(a: Double) = Math.acos(a)
36 | final def atan(a: Double) = Math.atan(a)
37 | final def atan2(a: Double, b: Double) = Math.atan2(a, b)
38 |
39 | final def pow(a: Double, b: Double) = Math.pow(a, b)
40 | }
41 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/Preamble.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | /**
4 | * The vecmath package includes geometry classes useful for 2D graphics and
5 | * physics engines.
6 | *
7 | * This preamble contains implicit conversions from tuples to vectors and matrices
8 | * and extension methods for Float.
9 | *
10 | * Vector2 is an immutable implementation of a 2D vector of two floats
11 | * Matrix22 is an immutable implementation of a 2D matrix of two vectors
12 | * Transform2 is a transformation, consisting of a point (vector) and it's rotation (matrix)
13 | * MathUtil contains useful math with floats.
14 | * FloatExtensions allows some operations to be used with the float on the left side.
15 | */
16 | object Preamble extends FloatMath with CommonMath {
17 | implicit def tuple2fToVector2(xy: (Float, Float)) = Vector2(xy._1, xy._2)
18 | implicit def tuple2vector2fToMatrix22(m: (Vector2, Vector2)) = Matrix22(m._1, m._2)
19 | implicit def floatToFloatExtensions(a: Float) = new FloatExtensions(a)
20 |
21 | def min(a: Vector2, b: Vector2): Vector2 = Vector2(min(a.x, b.x), min(a.y, b.y))
22 | def min(a: Vector2, b: Vector2, c: Vector2, d: Vector2): Vector2 = Vector2(
23 | min(a.x, b.x, c.x, d.x),
24 | min(a.y, b.y, c.y, d.y)
25 | )
26 | def max(a: Vector2, b: Vector2): Vector2 = Vector2(max(a.x, b.x), max(a.y, b.y))
27 | def max(a: Vector2, b: Vector2, c: Vector2, d: Vector2): Vector2 = Vector2(
28 | max(a.x, b.x, c.x, d.x),
29 | max(a.y, b.y, c.y, d.y)
30 | )
31 | }
32 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/GearJointDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | /**
4 | * Gear joint definition. This definition requires two existing
5 | * revolute or prismatic joints (any combination will work).
6 | * The provided joints must attach a dynamic body to a static body.
7 | *
8 | * A gear joint is used to connect two joints together. Either joint
9 | * can be a revolute or prismatic joint. You specify a gear ratio
10 | * to bind the motions together:
11 | * coordinate1 + ratio * coordinate2 = constant
12 | * The ratio can be negative or positive. If one joint is a revolute joint
13 | * and the other joint is a prismatic joint, then the ratio will have units
14 | * of length or units of 1/length.
15 | * Warning: The revolute and prismatic joints must be attached to
16 | * fixed bodies (which must be body1 on those joints).
17 | */
18 | class GearJointDef extends JointDef {
19 | /** The first revolute/prismatic joint attached to the gear joint. */
20 | private[this] var _joint1: Joint = null
21 | def joint1 = _joint1
22 | def joint1_=(joint: Joint) = {
23 | _joint1 = joint
24 | body1 = joint.body2
25 | }
26 |
27 | /** The second revolute/prismatic joint attached to the gear joint. */
28 | private[this] var _joint2: Joint = null
29 | def joint2 = _joint2
30 | def joint2_=(joint: Joint) = {
31 | _joint2 = joint
32 | body2 = joint.body2
33 | }
34 |
35 | /** The gear ratio. @see GearJoint for explanation. */
36 | var ratio = 1f
37 | }
38 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/BodyDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import vecmath.Vector2
4 | import shapes.Mass
5 |
6 | class BodyDef {
7 | var userData: AnyRef = null
8 | var mass = Mass.Zero
9 | var pos = Vector2.Zero
10 | var angle = 0f
11 |
12 | /**
13 | * Linear damping is use to reduce the linear velocity. The damping parameter
14 | * can be larger than 1.0f but the damping effect becomes sensitive to the
15 | * time step when the damping parameter is large.
16 | */
17 | var linearDamping = 0f
18 |
19 | /**
20 | * Angular damping is use to reduce the angular velocity. The damping parameter
21 | * can be larger than 1.0f but the damping effect becomes sensitive to the
22 | * time step when the damping parameter is large.
23 | */
24 | var angularDamping = 0f
25 |
26 | /**
27 | * Set this flag to false if this body should never fall asleep. Note that
28 | * this increases CPU usage.
29 | */
30 | var allowSleep = true
31 |
32 | /** Is this body initially sleeping? */
33 | var isSleeping = false
34 |
35 | /** Should this body be prevented from rotating? Useful for characters. */
36 | var fixedRotation = false
37 |
38 | /**
39 | * Is this a fast moving body that should be prevented from tunneling through
40 | * other moving bodies? Note that all bodies are prevented from tunneling through
41 | * static bodies.
42 | *
Warning: You should use this flag sparingly since it increases processing time.
43 | */
44 | var isBullet = false
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/TensorDampingController.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.controllers
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /**
7 | * Applies top down linear damping to the controlled bodies
8 | * The damping is calculated by multiplying velocity by a matrix in local co-ordinates.
9 | * Some examples:
10 | *
11 | * |-a 0| Standard isotropic damping with strength a
12 | * | 0 -a|
13 | *
14 | * | 0 a| Electron in fixed field - a force at right angles to velocity with proportional magnitude
15 | * |-a 0|
16 | *
17 | * |-a 0| Differing x and y damping. Useful e.g. for top-down wheels
18 | * | 0 -b|
19 | *
20 | */
21 | abstract class TensorDampingController extends Controller {
22 | /** Tensor to use in damping model. Tensor means Matrix here. */
23 | var T = Matrix22.Zero
24 |
25 | /**
26 | * Set this to a positive number to clamp the maximum amount of damping done.
27 | *
28 | * Typically one wants maxTimestep to be 1/(max eigenvalue of T),
29 | * so that damping will never cause something to reverse direction
30 | */
31 | var maxTimestep = 0f
32 |
33 | def step(step: TimeStep): Unit = {
34 | var timestep = step.dt
35 | if (timestep <= Settings.Epsilon)
36 | return
37 | if (timestep > maxTimestep && maxTimestep > 0)
38 | timestep = maxTimestep
39 |
40 | forAwakeBodies { body =>
41 | val damping = body.toWorldVector(T * body.toLocalVector(body.linearVelocity))
42 | body.linearVelocity += timestep * damping
43 | }
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Gears.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 | import shapes._
7 |
8 | object Gears extends TestbedScene {
9 |
10 | def createScene(implicit world: dynamics.World) {
11 | val ground = body {
12 | pos(0.0f, -10.0f)
13 | box(50.0f, 10.0f)
14 | }
15 |
16 | val circle1 = circle(1) density 5 define
17 | val r1 = circle1.shapeDef.asInstanceOf[CircleDef].radius
18 | val circle2 = circle(2) density 5 define
19 | val r2 = circle1.shapeDef.asInstanceOf[CircleDef].radius
20 |
21 | val box1 = box(0.5f, 5.0f) density 5
22 |
23 | val body1 = body {
24 | pos(-3.0f, 12.0f)
25 | fixture(circle1)
26 | massFromShapes
27 | }
28 |
29 | val rev1 = joint(
30 | revolute(ground -> body1) anchor(body1.pos)
31 | )
32 |
33 | val body2 = body {
34 | pos(0.0f, 12.0f)
35 | fixture(circle2)
36 | massFromShapes
37 | }
38 |
39 | val rev2 = joint(
40 | revolute(ground -> body2) anchor body2.pos
41 | )
42 |
43 | val body3 = body {
44 | pos(2.5f, 12.0f)
45 | fixture(box1)
46 | massFromShapes
47 | }
48 |
49 | val prism = joint(
50 | prismatic(ground -> body3)
51 | anchor(body3.pos)
52 | axis(Vector2.YUnit)
53 | lowerTranslation -5.0f
54 | upperTranslation 5.0f
55 | enableLimit true
56 | )
57 |
58 | joint(
59 | gear(rev1 -> rev2) ratio(r2 / r1)
60 | )
61 |
62 | joint(
63 | gear(rev2 -> prism) ratio(-1 / r2)
64 | )
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/FloatMath.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | /**
4 | * A trait implementing math functions with Float signatures.
5 | *
6 | * Replacable with DoubleMath.
7 | *
8 | * TODO: for Scala 2.8, make FloatMath and DoubleMath extend
9 | * MathFunctions[@specialized T] to enforce a common interface.
10 | */
11 | trait FloatMath {
12 | final val Pi = Math.Pi.toFloat
13 | final val π = Pi
14 |
15 | final val Infinity = Float.PositiveInfinity
16 | final val ∞ = Infinity
17 |
18 | // Max/min rewritten here because for some reason Math.max/min
19 | // can run absurdly slow for such simple functions...
20 | // TODO: profile, see if this just seems to be the case or is actually causing issues...
21 | final def max(a: Float, b: Float) = if (a > b) a else b
22 | final def max(as: Float*): Float = as reduceLeft max
23 | final def min(a: Float, b: Float) = if (a < b) a else b
24 | final def min(as: Float*): Float = as reduceLeft min
25 | final def clamp(a: Float, low: Float, high: Float) =
26 | if (a < low) low
27 | else if (a > high) high
28 | else a
29 |
30 | final def sqrt(a: Float) = Math.sqrt(a).toFloat
31 | final def √(a: Float) = sqrt(a)
32 |
33 | final def sin(a: Float) = Math.sin(a).toFloat
34 | final def cos(a: Float) = Math.cos(a).toFloat
35 | final def tan(a: Float) = Math.tan(a).toFloat
36 | final def asin(a: Float) = Math.asin(a).toFloat
37 | final def acos(a: Float) = Math.acos(a).toFloat
38 | final def atan(a: Float) = Math.atan(a).toFloat
39 | final def atan2(y: Float, x: Float) = Math.atan2(y, x).toFloat
40 |
41 | final def pow(a: Float, b: Float) = Math.pow(a, b).toFloat
42 | }
43 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/PrismaticJointDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath.Vector2
4 |
5 | class PrismaticJointDef extends JointDef {
6 | /** The local anchor point relative to body1's origin. */
7 | var localAnchor1 = Vector2.Zero
8 |
9 | /** The local anchor point relative to body2's origin. */
10 | var localAnchor2 = Vector2.Zero
11 |
12 | /** The local translation axis in body1. */
13 | var localAxis1 = Vector2.Zero
14 |
15 | /** The constrained angle between the bodies: body2_angle - body1_angle. */
16 | var referenceAngle = 0f
17 |
18 | /** A flag to enable joint limits. */
19 | var enableLimit = false
20 |
21 | /** The lower translation limit, usually in meters. */
22 | var lowerTranslation = 0f
23 |
24 | /** The upper translation limit, usually in meters. */
25 | var upperTranslation = 0f
26 |
27 | /** A flag to enable the joint motor. */
28 | var enableMotor = false
29 |
30 | /** The desired motor speed. Usually in radians per second. */
31 | var motorSpeed = 0f
32 |
33 | /**
34 | * The maximum motor torque used to achieve the desired motor speed.
35 | * Usually in N-m.
36 | */
37 | var maxMotorForce = 0f
38 |
39 | /**
40 | * Initialize the bodies, anchors, and reference angle using the world anchor.
41 | */
42 | def this(b1: Body, b2: Body, anchor: Vector2, axis: Vector2) {
43 | this()
44 | body1 = b1;
45 | body2 = b2;
46 | localAnchor1 = body1.toLocalPoint(anchor)
47 | localAnchor2 = body2.toLocalPoint(anchor)
48 | referenceAngle = body2.angle - body1.angle
49 | localAxis1 = body1.toLocalVector(axis)
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/scalabox2d-svg/src/main/scala/org/villane/box2d/svg/PureScalaSVGParser.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.svg
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes._
6 | import dynamics._
7 | import scala.xml.XML
8 | import dsl.DSL._
9 |
10 | class PureScalaSVGParser(fileName: String, scale: Float, gravity: Vector2) {
11 | implicit def stringExt(s: String) = new {
12 | def toScalar = s.toFloat / scale
13 | }
14 |
15 | def parse = {
16 | val svg = XML.load("C:/drawing.svg")
17 | val aabb = {
18 | val width = (svg \ "@width").text.toScalar
19 | val height = (svg \ "@width").text.toScalar
20 | AABB(Vector2.Zero, (width, height))
21 | }
22 | implicit val world = new World(aabb, gravity, true)
23 | for(g <- svg \\ "g") {
24 | for (r <- g \ "rect") body {
25 | val width = (r \ "@width").text.toScalar
26 | val height = (r \ "@height").text.toScalar
27 | pos((r \ "@x").text.toScalar + width / 2,
28 | (r \ "@y").text.toScalar + height / 2)
29 | box(width / 2, height / 2)
30 | }
31 | for (path <- g \ "path") {
32 | val sp = "@{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}" + _
33 |
34 | if ("arc" == (path \ sp("type")).text) {
35 | val rx = (path \ sp("rx")).text.toScalar
36 | val ry = (path \ sp("ry")).text.toScalar
37 | if (rx == ry) body {
38 | val cx = (path \ sp("cx")).text.toScalar
39 | val cy = (path \ sp("cy")).text.toScalar
40 | pos(cx, cy)
41 | circle(rx) density 1
42 | massFromShapes
43 | }
44 | }
45 | }
46 | }
47 | world
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/Segment.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 |
5 | object SegmentCollide {
6 | val Miss = SegmentCollide(SegmentCollideResult.Miss, 0, Vector2.Zero)
7 | def hit(lambda: Float, normal: Vector2) =
8 | SegmentCollide(SegmentCollideResult.Hit, lambda, normal)
9 | def startsInside(lambda: Float, normal: Vector2) =
10 | SegmentCollide(SegmentCollideResult.StartsInside, lambda, normal)
11 | }
12 |
13 | case class SegmentCollide(
14 | result: SegmentCollideResult,
15 | lambda: Float,
16 | normal: Vector2
17 | )
18 |
19 | sealed trait SegmentCollideResult
20 |
21 | object SegmentCollideResult {
22 | object StartsInside extends SegmentCollideResult
23 | object Miss extends SegmentCollideResult
24 | object Hit extends SegmentCollideResult
25 | }
26 |
27 | /** A line segment */
28 | class Segment(val p1: Vector2, val p2: Vector2) {
29 | /** Ray cast against this segment with another segment. */
30 | def testSegment(segment: Segment, maxLambda: Float): SegmentCollide = {
31 | val s = segment.p1
32 | val r = segment.p2 - s
33 | val d = p2 - p1
34 | val n = d cross 1f
35 |
36 | val slop = 100f * Settings.Epsilon
37 | val denom = -(r dot n)
38 |
39 | // Cull back facing collision and ignore parallel segments.
40 | if (denom > slop) {
41 | // Does the segment intersect the infinite line associated with this segment?
42 | val b = s - p1
43 | val a = b dot n
44 |
45 | if (0f <= a && a <= maxLambda * denom) {
46 | val mu2 = -r.x * b.y + r.y * b.x
47 |
48 | // Does the segment intersect this segment?
49 | if (-slop * denom <= mu2 && mu2 <= denom * (1f + slop))
50 | return SegmentCollide.hit(a / denom, n.normalize)
51 | }
52 | }
53 | SegmentCollide.Miss
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.villane.box2d
6 | scalabox2d-parent
7 | 2.0.1-SNAPSHOT
8 |
9 | org.villane.box2d
10 | scalabox2d-testbed
11 | ScalaBox2D - 2D Physics Engine for Games (Test Bed)
12 | 2.0.1-SNAPSHOT
13 |
14 |
15 | slick-repo
16 | http://slick.cokeandcode.com/mavenrepo/
17 |
18 |
19 | villane.org
20 | http://villane.org/mavenrepo/
21 |
22 |
23 |
24 |
25 | org.villane.box2d
26 | scalabox2d
27 | 2.0.1-SNAPSHOT
28 |
29 |
30 | org.villane.box2d
31 | scalabox2d-svg
32 | 2.0.1-SNAPSHOT
33 |
34 |
35 | slick
36 | slick
37 | 264
38 |
39 |
40 | org.lwjgl
41 | lwjgl
42 | 2.1.0
43 |
44 |
45 | org.lwjgl
46 | lwjgl-util
47 | 2.1.0
48 |
49 |
50 | org.fenggui
51 | fenggui
52 | 648
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Circles.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Circles extends TestbedScene {
8 |
9 | def createScene(implicit world: dynamics.World) {
10 | val ground = world.groundBody
11 |
12 | body {
13 | // Ground
14 | pos(0.0f, -10.0f)
15 | box(50.0f, 10.0f) friction 1.0f
16 | }
17 |
18 | val leftWall = body {
19 | pos(53.0f, 25.0f)
20 | box(3.0f,50.0f) friction 1.0f
21 | }
22 | val rightWall = body {
23 | pos(-53.0f, 25.0f)
24 | box(3.0f,50.0f) friction 1.0f
25 | }
26 | // Corners
27 | body {
28 | pos(-40f, 0.0f)
29 | angle(-π/4)
30 | box(20.0f, 3.0f) friction 1.0f
31 | }
32 | body {
33 | pos(40f, 0.0f)
34 | angle(π/4)
35 | box(20.0f, 3.0f) friction 1.0f
36 | }
37 |
38 | val bd = body {
39 | pos(0.0f, 10.0f)
40 | val numPieces = 5
41 | val radius = 6f
42 | for (i <- 0 until numPieces) {
43 | val x = radius * cos(2 * π * (i.toFloat / (numPieces)))
44 | val y = radius * sin(2 * π * (i.toFloat / (numPieces)))
45 | circle((x, y), 1.2f) density 25.0f friction 0.1f restitution 0.9f
46 | }
47 | massFromShapes
48 | }
49 |
50 | joint (
51 | revolute(bd -> ground)
52 | anchor bd.pos
53 | motorSpeed π
54 | maxMotorTorque 1000000.0f
55 | enableMotor true
56 | )
57 |
58 | val loadSize = 45
59 | for (j <- 0 until 10) {
60 | for (i <- 0 until loadSize) body {
61 | pos(-45f + 2*i, 50f + j)
62 | (circle(1.0f+(if (i%2==0) 1.0f else -1.0f)*.5f*(i.toFloat/loadSize))
63 | density 5.0f
64 | friction 0.1f
65 | restitution 0.5f
66 | )
67 | massFromShapes
68 | }
69 | }
70 | }
71 |
72 | }
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/Collider.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import vecmath._
4 | import shapes._
5 |
6 | /**
7 | * Companion object for performing collision detection without knowing the
8 | * concrete shape or collider types.
9 | */
10 | object Collider {
11 |
12 | /**
13 | * Collide two shapes for which a collider exists, given their world transforms.
14 | */
15 | def collide(shape1: Shape, t1: Transform2, shape2: Shape, t2: Transform2):
16 | Option[Manifold] = (shape1, shape2) match {
17 | case (s1: Circle, s2: Circle) =>
18 | CircleCollider.collide(s1, t1, s2, t2)
19 | case (s1: Polygon, s2: Circle) =>
20 | PolygonCircleCollider.collide(s1, t1, s2, t2)
21 | case (s1: Circle, s2: Polygon) =>
22 | PolygonCircleCollider.collide(s2, t2, s1, t1)
23 | case (s1: Polygon, s2: Polygon) =>
24 | PolygonCollider.collide(s1, t1, s2, t2)
25 | case (s1: Edge, s2: Circle) =>
26 | EdgeCircleCollider.collide(s1, t1, s2, t2)
27 | case (s1: Circle, s2: Edge) =>
28 | EdgeCircleCollider.collide(s2, t2, s1, t1)
29 | case (s1: Polygon, s2: Edge) =>
30 | PolygonEdgeCollider.collide(s1, t1, s2, t2)
31 | case (s1: Edge, s2: Polygon) =>
32 | PolygonEdgeCollider.collide(s2, t2, s1, t1)
33 | case (s1, s2) =>
34 | throw new IllegalArgumentException(
35 | "No collider exists for shapes (" +
36 | s1.getClass.getSimpleName + ", " +
37 | s2.getClass.getSimpleName + ")")
38 | }
39 |
40 | }
41 |
42 | /**
43 | * Base trait for specific shape colliders.
44 | *
45 | * A collider returns Some(Manifold(...)) on contact or None on no contact.
46 | */
47 | trait Collider[S1 <: Shape, S2 <: Shape] {
48 |
49 | /**
50 | * Collide two shapes, given their world transforms.
51 | */
52 | def collide(shape1: S1, t1: Transform2,
53 | shape2: S2, t2: Transform2): Option[Manifold]
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/EdgeCircleCollider.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes._
6 | import collision._
7 | import Settings.Epsilon
8 |
9 | object EdgeCircleCollider extends Collider[Edge, Circle] {
10 |
11 | def collide(edge: Edge, xf1: Transform2,
12 | circle: Circle, xf2: Transform2): Option[Manifold] = {
13 | val c = xf2 * circle.pos
14 | val cLocal = xf1 ** c
15 |
16 | val n = edge.normal
17 | val v1 = edge.v1
18 | val v2 = edge.v2
19 | val radius = circle.radius
20 |
21 | var separation = 0f
22 |
23 | var d = cLocal - v1
24 |
25 | var dirDist = d dot edge.direction
26 | if (dirDist <= 0) {
27 | if ((d dot edge.corner1Dir) < 0) return None
28 | d = c - xf1 * v1
29 | } else if (dirDist >= edge.length) {
30 | d = c - v2
31 | if ((d dot edge.corner2Dir) > 0) return None
32 | d = c - xf1 * v2
33 | } else {
34 | separation = d dot n
35 | if (separation > radius || separation < -radius) return None
36 | separation -= radius
37 |
38 | val id = ContactID.Zero
39 | val normal = xf1.rot * n
40 | val pos = c - normal * radius
41 | val points = new Array[ManifoldPoint](1)
42 | points(0) = ManifoldPoint(xf1 ** pos, xf2 ** pos, separation, id)
43 | return Some(Manifold(points, normal))
44 | }
45 |
46 | val distSqr = d dot d
47 | if (distSqr > radius * radius) return None
48 |
49 | val normal = if (distSqr < Epsilon) {
50 | separation = -radius
51 | xf1.rot * n
52 | } else {
53 | val len = d.length
54 | separation = len - radius
55 | d / len
56 | }
57 |
58 | val id = ContactID.Zero
59 | val pos = c - normal * radius
60 | val points = new Array[ManifoldPoint](1)
61 | points(0) = ManifoldPoint(xf1 ** pos, xf2 ** pos, separation, id)
62 | Some(Manifold(points, normal))
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/Joint.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath._
4 | import dynamics._
5 |
6 | object Joint {
7 | def create(defn: JointDef): Joint = defn match {
8 | case d: DistanceJointDef => new DistanceJoint(d)
9 | case d: RevoluteJointDef => new RevoluteJoint(d)
10 | case d: PrismaticJointDef => new PrismaticJoint(d)
11 | case d: GearJointDef => new GearJoint(d)
12 | case d: PointingDeviceJointDef => new PointingDeviceJoint(d)
13 | }
14 | }
15 |
16 | abstract class Joint(defn: JointDef) {
17 | val body1 = defn.body1
18 | val body2 = defn.body2
19 | val collideConnected = defn.collideConnected
20 | val userData = defn.userData
21 |
22 | /** Node for connecting bodies. */
23 | val node1 = if (body2 != null) JointEdge(body2, this) else null
24 |
25 | /** Node for connecting bodies. */
26 | val node2 = if (body1 != null) JointEdge(body1, this) else null
27 |
28 | var islandFlag = false
29 | var invDt = 0f
30 |
31 | init
32 | def init {
33 | if (body1 != null) body1.jointList = node1 :: body1.jointList
34 | if (body2 != null) body2.jointList = node2 :: body2.jointList
35 | }
36 |
37 | /** Get the anchor point on body1 in world coordinates. */
38 | def anchor1: Vector2
39 | /** Get the anchor point on body2 in world coordinates. */
40 | def anchor2: Vector2
41 | /** Get the anchor point on body1 in local coordinates. */
42 | def localAnchor1: Vector2
43 | /** Get the anchor point on body2 in local coordinates. */
44 | def localAnchor2: Vector2
45 | /** Get the reaction force on body2 at the joint anchor. */
46 | def reactionForce: Vector2
47 | /** Get the reaction torque on body2. */
48 | def reactionTorque: Float
49 |
50 | def initVelocityConstraints(step: TimeStep)
51 | def solveVelocityConstraints(step: TimeStep)
52 |
53 | def initPositionConstraints() {}
54 | /** This returns true if the position errors are within tolerance. */
55 | def solvePositionConstraints(): Boolean
56 | }
57 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/util/Timing.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.util
2 |
3 | object Timing {
4 | def currentTime = System.currentTimeMillis
5 | def nanoTime = System.nanoTime
6 |
7 | lazy val collectedTimes = new collection.mutable.HashMap[String, (Long, Int)]
8 |
9 | def repeat(times: Long)(block: => Unit) = {
10 | assert(times >= 0)
11 | var t = times;
12 | while(t > 0) {
13 | block
14 | t -= 1
15 | }
16 | }
17 |
18 | def repeatWithIndex(times: Long)(block: Long => Unit) = {
19 | assert(times >= 0)
20 | var t = 0;
21 | while(t < times) {
22 | block(t)
23 | t += 1
24 | }
25 | }
26 |
27 | def time[T](name: String)(block: => T) = {
28 | val start = currentTime
29 | try {
30 | block
31 | } finally {
32 | val diff = currentTime - start
33 | println("# Block \"" + name +"\" completed, time taken: " + diff + " ms (" + diff / 1000.0 + " s)")
34 | }
35 | }
36 |
37 | def timeNano[T](name: String)(block: => T) = {
38 | val start = nanoTime
39 | try {
40 | block
41 | } finally {
42 | val diff = nanoTime - start
43 | println("# Block \"" + name +"\" completed, time taken: " + diff + " ns (" + diff / 1000000.0 + " ms)")
44 | }
45 | }
46 |
47 | def collectNanoTime[T](name: String)(block: => T) = {
48 | val start = nanoTime
49 | try {
50 | block
51 | } finally {
52 | val diff = nanoTime - start
53 | val old = collectedTimes.getOrElseUpdate(name, (0,0))
54 | collectedTimes(name) = (old._1 + diff, old._2 + 1)
55 | }
56 | }
57 |
58 | def printCollectedTimes() {
59 | for((name, time) <- collectedTimes) {
60 | println("# Blocks \"" + name +"\" completed, time taken: " + time._1 + " ns (" + time._1 / 1000000.0 + " ms)")
61 | println("# Blocks \"" + name +"\" executions: " + time._2)
62 | val avg = time._1 / time._2
63 | println("# Blocks \"" + name +"\" avg: " + avg + " ns (" + avg / 1000000.0 + " ms)")
64 | }
65 | collectedTimes.clear
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/Controller.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.controllers
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | import collection.Set
7 |
8 | /**
9 | * Base trait for controllers.
10 | * Controllers are a convience for encapsulating common per-step functionality.
11 | */
12 | trait Controller {
13 |
14 | /** The list of bodies that this controller applies to. */
15 | def bodies: Set[Body]
16 |
17 | /** This is called on each world step after updating contacts. */
18 | def step(step: TimeStep)
19 |
20 | /** Iterate over awake bodies. */
21 | protected def forAwakeBodies(f: Body => Unit) =
22 | for (body <- bodies) if (!body.isSleeping) f(body)
23 |
24 | }
25 |
26 | /**
27 | * Controllers should mix this in if they don't have their own body list but
28 | * want to rely on contacts generated by
29 | */
30 | trait SensorManagedBodies extends Controller {
31 |
32 | // TODO make this immutable when Scala supports trait params
33 | private[this] var _sensor: Body = null
34 | /**
35 | * The sensor body that owns this controller. If the sensor is destoryed,
36 | * the controller is destroyed too.
37 | */
38 | def sensor = _sensor
39 | def sensor_=(body: Body) = {
40 | if (_sensor != null) _sensor.dependentControllers -= this
41 | _sensor = body
42 | if (_sensor != null) _sensor.dependentControllers += this
43 | }
44 |
45 | def bodies: Set[Body] =
46 | if (_sensor == null)
47 | Set()
48 | else
49 | Set() ++ _sensor.contactList filter (!_.contact.manifolds.isEmpty) map (_.other)
50 |
51 | }
52 |
53 | /**
54 | * Controllers should mix this in if they manage their own list of bodies.
55 | */
56 | trait SelfManagedBodies extends Controller {
57 |
58 | private[this] val _bodies = collection.mutable.Set[Body]()
59 |
60 | def bodies: Set[Body] = _bodies
61 |
62 | def addBody(body: Body) = {
63 | _bodies += body
64 | body.managingControllers += this
65 | }
66 |
67 | def removeBody(body: Body) = {
68 | _bodies -= body
69 | body.managingControllers -= this
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/Pistons.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 |
7 | object Pistons extends TestbedScene {
8 | val Bullets = false
9 |
10 | def createScene(implicit world: dynamics.World) {
11 | var prevBody = world.groundBody
12 |
13 | // Define crank.
14 | var bd = body {
15 | pos(0.0f, 7.0f)
16 | box(0.5f, 2.0f) density 1.0f
17 | massFromShapes
18 | }
19 |
20 | val joint1 = joint(
21 | revolute(prevBody -> bd)
22 | anchor(0.0f, 5.0f)
23 | motorSpeed 1.0f * 3.1415f
24 | maxMotorTorque Float.MaxValue
25 | enableMotor true
26 | )
27 |
28 | prevBody = bd
29 |
30 | // Define follower.
31 | bd = body {
32 | pos(0.0f, 13.0f)
33 | box(0.5f, 4.0f) density 1.0f
34 | massFromShapes
35 | }
36 |
37 | joint(
38 | revolute(prevBody -> bd)
39 | anchor(0.0f, 9.0f)
40 | enableMotor false
41 | )
42 |
43 | prevBody = bd
44 |
45 | // Define piston
46 | bd = body {
47 | pos(0.0f, 17.0f)
48 | box(5.0f, 1.5f) density 1.0f
49 | massFromShapes
50 | }
51 |
52 | joint(revolute(prevBody -> bd) anchor(0.0f, 17.0f))
53 |
54 | val joint2 = joint(
55 | prismatic(world.groundBody -> bd)
56 | anchor(0.0f, 17.0f)
57 | axis(0.0f, 1.0f)
58 | //maxMotorForce Float.MaxValue
59 | enableMotor false
60 | )
61 |
62 | // Create a payload
63 | for (i <- 0 until 100) body {
64 | pos(-1.0f, 23.0f + i)
65 | box(0.4f,0.3f) density 0.1f
66 | bullet(Bullets)
67 | massFromShapes
68 | }
69 |
70 | for (i <- 0 until 100) body {
71 | pos(1.0f, 23.0f + i)
72 | circle(0.36f) density 2.0f
73 | bullet(Bullets)
74 | massFromShapes
75 | }
76 |
77 | body {
78 | pos(6.1f, 50.0f)
79 | box(1.0f, 100.0f) density 0.0f friction 0.0f
80 | }
81 | body {
82 | pos(-6.1f, 50.0f)
83 | box(1.0f, 100.0f) density 0.0f friction 0.0f
84 | }
85 | }
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/RevoluteJointDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath.Vector2
4 |
5 | /**
6 | * Revolute joint definition. This requires defining an
7 | * anchor point where the bodies are joined. The definition
8 | * uses local anchor points so that the initial configuration
9 | * can violate the constraint slightly. You also need to
10 | * specify the initial relative angle for joint limits. This
11 | * helps when saving and loading a game.
12 | * The local anchor points are measured from the body's origin
13 | * rather than the center of mass because:
14 | * 1. you might not know where the center of mass will be.
15 | * 2. if you add/remove shapes from a body and recompute the mass,
16 | * the joints will be broken.
17 | */
18 | class RevoluteJointDef extends JointDef {
19 | /** The local anchor point relative to body1's origin. */
20 | var localAnchor1 = Vector2.Zero
21 |
22 | /** The local anchor point relative to body2's origin. */
23 | var localAnchor2 = Vector2.Zero
24 |
25 | /** The body2 angle minus body1 angle in the reference state (radians). */
26 | var referenceAngle = 0f
27 |
28 | /** A flag to enable joint limits. */
29 | var enableLimit = false
30 |
31 | /** The lower angle for the joint limit (radians). */
32 | var lowerAngle = 0f
33 |
34 | /** The upper angle for the joint limit (radians). */
35 | var upperAngle = 0f
36 |
37 | /** A flag to enable the joint motor. */
38 | var enableMotor = false
39 |
40 | /** The desired motor speed. Usually in radians per second. */
41 | var motorSpeed = 0f
42 |
43 | /**
44 | * The maximum motor torque used to achieve the desired motor speed.
45 | * Usually in N-m.
46 | */
47 | var maxMotorTorque = 0f
48 |
49 | /**
50 | * Initialize the bodies, anchors, and reference angle using the world anchor.
51 | */
52 | def this(b1: Body, b2: Body, anchor: Vector2) {
53 | this()
54 | body1 = b1;
55 | body2 = b2;
56 | localAnchor1 = body1.toLocalPoint(anchor)
57 | localAnchor2 = body2.toLocalPoint(anchor)
58 | referenceAngle = body2.angle - body1.angle
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/Sweep.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import Settings.Epsilon
6 |
7 | /**
8 | * Primarily for internal use.
9 | *
10 | * Describes the motion of a body/shape for TOI computation.
11 | * Shapes are defined with respect to the body origin, which may
12 | * not coincide with the center of mass. However, to support dynamics
13 | * we must interpolate the center of mass position.
14 | *
15 | * TODO move out of the vecmath package -- this is pretty much tied to the physics engine.
16 | */
17 | class Sweep {
18 | /** Local center of mass position */
19 | var localCenter = Vector2.Zero
20 | /** Center world positions */
21 | var c0, c = Vector2.Zero
22 | /** World angles */
23 | var a0, a = 0f
24 | /** Time interval = [t0,1], where t0 is in [0,1] */
25 | var t0 = 0f
26 |
27 | override def toString() = {
28 | var s = "Sweep:\nlocalCenter: "+localCenter+"\n";
29 | s += "c0: "+c0+", c: "+c+"\n";
30 | s += "a0: "+a0+", a: "+a+"\n";
31 | s += "t0: "+t0+"\n";
32 | s
33 | }
34 |
35 | /**
36 | * Get the interpolated transform at a specific time.
37 | * @param t the normalized time in [0,1].
38 | */
39 | def getTransform(t: Float) = {
40 | // center = p + R * localCenter
41 | val xf = if (1f - t0 > Settings.Epsilon) {
42 | val alpha = (t - t0) / (1f - t0)
43 | val oneLessAlpha = 1f - alpha
44 | //val pos = c0 * (1f - alpha) + c * alpha
45 | val posx = c0.x * oneLessAlpha + c.x * alpha
46 | val posy = c0.y * oneLessAlpha + c.y * alpha
47 | val angle = oneLessAlpha * a0 + alpha * a
48 | Transform2(Vector2(posx, posy), angle)
49 | } else {
50 | Transform2(c, a)
51 | }
52 |
53 | // Shift to origin
54 | Transform2(xf.pos - (xf.rot * localCenter), xf.rot)
55 | }
56 |
57 | /**
58 | * Advance the sweep forward, yielding a new initial state.
59 | * @param t the new initial time.
60 | */
61 | def advance(t: Float) {
62 | if (t0 < t && 1.0f - t0 > Epsilon) {
63 | val alpha = (t - t0) / (1.0f - t0)
64 | c0 = c0 * (1.0f - alpha) + c * alpha
65 | a0 = (1.0f - alpha) * a0 + alpha * a
66 | t0 = t
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/Shape.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import collision._
6 | import dynamics._
7 |
8 | object Shape {
9 | def create(defn: ShapeDef) = defn match {
10 | case d: CircleDef => new Circle(d.pos, d.radius)
11 | case d: PolygonDef => new Polygon(d)
12 | case d: EdgeDef => new Edge(d.v1, d.v2)
13 | case d: EdgeChainDef =>
14 | throw new IllegalArgumentException("Turn edge chain into separate edges when creating shapes!")
15 | case d: PointDef => new Point(d.pos, d.mass)
16 | }
17 | }
18 |
19 | /**
20 | * A shape is used for collision detection.
21 | */
22 | abstract class Shape {
23 | /** Sweep radius relative to the parent body's center of mass. */
24 | def radius: Float
25 |
26 | /**
27 | * Test a point for containment in this shape. This only works for convex shapes.
28 | * @param t the shape world transform.
29 | * @param p a point in world coordinates.
30 | * @return true if the point is within the shape
31 | */
32 | def testPoint(t: Transform2, p: Vector2): Boolean
33 | /**
34 | * Perform a ray cast against this shape.
35 | * @param t world transform
36 | /// @param segment defines the begin and end point of the ray cast.
37 | /// @param maxLambda a number typically in the range [0,1].
38 | * @param lambda returns the hit fraction. You can use this to compute the contact point
39 | /// p = (1 - lambda) * segment.p1 + lambda * segment.p2.
40 | /// @param normal returns the normal at the contact point. If there is no intersection, the normal
41 | /// is not set.
42 | */
43 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float): SegmentCollide
44 |
45 | def computeAABB(t: Transform2): AABB
46 | def computeMass(density: Float): Mass
47 | /**
48 | * Compute the volume and centroid of this shape intersected with a half plane
49 | * @param normal the surface normal
50 | * @param offset the surface offset along normal
51 | * @param xf the shape transform
52 | * @return the total volume less than offset along normal and the centroid
53 | */
54 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2): (Float, Vector2)
55 |
56 | def computeSweepRadius(pivot: Vector2): Float
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/test/scala/org/villane/box2d/testbed/HeadlessPerformanceTest.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed
2 |
3 | import vecmath._
4 | import dynamics._
5 | import dsl._
6 |
7 | class HeadlessPerformanceTest(sceneFactory: SceneFactory) {
8 |
9 | val settings = new TestSettings
10 |
11 | def run: Unit = run(false, 500, 0, false)
12 |
13 | def run(warmUp: Boolean, steps: Int, split: Int, waitForKeypress: Boolean) {
14 | //Settings.threadedIslandSolving = true
15 | //Settings.numThreads = 3
16 | if (warmUp) {
17 | for (j <- 1 to 1) {
18 | val world = sceneFactory.create
19 | var i = 0
20 | while (i < steps) { step(world); i += 1 }
21 | }
22 | }
23 |
24 | val world = sceneFactory.create
25 | Vector2.creationCount = 0
26 | //var x = 0
27 | //m_world.bodyList foreach { b => if (!b.isStatic) x += 1 }
28 | //println("bodycount: " + x)
29 | if (waitForKeypress && split == 0) {
30 | println("Attach profiler and press Enter to start test: ")
31 | Console.readLine
32 | }
33 | var start = System.currentTimeMillis
34 | var i = 0
35 | var splitTime = 0L
36 | while (i < steps) {
37 | if (i == split && split > 0) {
38 | if (waitForKeypress) {
39 | println("Attach profiler and press Enter to start test: ")
40 | Console.readLine
41 | }
42 | splitTime = System.currentTimeMillis
43 | }
44 | step(world)
45 | i += 1
46 | }
47 | /*while (m_world.bodyList exists { b =>
48 | !b.isSleeping && !b.isStatic
49 | }) {
50 | step
51 | }*/
52 | val end = System.currentTimeMillis
53 | println((end - start) + " ms")
54 | if (split > 0) {
55 | println(" first " + split + ": " + (splitTime - start) + " ms")
56 | println(" last " + (steps - split) + ": " + (end - splitTime) + " ms")
57 | }
58 | println("vectors created:" + Vector2.creationCount)
59 | IslandSolverWorker.stopWorkers
60 | util.Timing.printCollectedTimes
61 | }
62 |
63 | def step(world: World) {
64 | var timeStep = if (settings.hz > 0.0f) 1.0f / settings.hz else 0.0f
65 |
66 | world.warmStarting = settings.enableWarmStarting
67 | world.positionCorrection = settings.enablePositionCorrection
68 | world.continuousPhysics = settings.enableTOI
69 |
70 | world.step(timeStep, settings.iterationCount)
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/MultiPointSingleManifoldContact.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | import collision._
4 | import shapes._
5 |
6 | class MultiPointSingleManifoldContact[S1 <: Shape, S2 <: Shape](
7 | f1: Fixture,
8 | f2: Fixture,
9 | collider: Collider[S1, S2]
10 | ) extends SingleManifoldContact(f1, f2, collider) {
11 |
12 | override protected def evaluate(listener: ContactListener, manifold: Option[Manifold]) {
13 | val persisted = Array(false,false)
14 | val oldMH = manifoldHolder
15 | manifoldHolder = manifold
16 | if (manifoldHolder.isDefined) {
17 | val manifold = manifoldHolder.get
18 | // Match old contact ids to new contact ids and copy the
19 | // stored impulses to warm start the solver.
20 | val mpIter = manifold.points.elements
21 | while (mpIter.hasNext) {
22 | val mp = mpIter.next
23 | mp.normalImpulse = 0.0f
24 | mp.tangentImpulse = 0.0f
25 | var found = false
26 | val id = mp.id
27 |
28 | if (oldMH.isDefined) {
29 | val m0 = oldMH.get
30 | var j = 0
31 | val mp0Iter = m0.points.elements
32 | while (mp0Iter.hasNext) {
33 | val mp0 = mp0Iter.next
34 | if (!persisted(j) && !found) {
35 | //val mp0 = m0.points(j)
36 |
37 | if (mp0.id == id) {
38 | persisted(j) = true
39 | mp.normalImpulse = mp0.normalImpulse
40 | mp.tangentImpulse = mp0.tangentImpulse
41 |
42 | // A persistent point.
43 | found = true
44 |
45 | // Report persistent point.
46 | notifyListener(listener, mp, manifold.normal, EventType.Persist)
47 | }
48 | }
49 | j += 1
50 | }
51 | }
52 |
53 | // Report added point.
54 | if (!found) {
55 | notifyListener(listener, mp, manifold.normal, EventType.Add)
56 | }
57 | }
58 | }
59 | if (oldMH.isDefined) {
60 | // Report removed points.
61 | val m0 = oldMH.get
62 | val mp0Iter = m0.points.elements
63 | var i = 0
64 | while (mp0Iter.hasNext) {
65 | val mp = mp0Iter.next
66 | if (!persisted(i))
67 | notifyListener(listener, mp, m0.normal, EventType.Remove)
68 | i += 1
69 | }
70 | }
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/draw/DebugDrawHandler.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.draw
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /**
7 | * Implement this abstract class to allow JBox2d to automatically draw your
8 | * physics for debugging purposes. Not intended to replace your own custom
9 | * rendering routines!
10 | */
11 | trait DebugDrawHandler {
12 | /// Draw a closed polygon provided in CCW order.
13 | def drawPolygon(vertices: Array[Vector2], color: Color3f)
14 |
15 | /// Draw a solid closed polygon provided in CCW order.
16 | def drawSolidPolygon(vertices: Array[Vector2], color: Color3f)
17 |
18 | /// Draw a circle.
19 | def drawCircle(center: Vector2, radius: Float, color: Color3f)
20 |
21 | /// Draw a solid circle.
22 | def drawSolidCircle(center: Vector2, radius: Float, axis: Vector2, color: Color3f)
23 |
24 | /// Draw a point.
25 | def drawPoint(position: Vector2, f: Float, color: Color3f)
26 |
27 | /// Draw a line segment.
28 | def drawSegment(p1: Vector2, p2: Vector2, color: Color3f)
29 |
30 | /// Draw a transform. Choose your own length scale.
31 | /// @param xf a transform.
32 | def drawTransform(xf: Transform2)
33 |
34 | def drawString(x: Float, y: Float, s: String, color: Color3f)
35 |
36 | //All the following should be overridden if the concrete drawing
37 | //class does any sort of camera movement
38 |
39 | /**
40 | * Stub method to overload for camera movement/zoom.
41 | * @param x - x coordinate of camera
42 | * @param y - y coordinate of camera
43 | * @param scale - zoom factor
44 | */
45 | def setCamera(x: Float, y: Float, scale: Float) {}
46 |
47 | /**
48 | * @param screenV Screen position
49 | * @return World position
50 | */
51 | def screenToWorld(screenV: Vector2) = screenV
52 |
53 | /**
54 | * @param screenx Screen x position
55 | * @param screeny Screey y position
56 | * @return World position
57 | */
58 | final def screenToWorld(screenX: Float, screenY: Float): Vector2 = screenToWorld(Vector2(screenX, screenY))
59 |
60 | /**
61 | * @param worldV World position
62 | * @return Screen position
63 | */
64 | def worldToScreen(worldV: Vector2) = worldV
65 |
66 | /**
67 | * @param worldx World x position
68 | * @param worldy World y position
69 | * @return Screen position
70 | */
71 | final def worldToScreen(worldX: Float, worldY: Float): Vector2 = worldToScreen(Vector2(worldX, worldY))
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/controllers/BuoyancyController.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.controllers
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /** Calculates buoyancy forces for fluids in the form of a half plane. */
7 | abstract class BuoyancyController extends Controller {
8 | /** The outer surface normal */
9 | var normal = Vector2.Zero
10 | /** The height of the fluid surface along the normal */
11 | var offset = 0f
12 | /** The fluid density */
13 | var density = 0f
14 | /** Fluid velocity, for drag calculations */
15 | var velocity = Vector2.Zero
16 | /** Linear drag co-efficient */
17 | var linearDrag = 0f
18 | /** Angular drag co-efficient */
19 | var angularDrag = 0f
20 | /**
21 | * If false, bodies are assumed to be uniformly dense, otherwise use the shapes densities.
22 | * False by default to prevent a gotcha.
23 | */
24 | var useDensity = false
25 | /** If true, gravity is taken from the world instead of the gravity parameter. */
26 | var useWorldGravity = false
27 | /** Gravity vector, if the world's gravity is not used */
28 | var gravity = Vector2.Zero
29 |
30 | def step(step: TimeStep) {
31 | //Buoyancy force is just a function of position,
32 | //so unlike most forces, it is safe to ignore sleeping bodes
33 | forAwakeBodies { body =>
34 | var areac = Vector2.Zero
35 | var massc = Vector2.Zero
36 | var area = 0f
37 | var mass = 0f
38 | for (f <- body.fixtures) {
39 | val (sarea, sc) = f.computeSubmergedArea(normal, offset)
40 | area += sarea
41 | areac += sarea * sc
42 | val shapeDensity = if (useDensity) f.density else 1f
43 | mass += sarea * shapeDensity
44 | massc += sarea * sc * shapeDensity
45 | }
46 | areac /= area
47 |
48 | val localCentroid = body.transform ** areac
49 | massc /= mass
50 | if (area >= Settings.Epsilon) {
51 | //Buoyancy
52 | val buoyancyForce = -density * area * gravity
53 | body.applyForce(buoyancyForce, massc)
54 | //Linear drag
55 | var dragForce = body.getLinearVelocityFromWorldPoint(areac) - velocity
56 | dragForce *= -linearDrag * area
57 | body.applyForce(dragForce, areac)
58 | //Angular drag
59 | //TODO: Something that makes more physical sense?
60 | body.applyTorque(-body.I / body.mass * area * body.angularVelocity * angularDrag)
61 | }
62 | }
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/Edge.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import Settings.ε
6 |
7 | /**
8 | * A single edge from v1 to v2
9 | */
10 | class Edge(val v1: Vector2, val v2: Vector2) extends Shape {
11 | // Erkki: is this correct?
12 | val radius = Settings.polygonRadius
13 |
14 | val direction = (v2 - v1).normalize
15 | val length = (v2 - v1).length
16 | val normal = direction.normal
17 |
18 | val corner1Dir = normal
19 | val corner2Dir = normal * -1.0f
20 | val corner1Convex = true // TODO!!!
21 | val corner2Convex = true // TODO!!!
22 |
23 | def testPoint(t: Transform2, p: Vector2) = false
24 |
25 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float) =
26 | new Segment(t * v1, t * v2).testSegment(segment, maxLambda)
27 |
28 | def computeAABB(t: Transform2) = {
29 | val p1 = t * v1
30 | val p2 = t * v2
31 | val r = Vector2(radius, radius)
32 | AABB(min(p1, p2), max(p1, p2))
33 | }
34 |
35 | // ERKKI Shouldn't the center be v1 + (v2 - v1) / 2 ? Or is it not important?
36 | def computeMass(density: Float) = Mass(0, v1, 0)
37 |
38 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2):
39 | (Float, Vector2) = {
40 | //Note that v0 is independent of any details of the specific edge
41 | //We are relying on v0 being consistent between multiple edges of the same body
42 | val v0 = offset * normal
43 | //b2Vec2 v0 = xf.position + (offset - b2Dot(normal, xf.position)) * normal;
44 |
45 | var lv1 = t * v1
46 | var lv2 = t * v2
47 |
48 | val d1 = (normal dot lv1) - offset
49 | val d2 = (normal dot lv2) - offset
50 |
51 | if (d1 > 0.0f) {
52 | if (d2 > 0.0f) {
53 | return (0, Vector2.Zero)
54 | } else {
55 | lv1 = -d2 / (d1 - d2) * lv1 + d1 / (d1 - d2) * lv2
56 | }
57 | } else {
58 | if (d2 > 0.0f) {
59 | lv2 = -d2 / (d1 - d2) * lv1 + d1 / (d1 - d2) * lv2
60 | } else {
61 | // Nothing
62 | }
63 | }
64 |
65 | // v0,v1,v2 represents a fully submerged triangle
66 | val tri = Polygon.Triangle(v0, v1, v2)
67 | // ERKKI in Box2D this centroid is not area weighted
68 | // even as the comment said so
69 | import Polygon.Triangle.inv3
70 | (tri.area, inv3 * (v0 + v1 + v2))
71 | }
72 |
73 | def computeSweepRadius(pivot: Vector2) = {
74 | val ds1 = distanceSquared(v1, pivot)
75 | val ds2 = distanceSquared(v2, pivot)
76 | sqrt(max(ds1, ds2))
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/scenes/DominoTower.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.scenes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl.DSL._
6 | import dynamics.World
7 |
8 | // ERKKI: this was 120 Hz in JBox2D
9 | object DominoTower extends TestbedScene {
10 | val dwidth = 0.2f
11 | val dheight = 1f
12 | var ddensity = 10f
13 | val dfriction = 0.1f
14 | val baseCount = 25
15 |
16 | def makeDomino(x: Float, y: Float, horizontal: Boolean)(implicit world: World) =
17 | body {
18 | pos(x, y)
19 | angle(if (horizontal) Pi / 2 else 0f)
20 | (box(dwidth / 2, dheight / 2)
21 | density ddensity
22 | friction dfriction
23 | restitution 0.65f)
24 | massFromShapes
25 | }
26 |
27 | def createScene(implicit world: dynamics.World) {
28 | body { // Floor
29 | pos(0, -10)
30 | box(50, 10)
31 | }
32 |
33 | //Make bullets
34 | val bs = box(0.7f, 0.7f) density 35f friction 0f restitution 0.85f
35 | var b = body {
36 | bullet(true)
37 | pos(30, 50)
38 | fixture(bs)
39 | massFromShapes
40 | }
41 | b.linearVelocity = (-25, -25)
42 | b.angularVelocity = 6.7f
43 | b = body {
44 | pos(-30, 25)
45 | fixture(bs) density 25f
46 | massFromShapes
47 | }
48 | b.linearVelocity = (35, 10)
49 | b.angularVelocity = -8.3f
50 |
51 | //Make base
52 | for (i <- 0 until baseCount) {
53 | val currX = i * 1.5f * dheight - (1.5f * dheight * baseCount / 2f)
54 | makeDomino(currX, dheight / 2.0f, false)
55 | makeDomino(currX, dheight + dwidth / 2.0f, true)
56 | }
57 |
58 | //Make 'I's
59 | for (j <- 1 until baseCount) {
60 | if (j > 3) ddensity *= .8f
61 | //y at center of 'I' structure
62 | val currY = dheight * 0.5f + (dheight + 2f * dwidth) * 0.99f * j
63 |
64 | for (i <- 0 until baseCount - j) {
65 | // + random(-.05f, .05f);
66 | val currX = i * 1.5f * dheight - (1.5f * dheight * (baseCount - j) /2f)
67 | ddensity *= 2.5f
68 | if (i == 0)
69 | makeDomino(currX - (1.25f * dheight) + 0.5f * dwidth, currY - dwidth, false)
70 | if ((i == baseCount - j - 1) && (j != 1))
71 | makeDomino(currX + (1.25f * dheight) - 0.5f * dwidth, currY - dwidth, false)
72 | ddensity /= 2.5f
73 | makeDomino(currX, currY, false)
74 | makeDomino(currX, currY + 0.5f * (dwidth + dheight), true)
75 | makeDomino(currX, currY - 0.5f * (dwidth + dheight), true)
76 | }
77 | }
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/SingleManifoldContact.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | import vecmath._
4 | import collision._
5 | import shapes._
6 |
7 | /**
8 | * Reusable implementation for contacts between convex shapes i.e. contacts with single manifolds.
9 | */
10 | class SingleManifoldContact[S1 <: Shape, S2 <: Shape](
11 | f1: Fixture,
12 | f2: Fixture,
13 | collider: Collider[S1, S2]
14 | ) extends Contact(f1, f2) {
15 | protected var manifoldHolder: Option[Manifold] = None
16 | def manifolds = if (manifoldHolder.isEmpty) Nil else manifoldHolder.get :: Nil
17 |
18 | def evaluate(listener: ContactListener) = evaluate(
19 | listener,
20 | collider.collide(f1.shape.asInstanceOf[S1], f1.body.transform,
21 | f2.shape.asInstanceOf[S2], f2.body.transform)
22 | )
23 |
24 | protected def evaluate(listener: ContactListener, manifold: Option[Manifold]) {
25 | val oldMH = manifoldHolder
26 | manifoldHolder = manifold
27 | (oldMH, manifoldHolder) match {
28 | case (None, None) =>
29 | case (None, Some(manifold)) =>
30 | val mp = manifold.points(0)
31 | mp.normalImpulse = 0f
32 | mp.tangentImpulse = 0f
33 | notifyListener(listener, mp, manifold.normal, EventType.Add)
34 | case (Some(m0), Some(manifold)) =>
35 | val mp = manifold.points(0)
36 | val mp0 = m0.points(0)
37 | mp.normalImpulse = mp0.normalImpulse
38 | mp.tangentImpulse = mp0.tangentImpulse
39 | notifyListener(listener, mp, manifold.normal, EventType.Persist)
40 | case (Some(m0), None) =>
41 | notifyListener(listener, m0.points(0), m0.normal, EventType.Remove)
42 | }
43 | }
44 |
45 | protected final def notifyListener(listener: ContactListener, mp: ManifoldPoint, normal: Vector2, event: EventType) {
46 | if (listener != null) {
47 | val b1 = fixture1.body
48 | val b2 = fixture2.body
49 | val v1 = b1.getLinearVelocityFromLocalPoint(mp.localPoint1)
50 | val v2 = b2.getLinearVelocityFromLocalPoint(mp.localPoint2)
51 | val cp = ContactPoint(
52 | fixture1,
53 | fixture2,
54 | b1.toWorldPoint(mp.localPoint1),
55 | v2 - v1,
56 | normal,
57 | mp.separation,
58 | friction,
59 | restitution,
60 | mp.id
61 | )
62 | event match {
63 | case EventType.Add => listener.add(cp)
64 | case EventType.Persist => listener.persist(cp)
65 | case EventType.Remove => listener.remove(cp)
66 | }
67 | }
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/PolygonCircleCollider.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import vecmath._
4 | import shapes._
5 | import collision._
6 | import Settings.ε
7 |
8 | object PolygonCircleCollider extends Collider[Polygon, Circle] {
9 |
10 | def collide(polygon: Polygon, xf1: Transform2,
11 | circle: Circle, xf2: Transform2): Option[Manifold] = {
12 | // Compute circle position in the frame of the polygon.
13 | val c = xf2 * circle.pos
14 | val cLocal = xf1 ** c
15 |
16 | // Find edge with maximum separation.
17 | var normalIndex = 0
18 | var separation = -Float.MaxValue
19 |
20 | val vertexCount = polygon.vertexCount
21 | val vertices = polygon.vertices
22 | val normals = polygon.normals
23 | for (i <- 0 until vertexCount) {
24 | val s = normals(i) ∙ (cLocal - vertices(i))
25 | if (s > circle.radius) {
26 | // Early out.
27 | return None
28 | }
29 |
30 | if (s > separation) {
31 | normalIndex = i;
32 | separation = s;
33 | }
34 | }
35 |
36 | // If the center is inside the polygon ...
37 | if (separation < ε) {
38 | val id = ContactID(0, normalIndex, ContactID.NullFeature, false)
39 | val normal = xf1.rot * normals(normalIndex)
40 | val p = c - (normal * circle.radius)
41 | val points = new Array[ManifoldPoint](1)
42 | points(0) = ManifoldPoint(xf1 ** p, xf2 ** p, separation - circle.radius, id)
43 | return Some(Manifold(points, normal))
44 | }
45 |
46 | // Project the circle center onto the edge segment.
47 | val vertIndex1 = normalIndex;
48 | val vertIndex2 = if (vertIndex1 + 1 < vertexCount) vertIndex1 + 1 else 0
49 | var e = vertices(vertIndex2) - vertices(vertIndex1)
50 | val length = e.length
51 | e /= length // normalize
52 | assert(length > ε)
53 |
54 | // Project the center onto the edge.
55 | val u = (cLocal - vertices(vertIndex1)) ∙ e
56 |
57 | val (p, incidentEdge, incidentVertex) = u match {
58 | case _ if u <= 0f => (vertices(vertIndex1), ContactID.NullFeature, vertIndex1)
59 | case _ if u >= length => (vertices(vertIndex2), ContactID.NullFeature, vertIndex2)
60 | case _ => (vertices(vertIndex1) + (e * u), normalIndex, 0)
61 | }
62 |
63 | var d = cLocal - p
64 | val dist = d.length
65 | d /= dist // normalize
66 |
67 | if (dist > circle.radius) {
68 | return None
69 | }
70 |
71 | val id = ContactID(0, incidentEdge, incidentVertex, false)
72 | val normal = xf1.rot * d
73 | val pos = c - (normal * circle.radius)
74 | val points = new Array[ManifoldPoint](1)
75 | points(0) = ManifoldPoint(xf1 ** pos, xf2 ** pos, dist - circle.radius, id)
76 | Some(Manifold(points, normal))
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/Circle.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import Settings.ε
6 |
7 | /**
8 | * Circle
9 | *
10 | * pos - position
11 | * radius - radius of the circle
12 | */
13 | class Circle(val pos: Vector2, val radius: Float) extends Shape {
14 |
15 | def testPoint(t: Transform2, p: Vector2) = {
16 | val center = t * pos
17 | val d = p - center
18 | (d ∙ d) <= (radius * radius)
19 | }
20 |
21 | /**
22 | * Collision Detection in Interactive 3D Environments by Gino van den Bergen
23 | * From Section 3.1.2
24 | * x = s + a * r
25 | * norm(x) = radius
26 | */
27 | def testSegment(t: Transform2, segment: Segment, maxLambda: Float): SegmentCollide = {
28 | val p = t * pos
29 | val s = segment.p1 - p
30 | val b = (s dot s) - radius * radius
31 |
32 | // Does the segment start inside the circle?
33 | if (b < 0.0f)
34 | return SegmentCollide.startsInside(0, Vector2.Zero)
35 |
36 | // Solve quadratic equation.
37 | val r = segment.p2 - segment.p1
38 | val c = s dot r
39 | val rr = r dot r
40 | val sigma = c * c - rr * b
41 |
42 | // Check for negative discriminant and short segment.
43 | if (sigma < 0.0f || rr < ε)
44 | return SegmentCollide.Miss
45 |
46 | // Find the point of intersection of the line with the circle.
47 | var a = -(c + sqrt(sigma))
48 |
49 | // Is the intersection point on the segment?
50 | if (0.0f <= a && a <= maxLambda * rr) {
51 | a /= rr
52 | return SegmentCollide.hit(a, (s + a * r).normalize)
53 | }
54 |
55 | SegmentCollide.Miss
56 | }
57 |
58 | def computeAABB(t: Transform2) = {
59 | val p = t * pos
60 | AABB(p - radius, p + radius)
61 | }
62 |
63 | def computeMass(density: Float) = {
64 | val mass = density * π * radius * radius
65 | // inertia about the local origin
66 | val i = mass * (0.5f * radius * radius + (pos ∙ pos))
67 | Mass(mass, pos, i)
68 | }
69 |
70 | def computeSubmergedArea(normal: Vector2, offset: Float, t: Transform2):
71 | (Float, Vector2) = {
72 | val p = t * pos
73 | val l = -((normal dot p) - offset)
74 | if (l < -radius + ε) {
75 | // Completely dry
76 | return (0, Vector2.Zero)
77 | }
78 | if (l > radius) {
79 | // Completely wet
80 | return (π * radius * radius, p)
81 | }
82 |
83 | //Magic
84 | val r2 = radius * radius
85 | val l2 = l * l
86 | val area = r2 * (asin(l / radius) + π / 2) + l * sqrt(r2 - l2)
87 | val com = -2.0f / 3.0f * pow(r2-l2, 1.5f) / area
88 |
89 | (area, p + normal * com)
90 | }
91 |
92 | def computeSweepRadius(pivot: Vector2) = {
93 | // ERKKI : in Box2D now it is: = distance(pos, pivot)
94 | val d = pos - pivot
95 | d.length + radius - Settings.toiSlop
96 | }
97 |
98 | }
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | org.villane.box2d
5 | scalabox2d-parent
6 | ScalaBox2D - 2D Physics Engine for Games (Parent POM)
7 | 2.0.1-SNAPSHOT
8 | pom
9 |
10 | scalabox2d
11 | scalabox2d-svg
12 | scalabox2d-testbed
13 |
14 |
15 | 2.7.4
16 |
17 |
18 |
19 | scala-tools.org
20 | Scala-Tools Maven2 Repository
21 | http://scala-tools.org/repo-releases
22 |
23 |
24 |
25 |
26 | scala-tools.org
27 | Scala-Tools Maven2 Repository
28 | http://scala-tools.org/repo-releases
29 |
30 |
31 |
32 |
33 | org.scala-lang
34 | scala-library
35 | ${scala.version}
36 | compile
37 |
38 |
39 |
40 |
41 |
42 | org.apache.maven.plugins
43 | maven-source-plugin
44 |
45 |
46 | attach-sources
47 |
48 | jar
49 |
50 |
51 |
52 |
53 |
54 | org.scala-tools
55 | maven-scala-plugin
56 |
57 |
58 |
59 | compile
60 | testCompile
61 |
62 |
63 |
64 |
65 | ${scala.version}
66 |
67 | -optimise
68 | -Yinline
69 |
70 |
71 |
72 |
73 | maven-compiler-plugin
74 |
75 | 1.6
76 | 1.6
77 | UTF-8
78 |
79 |
80 |
81 | maven-resources-plugin
82 |
83 | UTF-8
84 |
85 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/shapes/ShapeDef.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.shapes
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /**
7 | * Superclass for shape definitions.
8 | *
9 | * Shape definitions are mutable to make them easier to use.
10 | */
11 | sealed abstract class ShapeDef
12 |
13 | /** A circle definition. */
14 | final case class CircleDef(var pos: Vector2, var radius: Float) extends ShapeDef
15 |
16 | /** Point definition. Like a 0-radius circle, but has mass */
17 | final case class PointDef(var pos: Vector2, var mass: Float) extends ShapeDef
18 |
19 | object EdgeChainDef {
20 | def apply(vertices: Vector2*): EdgeChainDef = apply(false, vertices.toArray)
21 | def loop(vertices: Vector2*): EdgeChainDef = apply(true, vertices.toArray)
22 | }
23 |
24 | /** A chain of connected edges */
25 | final case class EdgeChainDef(
26 | /** Whether to create an extra edge between the first and last vertices. */
27 | var loop: Boolean,
28 | /** The vertices in local coordinates. */
29 | var vertices: Array[Vector2]
30 | ) extends ShapeDef {
31 | def edges: Array[EdgeDef] = {
32 | val len = if (loop) vertices.length else vertices.length - 1
33 | val edges = new Array[EdgeDef](len)
34 | for (i <- 0 until len) {
35 | edges(i) = EdgeDef(vertices(i), vertices((i + 1) % vertices.length))
36 | }
37 | edges
38 | }
39 | }
40 |
41 | final case class EdgeDef(
42 | var v1: Vector2,
43 | var v2: Vector2
44 | ) extends ShapeDef
45 |
46 | object PolygonDef {
47 | /**
48 | * Build vertices to represent an axis-aligned box.
49 | * @param hx the half-width.
50 | * @param hy the half-height.
51 | */
52 | def box(hx: Float, hy: Float): PolygonDef = apply(Array((-hx,-hy),(hx,-hy),(hx,hy),(-hx,hy)))
53 |
54 | /**
55 | * Build vertices to represent an oriented box.
56 | * @param hx the half-width.
57 | * @param hy the half-height.
58 | * @param center the center of the box in local coordinates.
59 | */
60 | def box(hx: Float, hy: Float, center: Vector2): PolygonDef = {
61 | val pd = box(hx, hy)
62 | val xf = Transform2(center, Matrix22.Identity)
63 | pd.vertices = pd.vertices.map(v => xf * v)
64 | pd
65 | }
66 |
67 | /**
68 | * Build vertices to represent an oriented box.
69 | * @param hx the half-width.
70 | * @param hy the half-height.
71 | * @param center the center of the box in local coordinates.
72 | * @param angle the rotation of the box in local coordinates.
73 | */
74 | def box(hx: Float, hy: Float, center: Vector2, angle: Float): PolygonDef = {
75 | val pd = box(hx, hy)
76 | val xf = Transform2(center, angle)
77 | pd.vertices = pd.vertices.map(v => xf * v)
78 | pd
79 | }
80 |
81 | }
82 |
83 | /**
84 | * Convex polygon definition.
85 | *
86 | * The vertices must be in CCW order for a right-handed coordinate system
87 | * with the z-axis coming out of the screen.
88 | */
89 | final case class PolygonDef(
90 | /** The CCW vertices in local coordinates. */
91 | var vertices: Array[Vector2]
92 | ) extends ShapeDef
93 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dsl/JointBuilders.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dsl
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes._
6 | import collision._
7 | import dynamics._
8 | import dynamics.joints._
9 |
10 | class JointBuilder(j: JointDef) {
11 | def collideConnected(v: Boolean) = { j.collideConnected = v; this }
12 | def userData(v: AnyRef) = { j.userData = v; this }
13 | def define = j
14 | }
15 |
16 | class BodyJointBuilder(j: JointDef, bodies: (Body, Body))
17 | extends JointBuilder(j) {
18 | j.body1 = bodies._1
19 | j.body2 = bodies._2
20 | }
21 |
22 | class GearJointBuilder(j: GearJointDef, joints: (Joint, Joint))
23 | extends JointBuilder(j) {
24 | j.joint1 = joints._1
25 | j.joint2 = joints._2
26 | def ratio(v: Float) = { j.ratio = v; this }
27 | }
28 |
29 | class DistanceJointBuilder(j: DistanceJointDef, bodies: (Body, Body))
30 | extends BodyJointBuilder(j, bodies) {
31 | def localAnchor1(v: Vector2) = { j.localAnchor1 = v; this }
32 | def localAnchor2(v: Vector2) = { j.localAnchor2 = v; this }
33 | def length(v: Float) = { j.length = v; this }
34 | def frequencyHz(v: Float) = { j.frequencyHz = v; this }
35 | def dampingRatio(v: Float) = { j.dampingRatio = v; this }
36 | }
37 |
38 | class RevoluteJointBuilder(j: RevoluteJointDef, bodies: (Body, Body))
39 | extends BodyJointBuilder(j, bodies) {
40 | def localAnchor1(v: Vector2) = { j.localAnchor1 = v; this }
41 | def localAnchor2(v: Vector2) = { j.localAnchor2 = v; this }
42 | def referenceAngle(v: Float) = { j.referenceAngle = v; this }
43 | def anchor(v: Vector2) = {
44 | j.localAnchor1 = j.body1.toLocalPoint(v)
45 | j.localAnchor2 = j.body2.toLocalPoint(v)
46 | j.referenceAngle = j.body2.angle - j.body1.angle
47 | this
48 | }
49 | def enableLimit(v: Boolean) = { j.enableLimit = v; this }
50 | def lowerAngle(v: Float) = { j.lowerAngle = v; this }
51 | def upperAngle(v: Float) = { j.upperAngle = v; this }
52 | def enableMotor(v: Boolean) = { j.enableMotor = v; this }
53 | def motorSpeed(v: Float) = { j.motorSpeed = v; this }
54 | def maxMotorTorque(v: Float) = { j.maxMotorTorque = v; this }
55 | }
56 |
57 | class PrismaticJointBuilder(j: PrismaticJointDef, bodies: (Body, Body))
58 | extends BodyJointBuilder(j, bodies) {
59 | def localAnchor1(v: Vector2) = { j.localAnchor1 = v; this }
60 | def localAnchor2(v: Vector2) = { j.localAnchor2 = v; this }
61 | def referenceAngle(v: Float) = { j.referenceAngle = v; this }
62 | def anchor(v: Vector2) = {
63 | j.localAnchor1 = j.body1.toLocalPoint(v)
64 | j.localAnchor2 = j.body2.toLocalPoint(v)
65 | j.referenceAngle = j.body2.angle - j.body1.angle
66 | this
67 | }
68 | def localAxis1(v: Vector2) = { j.localAxis1 = v; this }
69 | def axis(v: Vector2) = {
70 | j.localAxis1 = j.body1.toLocalVector(v);
71 | this
72 | }
73 | def enableLimit(v: Boolean) = { j.enableLimit = v; this }
74 | def lowerTranslation(v: Float) = { j.lowerTranslation = v; this }
75 | def upperTranslation(v: Float) = { j.upperTranslation = v; this }
76 | def enableMotor(v: Boolean) = { j.enableMotor = v; this }
77 | def motorSpeed(v: Float) = { j.motorSpeed = v; this }
78 | def maxMotorForce(v: Float) = { j.maxMotorForce = v; this }
79 | }
80 |
--------------------------------------------------------------------------------
/scalabox2d-svg/src/main/scala/org/villane/box2d/svg/SlickSVGSceneLoader.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.svg
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dsl._
6 | import dsl.DSL._
7 |
8 | import shapes._
9 | import dynamics._
10 |
11 | import ConvexHull._
12 |
13 | import java.io.FileInputStream
14 | import org.newdawn.slick.Color
15 | import org.newdawn.slick.svg._
16 | import org.newdawn.slick.geom
17 |
18 | /**
19 | * TODO offset?
20 | */
21 | class SlickSVGSceneLoader(fileName: String, scale: Float)
22 | extends SceneFactory {
23 |
24 | implicit def slickVector2fToVector2(v: geom.Vector2f) = Vector2(v.x, v.y)
25 |
26 | var gravity = Vector2(0, 9.81f)
27 |
28 | def create = {
29 | val svg = InkscapeLoader.load(new FileInputStream(fileName), false)
30 | val aabb = AABB(Vector2.Zero, (svg.getWidth, svg.getHeight) / scale)
31 | val world = new World(aabb, gravity, true)
32 | createScene(svg)(world)
33 | world
34 | }
35 |
36 | def createScene(svg: Diagram)(implicit world: World) {
37 | for (i <- 0 until svg.getFigureCount) {
38 | createFigure(svg.getFigure(i))
39 | }
40 | }
41 |
42 | def createFigure(fig: Figure)(implicit world: World) = fig.getType match {
43 | case Figure.ELLIPSE =>
44 | val c = fig.getShape.asInstanceOf[geom.Shape]
45 | body {
46 | pos((c.getCenterX, c.getCenterY) / scale)
47 | circle((c.getMinX - c.getCenterX).abs / scale) density 1
48 | if (isDynamic(fig)) massFromShapes
49 | }
50 | case Figure.LINE =>
51 | val line = fig.getShape.asInstanceOf[geom.Line]
52 | body {
53 | edge(line.getStart / scale, line.getEnd / scale)
54 | }
55 | case Figure.RECTANGLE =>
56 | val poly = fig.getShape.asInstanceOf[geom.Polygon]
57 | body {
58 | pos((poly.getCenterX, poly.getCenterY) / scale)
59 | box(
60 | (poly.getMinX - poly.getCenterX).abs / scale,
61 | (poly.getMinY - poly.getCenterY).abs / scale
62 | ) density 1
63 | if (isDynamic(fig)) massFromShapes
64 | }
65 | case Figure.PATH =>
66 | val path = fig.getShape.asInstanceOf[geom.Path]
67 | body {
68 | val ps = new Array[Vector2](path.getPointCount)
69 | for (i <- 0 until path.getPointCount) {
70 | val p = path.getPoint(i)
71 | ps(i) = (p(0), p(1)) / scale
72 | }
73 | edge(ps:_*)
74 | }
75 | case Figure.POLYGON =>
76 | val poly = fig.getShape.asInstanceOf[geom.Polygon]
77 | body {
78 | pos(poly.getCenterX / scale, poly.getCenterY / scale)
79 | val ps = new Array[Vector2](poly.getPointCount)
80 | for (i <- 0 until poly.getPointCount) {
81 | val p = poly.getPoint(i)
82 | ps(i) = (p(0) - poly.getCenterX, p(1) - poly.getCenterY) / scale
83 | }
84 | if (ps.length <= 8 && poly.closed) {
85 | polygon(convexHull(ps):_*) density 1
86 | if (isDynamic(fig)) massFromShapes
87 | } else {
88 | edge(ps:_*)
89 | }
90 | }
91 | }
92 |
93 | def isDynamic(fig: Figure) =
94 | fig.getData.getAttribute("fill") != null &&
95 | fig.getData.isColor("fill") &&
96 | Color.red == fig.getData.getAsColor("fill")
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/Matrix22.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | import Preamble._
4 |
5 | object Matrix22 {
6 | val Zero = Matrix22(0, 0, 0, 0)
7 | val One = Matrix22(1, 1, 1, 1)
8 | val Identity = Matrix22(1, 0, 0, 1)
9 |
10 | def apply(col1: Vector2, col2: Vector2): Matrix22 =
11 | Matrix22(col1.x, col2.x, col1.y, col2.y)
12 |
13 | /**
14 | * Creates a rotation matrix from an angle.
15 | *
16 | * @param angle rotation in radians
17 | */
18 | def rotation(angle: Float) = {
19 | val c = cos(angle)
20 | val s = sin(angle)
21 | Matrix22(c, -s, s, c)
22 | }
23 | }
24 |
25 | /**
26 | * A 2-by-2 matrix. Stored in row-major order.
27 | *
28 | * Design note: Matrix22 could conceptually extend Tuple4, but does not for efficiency.
29 | * See the same note about Vector2 for details.
30 | *
31 | * TODO compute eigenvalues / max eigenvalue
32 | */
33 | case class Matrix22(a11: Float, a12: Float, a21: Float, a22: Float) {
34 | def col1 = Vector2(a11, a21)
35 | def col2 = Vector2(a12, a22)
36 |
37 | // TODO +,-,*,/ Float
38 | def +(a: Float) = Matrix22(a11 + a, a12 + a, a21 + a, a22 + a)
39 |
40 | def +(m: Matrix22) = Matrix22(a11 + m.a11, a12 + m.a12,
41 | a21 + m.a21, a22 + m.a22)
42 |
43 | /**
44 | * Multiply a vector by this matrix.
45 | * @param v Vector to multiply by matrix.
46 | * @return Resulting vector
47 | */
48 | def *(v: Vector2) = Vector2(a11 * v.x + a12 * v.y,
49 | a21 * v.x + a22 * v.y)
50 |
51 | /**
52 | * Multiply a vector by the transpose of this matrix. (mulT)
53 | * @param v
54 | * @return
55 | */
56 | def **(v: Vector2) = Vector2(a11 * v.x + a21 * v.y,
57 | a12 * v.x + a22 * v.y)
58 |
59 | def determinant = a11 * a22 - a12 * a21
60 |
61 | /**
62 | * Solve A * x = b where A = this matrix.
63 | * @return The vector x that solves the above equation.
64 | */
65 | def solve(b: Vector2) = {
66 | var det = determinant
67 | assert (det != 0.0f)
68 | det = 1 / det
69 | Vector2(det * (a22 * b.x - a12 * b.y),
70 | det * (a11 * b.y - a21 * b.x))
71 | }
72 |
73 | /**
74 | * Multiply another matrix by this one.
75 | */
76 | // Matrix22(this * m.col1, this * m.col2)
77 | def *(m: Matrix22): Matrix22 = Matrix22(
78 | a11 * m.a11 + a12 * m.a21,
79 | a11 * m.a12 + a12 * m.a22,
80 | a21 * m.a11 + a22 * m.a21,
81 | a21 * m.a12 + a22 * m.a22
82 | )
83 |
84 | /**
85 | * Multiply another matrix by the transpose of this one.
86 | */
87 | def **(m: Matrix22): Matrix22 = Matrix22(
88 | a11 * m.a11 + a21 * m.a21,
89 | a11 * m.a12 + a21 * m.a22,
90 | a12 * m.a11 + a22 * m.a21,
91 | a12 * m.a12 + a22 * m.a22
92 | )
93 |
94 | def transpose = Matrix22(a11, a21, a12, a22)
95 |
96 | def abs = Matrix22(Math.abs(a11), Math.abs(a12),
97 | Math.abs(a21), Math.abs(a22))
98 |
99 | def invert = {
100 | var det = determinant
101 | assert (det != 0.0f)
102 | det = 1 / det
103 | Matrix22(det * a22, -det * a12,
104 | -det * a21, det * a11)
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/vecmath/Vector2.scala:
--------------------------------------------------------------------------------
1 | package org.villane.vecmath
2 |
3 | import Preamble._
4 |
5 | /**
6 | * Vector2 creation methods and often used constant values.
7 | */
8 | object Vector2 {
9 | val Zero = new Vector2(0, 0)
10 | val One = Vector2(1, 1)
11 | val XUnit = Vector2(1, 0)
12 | val YUnit = Vector2(0, 1)
13 | def polar(r: Float, theta: Float) = Vector2(cos(theta) * r, sin(theta) * r)
14 |
15 | def lerp(begin: Vector2, end: Vector2, scalar: Float) = Vector2(
16 | begin.x + scalar * (end.x - begin.x),
17 | begin.y + scalar * (end.y - begin.y)
18 | )
19 |
20 | // for debugging only
21 | var creationCount = 0L
22 | }
23 |
24 | /**
25 | * An immutable 2D Vector represented as x and y coordinates in single precision
26 | * floating point numbers.
27 | *
28 | * TODO Design ideas:
29 | *
30 | * Vector2 could extend Tuple2(x, y), but this causes inefficiency:
31 | * tuple2 will store x,y as objects, causing boxing. Otherwise we could add:
32 | * extends Tuple2(x, y) { override def swap = Vector2(y, x) }
33 | *
34 | * Reconsider with Scala 2.8 @specialized
35 | *
36 | * Idea: provide mutable version for big computations
37 | */
38 | case class Vector2(x: Float, y: Float) {
39 | def +(a: Float) = Vector2(x + a, y + a)
40 | def -(a: Float) = Vector2(x - a, y - a)
41 | def *(a: Float) = Vector2(x * a, y * a)
42 | def /(a: Float) = Vector2(x / a, y / a)
43 |
44 | def cross(a: Float) = Vector2(a * y, -a * x)
45 | def ×(a: Float) = Vector2(a * y, -a * x)
46 |
47 | def +(v: Vector2) = Vector2(x + v.x, y + v.y)
48 | def -(v: Vector2) = Vector2(x - v.x, y - v.y)
49 |
50 | def dot(v: Vector2) = x * v.x + y * v.y
51 | def ∙(v: Vector2) = x * v.x + y * v.y
52 |
53 | def cross(v: Vector2) = x * v.y - y * v.x
54 | def ×(v: Vector2) = x * v.y - y * v.x
55 |
56 | def normal = Vector2(y, -x) // = ×(1)
57 | def unary_- = Vector2(-x, -y)
58 | def swap = Vector2(y, x)
59 | def abs = Vector2(x.abs, y.abs)
60 |
61 | /** Polar coordinates */
62 | def theta = atan2(y, x)
63 | def θ = atan2(y, x)
64 |
65 | def to(v: Vector2) = new Vector2Range(this, v, Vector2.One)
66 | def times(n: Int) = new Vector2Times(this, n, Vector2.One)
67 |
68 | /**
69 | * Since normalization is a simple operation, in cases where speed is desired,
70 | * but the length before normalization is also needed, use this instead:
71 | *
72 | * val len = v.length
73 | * v /= len
74 | *
75 | */
76 | def normalize = this / length
77 | /** @see normalize */
78 | def unit = this / length
79 |
80 | def length = sqrt(x * x + y * y)
81 | def lengthSquared = x * x + y * y
82 |
83 | // Unlike with float extensions, calling clamp on an existing vector doesn't effect performance, so this shouldn't be static.
84 | def clamp(low: Vector2, high: Vector2) = Preamble.max(low, Preamble.min(this, high))
85 |
86 | // TODO remove this if Scala 2.8 allows Vector to extend Tuple2 without performance hit
87 | def tuple = (x, y)
88 |
89 | // Inlined for performance
90 | def isValid = x != Float.NaN && x != Float.NegativeInfinity && x != Float.PositiveInfinity &&
91 | y != Float.NaN && y != Float.NegativeInfinity && y != Float.PositiveInfinity
92 |
93 | override def productPrefix = ""
94 |
95 | // for debugging only
96 | Vector2.creationCount += 1
97 | }
98 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/Fixture.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import vecmath._
4 | import vecmath.Preamble
5 | import shapes._
6 | import collision._
7 | import broadphase.BroadPhase
8 | import broadphase.PairManager
9 |
10 | object Fixture {
11 | def apply(body: Body, defn: FixtureDef) = new Fixture(defn, body)
12 | }
13 |
14 | class Fixture(defn: FixtureDef, val body: Body) {
15 | val shape = Shape.create(defn.shapeDef)
16 |
17 | /** The shape's friction coefficient, usually in the range [0,1]. */
18 | var friction = defn.friction
19 |
20 | /** The shape's restitution (elasticity) usually in the range [0,1]. */
21 | var restitution = defn.restitution
22 |
23 | /** Sweep radius relative to the parent body's center of mass. */
24 | var sweepRadius = 0f
25 |
26 | /** The shape's density, usually in kg/m^2. */
27 | var density = defn.density
28 |
29 | var proxyId = PairManager.NullProxy
30 |
31 | /**
32 | * A sensor shape collects contact information but never generates a collision
33 | * response.
34 | */
35 | var isSensor = defn.isSensor
36 |
37 | /** Contact filtering data. */
38 | var filter = defn.filter
39 |
40 | /** Use this to store application specify shape data. */
41 | var userData = defn.userData
42 |
43 | def computeMass() = shape.computeMass(density)
44 |
45 | def computeSubmergedArea(normal: Vector2, offset: Float): (Float, Vector2) =
46 | shape.computeSubmergedArea(normal, offset, body.transform)
47 |
48 | def computeSweepRadius(pivot: Vector2) = shape.computeSweepRadius(pivot)
49 |
50 | /** Internal */
51 | def createProxy(broadPhase: BroadPhase, transform: Transform2) {
52 | assert(proxyId == PairManager.NullProxy)
53 |
54 | val aabb = shape.computeAABB(transform)
55 |
56 | val inRange = broadPhase.inRange(aabb)
57 |
58 | assert(inRange, "You are creating a shape outside the world box.")
59 |
60 | proxyId = if (inRange)
61 | broadPhase.createProxy(aabb, this)
62 | else
63 | PairManager.NullProxy
64 | }
65 |
66 | /** Internal */
67 | def destroyProxy(broadPhase: BroadPhase) {
68 | if (proxyId != PairManager.NullProxy) {
69 | broadPhase.destroyProxy(proxyId)
70 | proxyId = PairManager.NullProxy
71 | }
72 | // ERKKI: box2d de-allocates the shape here
73 | }
74 |
75 | /** Internal */
76 | def synchronize(broadPhase: BroadPhase, t1: Transform2, t2: Transform2): Boolean = {
77 | if (proxyId == PairManager.NullProxy) return false
78 |
79 | // Compute an AABB that covers the swept shape (may miss some rotation effect).
80 | val aabb = shape.computeAABB(t1) combineWith shape.computeAABB(t2)
81 | if (broadPhase.inRange(aabb)) {
82 | broadPhase.moveProxy(proxyId, aabb)
83 | true
84 | } else {
85 | false
86 | }
87 | }
88 |
89 | /** Internal */
90 | def refilterProxy(broadPhase: BroadPhase, transform: Transform2) {
91 | if (proxyId == PairManager.NullProxy) return
92 |
93 | broadPhase.destroyProxy(proxyId)
94 |
95 | val aabb = shape.computeAABB(transform)
96 |
97 | if (broadPhase.inRange(aabb)) {
98 | proxyId = broadPhase.createProxy(aabb, this)
99 | } else {
100 | proxyId = PairManager.NullProxy
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/ContactManager.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import contacts._
4 | import shapes._
5 | import collision._
6 | import broadphase.PairListener
7 |
8 | /** Delegate of World - for internal use. */
9 | class ContactManager extends PairListener {
10 | var world: World = null
11 |
12 | val destroyImmediate = false
13 |
14 | def pairAdded(proxyUserData1: AnyRef, proxyUserData2: AnyRef): AnyRef = {
15 | val fixture1 = proxyUserData1.asInstanceOf[Fixture]
16 | val fixture2 = proxyUserData2.asInstanceOf[Fixture]
17 |
18 | val body1 = fixture1.body
19 | val body2 = fixture2.body
20 |
21 | if (body1.isStatic && body2.isStatic) {
22 | return NullContact
23 | }
24 |
25 | if (body1 eq body2) {
26 | return NullContact
27 | }
28 |
29 | if (body2.isConnected(body1)) {
30 | return NullContact
31 | }
32 |
33 | if (world.contactFilter != null && !world.contactFilter.shouldCollide(fixture1, fixture2)) {
34 | return NullContact
35 | }
36 |
37 | // Call the factory.
38 | val c = Contact(fixture1, fixture2)
39 |
40 | if (c == null) {
41 | return NullContact
42 | }
43 |
44 | // Insert into the world.
45 | world.contactList += c
46 | // world.contactCount += 1
47 |
48 | c
49 | }
50 |
51 | // This is a callback from the broadphase when two AABB proxies cease
52 | // to overlap. We retire the b2Contact.
53 | def pairRemoved(proxyUserData1: AnyRef, proxyUserData2: AnyRef, pairUserData: AnyRef) {
54 | //B2_NOT_USED(proxyUserData1);
55 | //B2_NOT_USED(proxyUserData2);
56 |
57 | if (pairUserData == null) {
58 | return
59 | }
60 |
61 | val c = pairUserData.asInstanceOf[Contact]
62 | if (c == NullContact) {
63 | return
64 | }
65 |
66 | // An attached body is being destroyed, we must destroy this contact
67 | // immediately to avoid orphaned shape pointers.
68 | destroy(c)
69 | }
70 |
71 | def destroy(c: Contact) {
72 | val f1 = c.fixture1
73 | val f2 = c.fixture2
74 |
75 | // Inform the user that this contact is ending.
76 | val manifoldCount = c.manifolds.length
77 | if (manifoldCount > 0 && (world.contactListener != null)) {
78 | val b1 = f1.body
79 | val b2 = f2.body
80 | for (manifold <- c.manifolds) {
81 | val normal = manifold.normal
82 | for (mp <- manifold.points) {
83 | val pos = b1.toWorldPoint(mp.localPoint1)
84 | val v1 = b1.getLinearVelocityFromLocalPoint(mp.localPoint1)
85 | val v2 = b2.getLinearVelocityFromLocalPoint(mp.localPoint2)
86 | val cp = ContactPoint(f1, f2, pos, v2 - v1, manifold.normal, mp.separation, c.friction, c.restitution, mp.id)
87 | world.contactListener.remove(cp)
88 | }
89 | }
90 | }
91 |
92 | // TODO XXX HACK This probably has horrible performance!
93 | // Contact lists should be optimized for random removal as well
94 | // Remove from the world.
95 | world.contactList -= c
96 | val body1 = f1.body
97 | val body2 = f2.body
98 |
99 | // Remove from body 1
100 | body1.contactList = body1.contactList.remove(_.contact == c)
101 |
102 | // Remove from body 2
103 | body2.contactList = body2.contactList.remove(_.contact == c)
104 |
105 | Contact.destroy(c)
106 | // world.contactCount -= 1
107 | }
108 |
109 | def collide() {
110 | // Update awake contacts.
111 | var iC = 0
112 | while (iC < world.contactList.length) {
113 | val c = world.contactList(iC)
114 | iC += 1
115 |
116 | val body1 = c.fixture1.body
117 | val body2 = c.fixture2.body
118 | if (!body1.isSleeping || !body2.isSleeping) {
119 | c.update(world.contactListener)
120 | }
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/PointingDeviceJoint.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import dynamics._
6 |
7 | //p = attached point, m = mouse point
8 | //C = p - m
9 | //Cdot = v
10 | // = v + cross(w, r)
11 | //J = [I r_skew]
12 | //Identity used:
13 | //w k % (rx i + ry j) = w * (-ry i + rx j)
14 | class PointingDeviceJoint(defn: PointingDeviceJointDef) extends Joint(defn) {
15 | private[this] var _target = defn.target
16 | var localAnchor = body2.transform ** _target
17 | var force = Vector2.Zero
18 | var mass = Matrix22.Zero // effective mass for point-to-point constraint.
19 | var C = Vector2.Zero // position error
20 |
21 | var maxForce = defn.maxForce
22 | var beta = 0f // bias factor
23 | var gamma = 0f // softness
24 |
25 | initPDJ
26 | def initPDJ {
27 | val m = body2.mass
28 | // Frequency
29 | val ω = 2.0f * π * defn.frequencyHz
30 | // Damping coefficient
31 | val d = 2.0f * m * defn.dampingRatio * ω
32 | // Spring stiffness
33 | val k = m * ω * ω
34 | // magic formulas
35 | gamma = 1.0f / (d + defn.timeStep * k)
36 | beta = defn.timeStep * k / (d + defn.timeStep * k)
37 | }
38 |
39 | def target = _target
40 | /** Use this to update the target point. */
41 | def target_=(target: Vector2) {
42 | if (body2.isSleeping) body2.wakeUp()
43 | this._target = target
44 | }
45 |
46 | def anchor1 = target
47 | def anchor2 = body2.toWorldPoint(localAnchor)
48 | def localAnchor1 = Vector2.Zero
49 | def localAnchor2 = localAnchor
50 | def reactionForce = force
51 | def reactionTorque = 0f
52 |
53 | def initVelocityConstraints(step: TimeStep) {
54 | val b = body2
55 |
56 | // Compute the effective mass matrix.
57 | val r = b.transform.rot * (localAnchor - b.localCenter)
58 |
59 | // K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2)
60 | // * invI2 * skew(r2)]
61 | // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 *
62 | // [r1.y*r1.y -r1.x*r1.y]
63 | // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x]
64 | val invMass = b.invMass
65 | val invI = b.invI
66 |
67 | val K1 = Matrix22(invMass, 0.0f, 0.0f, invMass)
68 | val K2 = Matrix22(invI * r.y * r.y, -invI * r.x * r.y,
69 | -invI * r.x * r.y, invI * r.x * r.x)
70 |
71 | var K = K1 + K2
72 | K = Matrix22(K.a11 + gamma, K.a12,
73 | K.a21, K.a22 + gamma)
74 |
75 | mass = K.invert
76 |
77 | C = (b.sweep.c.x + r.x - target.x, b.sweep.c.y + r.y - target.y)
78 |
79 | // Cheat with some damping
80 | b.angularVelocity *= 0.98f
81 |
82 | // Warm starting.
83 | val P = force * step.dt
84 | b.linearVelocity += P * invMass
85 | b.angularVelocity += invI * (r.x * P.y - r.y * P.x)
86 | }
87 |
88 | def solveVelocityConstraints(step: TimeStep) {
89 | val b = body2
90 |
91 | val r = b.transform.rot * (localAnchor - b.localCenter)
92 |
93 | // Cdot = v + cross(w, r)
94 | val Cdot = b.linearVelocity + b.angularVelocity × r
95 |
96 | //Vec2 force = -step.inv_dt * Mat22.mul(m_mass, Cdot + (m_beta * step.inv_dt) * m_C + m_gamma * step.dt * m_force);
97 | var f = Vector2(Cdot.x + (beta*step.invDt)*C.x + gamma * step.dt * force.x,
98 | Cdot.y + (beta*step.invDt)*C.y + gamma * step.dt * force.y)
99 | f = mass * f * (-step.invDt)
100 |
101 | val oldForce = force
102 | force += f
103 | val forceMagnitude = force.length
104 | if (forceMagnitude > maxForce) {
105 | force *= (maxForce / forceMagnitude)
106 | }
107 | f = (force.x - oldForce.x, force.y - oldForce.y)
108 |
109 | val P = f * step.dt
110 | b.linearVelocity += P * b.invMass
111 | b.angularVelocity += b.invI * r × P
112 | }
113 |
114 | def solvePositionConstraints() = true
115 | }
116 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/TOI.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import dynamics.Sweep
4 | import vecmath._
5 | import vecmath.Preamble._
6 | import shapes._
7 |
8 | import Settings.ε
9 |
10 | case class TOIInput(
11 | sweep1: Sweep,
12 | sweep2: Sweep,
13 | sweepRadius1: Float,
14 | sweepRadius2: Float,
15 | tolerance: Float
16 | )
17 |
18 | /** Handles conservative advancement to compute time of impact between shapes. */
19 | object TOI {
20 | // This algorithm uses conservative advancement to compute the time of
21 | // impact (TOI) of two shapes.
22 | // Refs: Bullet, Young Kim
23 |
24 | /**
25 | * Compute the time when two shapes begin to touch or touch at a closer distance.
26 | *
Warning: the sweeps must have the same time interval.
27 | * @return the fraction between [0,1] in which the shapes first touch.
28 | * fraction=0 means the shapes begin touching/overlapped, and fraction=1 means the shapes don't touch.
29 | */
30 | def timeOfImpact(input: TOIInput, shape1: Shape, shape2: Shape) = {
31 | val r1 = input.sweepRadius1
32 | val r2 = input.sweepRadius2
33 | val sweep1 = input.sweep1
34 | val sweep2 = input.sweep2
35 | val tolerance = input.tolerance
36 |
37 | assert(sweep1.t0 == sweep2.t0)
38 | assert(1.0f - sweep1.t0 > ε)
39 |
40 | val t0 = sweep1.t0
41 | val v1 = sweep1.c - sweep1.c0
42 | val v2 = sweep2.c - sweep2.c0
43 | val ω1 = sweep1.a - sweep1.a0
44 | val ω2 = sweep2.a - sweep2.a0
45 |
46 | var α = 0f
47 |
48 | val k_maxIterations = 20 // TODO_ERIN b2Settings
49 | var iter = 0
50 | var targetDistance = 0f
51 | // use loop = false instead of breaks as 'break' isn't available in Scala
52 | var loop = true
53 | while(loop) {
54 | val t = (1f - α) * t0 + α
55 | val xf1 = sweep1.getTransform(t)
56 | val xf2 = sweep2.getTransform(t)
57 |
58 | //val distInput = DistanceInput(xf1, xf2, false)
59 |
60 | // Get the distance between shapes.
61 | val (distance,p1,p2) = Distance.distance(shape1, xf1, shape2, xf2)
62 | //System.out.println(distance);
63 |
64 | if (iter == 0) {
65 | // Compute a reasonable target distance to give some breathing room
66 | // for conservative advancement.
67 | if (distance > 2.0f * Settings.toiSlop) {
68 | targetDistance = 1.5f * Settings.toiSlop;
69 | } else {
70 | targetDistance = max(0.05f * Settings.toiSlop, distance - 0.5f * Settings.toiSlop);
71 | }
72 | }
73 |
74 | if (distance - targetDistance < 0.05f * Settings.toiSlop || iter == k_maxIterations) {
75 | //if (distance-targetDistance < 0) System.out.println("dist error: "+ (distance-targetDistance) + " toiSlop: "+Settings.toiSlop + " iter: "+iter);
76 | loop = false
77 | } else {
78 | val normal = (p2 - p1).normalize
79 |
80 | // Compute upper bound on remaining movement.
81 | val approachVelocityBound = (normal ∙ (v1 - v2)) + ω1.abs * r1 + ω2.abs * r2
82 | if (approachVelocityBound.abs < ε) {
83 | α = 1.0f
84 | loop = false
85 | } else {
86 | // Get the conservative time increment. Don't advance all the way.
87 | val dAlpha = (distance - targetDistance) / approachVelocityBound
88 | //float32 dt = (distance - 0.5f * b2_linearSlop) / approachVelocityBound;
89 | val newAlpha = α + dAlpha
90 |
91 | // The shapes may be moving apart or a safe distance apart.
92 | if (newAlpha < 0.0f || 1.0f < newAlpha) {
93 | α = 1.0f
94 | loop = false
95 | } else {
96 |
97 | // Ensure significant advancement.
98 | if (newAlpha < (1.0f + 100.0f * ε) * α) {
99 | loop = false
100 | } else {
101 | α = newAlpha
102 | iter += 1
103 | }
104 | }
105 | }
106 | }
107 | }
108 |
109 | α
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/slick/SlickDisplayWorld.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.slick
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import box2d.draw._
6 | import box2d.shapes._
7 | import box2d.collision._
8 | import box2d.dynamics._
9 | import box2d.dynamics.joints._
10 | import box2d.dynamics.contacts.ContactListener
11 | import org.newdawn.slick.AppGameContainer;
12 | import org.newdawn.slick.BasicGame;
13 | import org.newdawn.slick.GameContainer;
14 | import org.newdawn.slick.Graphics;
15 | import org.newdawn.slick.SlickException;
16 | import org.newdawn.slick.tests.AntiAliasTest;
17 | import box2d.testbed._
18 |
19 | object SlickDisplayWorld {
20 | import DrawFlags._
21 | def runWithSimulation(world: World, flipY: Boolean, flags: Int): Unit =
22 | run(world, flipY, true, flags)
23 |
24 | def run(world: World): Unit = run(world, true, false, Shapes)
25 |
26 | def run(world: World, flipY: Boolean, runSimulation: Boolean, flags: Int) {
27 | val app = new SlickDisplayWorld(world)
28 | app.simulate = runSimulation
29 | app.dd.flags = flags
30 | val container = new AppGameContainer(app)
31 | app.m_debugDraw.yFlip = if (flipY) -1 else 1
32 | container.setTargetFrameRate(60)
33 | container.setDisplayMode(800,800,false)
34 | container.start()
35 | }
36 | }
37 |
38 | class SlickDisplayWorld(world: World) extends BasicGame("Slick ScalaBox2D: World") {
39 | var simulate = false
40 | val m_debugDraw = new SlickDebugDraw(null,null)
41 | val dd = new DebugDraw(m_debugDraw)
42 | def width = gameContainer.getWidth
43 | def height = gameContainer.getHeight
44 | def mouseX = mousePos.x
45 | def mouseY = mousePos.y
46 | def pmouseX = mousePosOld.x
47 | def pmouseY = mousePosOld.y
48 | var gameContainer: GameContainer = null
49 |
50 | def init(container: GameContainer) {
51 | m_debugDraw.container = container;
52 | gameContainer = container
53 | m_debugDraw.transX = world.aabb.center.x
54 | m_debugDraw.transY = world.aabb.center.y
55 | }
56 |
57 | def update(container: GameContainer, delta: Int) {
58 | if (simulate) {
59 | var timeStep = 1.0f / 60
60 | world.step(timeStep, 10)
61 | }
62 | }
63 |
64 | def render(container: GameContainer, g: Graphics) {
65 | m_debugDraw.g = g
66 | handleCanvasDrag
67 | dd.drawDebugData(world)
68 | }
69 |
70 | var mouseButton = 0
71 | var mousePressed = false
72 | var mousePos = Vector2.Zero
73 | var mousePosOld = Vector2.Zero
74 | /**
75 | * Handle mouseDown events.
76 | * @param p The screen location that the mouse is down at.
77 | */
78 | override def mousePressed(b: Int, x: Int, y: Int) {
79 | mouseButton = b
80 | mousePressed = true
81 | mousePosOld = mousePos
82 | mousePos = (x,y)
83 | }
84 |
85 | /**
86 | * Handle mouseUp events.
87 | */
88 | override def mouseReleased(b: Int, x: Int, y: Int) {
89 | mousePosOld = mousePos
90 | mousePos = (x,y)
91 | mousePressed = false
92 | }
93 |
94 | /**
95 | * Handle mouseMove events (TestbedMain also sends mouseDragged events here)
96 | * @param p The new mouse location (screen coordinates)
97 | */
98 | override def mouseMoved(oldX: Int, oldY: Int, x: Int, y: Int) {
99 | mousePosOld = mousePos
100 | mousePos = (x,y)
101 | }
102 |
103 | def handleCanvasDrag() {
104 | val d = m_debugDraw
105 | //Vec2 mouseWorld = d.screenToWorld(mouseX, mouseY);
106 | if (mouseButton == 1) {
107 | if (mousePressed) {
108 | d.transX += mouseX - pmouseX;
109 | d.transY -= mouseY - pmouseY;
110 | val v = d.screenToWorld(width*.5f,height*.5f);
111 | }
112 | }
113 |
114 | }
115 | override def mouseWheelMoved(amount: Int) {
116 | val d = m_debugDraw
117 | val notches = amount
118 | val oldCenter = d.screenToWorld(width / 2.0f, height / 2.0f)
119 | //Change the zoom and clamp it to reasonable values
120 | if (notches < 0) {
121 | d.scaleFactor = Math.min(300f, d.scaleFactor * 1.05f);
122 | }
123 | else if (notches > 0) {
124 | d.scaleFactor = Math.max(.02f, d.scaleFactor / 1.05f);
125 | }
126 | val newCenter = d.screenToWorld(width / 2.0f, height / 2.0f);
127 | d.transX -= (oldCenter.x - newCenter.x) * d.scaleFactor;
128 | d.transY -= (oldCenter.y - newCenter.y) * d.scaleFactor;
129 | }
130 |
131 | }
132 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/Settings.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d
2 |
3 | import vecmath.Preamble._
4 |
5 | /** Global tuning constants based on MKS units and various integer maximums (vertices per shape, pairs, etc.). */
6 | object Settings {
7 |
8 | /** A "close to zero" float epsilon value for use */
9 | val ε = 1.1920928955078125E-7f
10 | val Epsilon = ε
11 |
12 | // Define your unit system here. The default system is
13 | // meters-kilograms-seconds. For the tuning to work well,
14 | // your dynamic objects should be bigger than a pebble and smaller
15 | // than a house.
16 | //
17 | // Use of these settings has been deprecated - they do not even
18 | // exist anymore in the C++ version of the engine, and future support
19 | // is unlikely.
20 | val lengthUnitsPerMeter = 1.0f
21 | val massUnitsPerKilogram = 1.0f
22 | val timeUnitsPerSecond = 1.0f
23 |
24 | // Collision
25 |
26 | val maxManifoldPoints = 2
27 | val maxShapesPerBody = 64
28 | val maxPolygonVertices = 8
29 |
30 | // ERKKI XXX do these still have to be power of two?
31 | /** Must be a power of two. */
32 | val maxProxies = 2048
33 | /** Must be a power of two. */
34 | val maxPairs = 8 * maxProxies
35 |
36 | // Dynamics
37 |
38 | /**
39 | * A small length used as a collision and constraint tolerance. Usually it is
40 | * chosen to be numerically significant, but visually insignificant.
41 | */
42 | val linearSlop = 0.005f * lengthUnitsPerMeter; // 0.5 cm
43 |
44 | /**
45 | * A small angle used as a collision and constraint tolerance. Usually it is
46 | * chosen to be numerically significant, but visually insignificant.
47 | */
48 | val angularSlop = 2.0f / 180.0f * π // 2 degrees
49 |
50 | /**
51 | * The radius of the polygon/edge shape skin. This should not be modified.
52 | * Making this smaller means polygons will have and insufficient for
53 | * continuous collision. Making it larger may create artifacts for vertex
54 | * collision.
55 | */
56 | val polygonRadius = 2.0f * linearSlop
57 |
58 | /**
59 | * A velocity threshold for elastic collisions. Any collision with a relative linear
60 | * velocity below this threshold will be treated as inelastic.
61 | */
62 | val velocityThreshold = 1.0f * lengthUnitsPerMeter / timeUnitsPerSecond // 1 m/s
63 |
64 | /**
65 | * The maximum linear position correction used when solving constraints. This helps to
66 | * prevent overshoot.
67 | */
68 | val maxLinearCorrection = 0.2f * lengthUnitsPerMeter // 20 cm
69 |
70 | /**
71 | * The maximum angular position correction used when solving constraints. This helps to
72 | * prevent overshoot.
73 | */
74 | val maxAngularCorrection = 8.0f / 180.0f * π // 8 degrees
75 |
76 | /**
77 | * This scale factor controls how fast overlap is resolved. Ideally this would be 1 so
78 | * that overlap is removed in one time step. However using values close to 1 often lead
79 | * to overshoot.
80 | */
81 | val contactBaumgarte = 0.2f
82 |
83 | /** The time that a body must be still before it will go to sleep. */
84 | val timeToSleep = 0.5f * timeUnitsPerSecond // half a second
85 |
86 | /** A body cannot sleep if its linear velocity is above this tolerance. */
87 | val linearSleepTolerance = 0.01f * lengthUnitsPerMeter / timeUnitsPerSecond // 1 cm/s
88 |
89 | /** A body cannot sleep if its angular velocity is above this tolerance. */
90 | val angularSleepTolerance = 2.0f / 180.0f / timeUnitsPerSecond
91 |
92 | /**
93 | * Continuous collision detection (CCD) works with core, shrunken shapes. This is the
94 | * amount by which shapes are automatically shrunk to work with CCD. This must be
95 | * larger than b2_linearSlop.
96 | */
97 | val toiSlop = 8.0f * linearSlop
98 |
99 | /**
100 | * The maximum linear velocity of a body. This limit is very large and is used
101 | * to prevent numerical problems. You shouldn't need to adjust this.
102 | */
103 | val maxLinearVelocity = 200.0f
104 | val maxLinearVelocitySquared = maxLinearVelocity * maxLinearVelocity
105 |
106 | /**
107 | * The maximum angular velocity of a body. This limit is very large and is used
108 | * to prevent numerical problems. You shouldn't need to adjust this.
109 | */
110 | val maxAngularVelocity = 250.0f
111 | val maxAngularVelocitySquared = maxAngularVelocity * maxAngularVelocity
112 |
113 | /** Maximum number of contacts to be handled to solve a TOI island. */
114 | val maxTOIContactsPerIsland = 32
115 |
116 | var mixFriction = { (a: Float, b: Float) => sqrt(a * b) }
117 | var mixRestitution = { (a: Float, b: Float) => max(a, b) }
118 |
119 | var threadedIslandSolving = false
120 | var numThreads = Runtime.getRuntime.availableProcessors + 1
121 | }
122 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/contacts/Contact.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.contacts
2 |
3 | import collection.mutable
4 | import vecmath._
5 | import vecmath.Preamble._
6 | import shapes._
7 | import collision._
8 |
9 | object ContactFlags {
10 | val nonSolid = 0x0001
11 | val slow = 0x0002
12 | val island = 0x0004
13 | val toi = 0x0008
14 | }
15 |
16 | object Contact {
17 | def apply(f1: Fixture, f2: Fixture) = (f1.shape, f2.shape) match {
18 | case (s1: Circle, s2: Circle) =>
19 | new SingleManifoldContact(f1, f2, CircleCollider)
20 | case (s1: Polygon, s2: Circle) =>
21 | new MultiPointSingleManifoldContact(f1, f2, PolygonCircleCollider)
22 | case (s1: Circle, s2: Polygon) =>
23 | new MultiPointSingleManifoldContact(f2, f1, PolygonCircleCollider)
24 | case (s1: Polygon, s2: Polygon) =>
25 | new MultiPointSingleManifoldContact(f1, f2, PolygonCollider)
26 | case (s1: Edge, s2: Circle) =>
27 | new SingleManifoldContact(f1, f2, EdgeCircleCollider)
28 | case (s1: Circle, s2: Edge) =>
29 | new SingleManifoldContact(f2, f1, EdgeCircleCollider)
30 | case (s1: Polygon, s2: Edge) =>
31 | new MultiPointSingleManifoldContact(f1, f2, PolygonEdgeCollider)
32 | case (s1: Edge, s2: Polygon) =>
33 | new MultiPointSingleManifoldContact(f2, f1, PolygonEdgeCollider)
34 | case (s1, s2) =>
35 | // XXX ERKKI this was return null; and probably should be NullContact?
36 | throw new IllegalArgumentException("Contact(" + s1.getClass.getSimpleName +
37 | ", " + s2.getClass.getSimpleName + ") not supported!")
38 | }
39 |
40 | def destroy(contact: Contact) {
41 | if (contact.manifolds.length > 0) {
42 | contact.fixture1.body.wakeUp()
43 | contact.fixture2.body.wakeUp()
44 | }
45 | }
46 |
47 | }
48 |
49 | /**
50 | * Base class for contacts between shapes.
51 | * @author ewjordan
52 | */
53 | abstract class Contact(val fixture1: Fixture, val fixture2: Fixture) {
54 | /** Node for connecting bodies. */
55 | val node1 = if (fixture2 != null) ContactEdge(fixture2.body, this) else null
56 |
57 | /** Node for connecting bodies. */
58 | val node2 = if (fixture1 != null) ContactEdge(fixture1.body, this) else null
59 |
60 | // TODO ERKKI: mixing friction/restitution has been moved to the solver
61 | /** Combined friction */
62 | var friction =
63 | if (fixture1 == null || fixture2 == null) 0f
64 | else sqrt(fixture1.friction * fixture2.friction)
65 | // TODO Settings.mixFriction(fixture1.friction, fixture2.friction)
66 |
67 | /** Combined restitution */
68 | var restitution =
69 | if (fixture1 == null || fixture2 == null) 0f
70 | else max(fixture1.restitution, fixture2.restitution)
71 | // TODO Settings.mixRestitution(fixture1.restitution, fixture2.restitution)
72 |
73 | var flags = 0
74 | var toi = 0f
75 |
76 | def evaluate(listener: ContactListener)
77 |
78 | /**
79 | * Get the number of manifolds. This is 0 or 1 between convex shapes.
80 | * This may be greater than 1 for convex-vs-concave shapes. Each
81 | * manifold holds up to two contact points with a shared contact normal.
82 | *
83 | * Get the manifold array.
84 | */
85 | def manifolds: Seq[Manifold]
86 |
87 | def solid = (flags & ContactFlags.nonSolid) == 0
88 |
89 | init
90 |
91 | def init {
92 | // This is mainly so that NullContract could be created!
93 | if (fixture1 != null && fixture2 != null) {
94 | if (fixture1.isSensor || fixture2.isSensor) {
95 | flags |= ContactFlags.nonSolid
96 | }
97 | //world = s1.body.world
98 | fixture1.body.contactList = node1 :: fixture1.body.contactList
99 | fixture2.body.contactList = node2 :: fixture2.body.contactList
100 | }
101 | }
102 |
103 | def update(listener: ContactListener) {
104 | val oldCount = manifolds.length
105 | evaluate(listener)
106 | val newCount = manifolds.length
107 |
108 | val body1 = fixture1.body
109 | val body2 = fixture2.body
110 |
111 | if (newCount == 0 && oldCount > 0) {
112 | body1.wakeUp()
113 | body2.wakeUp()
114 | }
115 |
116 | // Slow contacts don't generate TOI events.
117 | if (body1.isStatic || body1.isBullet || body2.isStatic || body2.isBullet) {
118 | flags &= ~ContactFlags.slow
119 | } else {
120 | flags |= ContactFlags.slow
121 | }
122 | }
123 |
124 | // ERKKI impl from circlecontact
125 | def computeTOI(sweep1: Sweep, sweep2: Sweep) = {
126 | val toiInput = TOIInput(
127 | sweep1,
128 | sweep2,
129 | fixture1.computeSweepRadius(sweep1.localCenter),
130 | fixture2.computeSweepRadius(sweep2.localCenter),
131 | Settings.linearSlop
132 | )
133 | TOI.timeOfImpact(toiInput, fixture1.shape, fixture2.shape)
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/DistanceJoint.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath.Vector2
4 | import vecmath.Preamble._
5 | import Settings.ε
6 |
7 | //C = norm(p2 - p1) - L
8 | //u = (p2 - p1) / norm(p2 - p1)
9 | //Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1))
10 | //J = [-u -cross(r1, u) u cross(r2, u)]
11 | //K = J * invM * JT
12 | //= invMass1 + invI1 * cross(r1, u)^2 + invMass2 + invI2 * cross(r2, u)^2
13 |
14 | /**
15 | * A distance joint constrains two points on two bodies
16 | * to remain at a fixed distance from each other. You can view
17 | * this as a massless, rigid rod.
18 | */
19 | class DistanceJoint(defn: DistanceJointDef) extends Joint(defn) {
20 | val localAnchor1 = defn.localAnchor1
21 | val localAnchor2 = defn.localAnchor2
22 | val length = defn.length
23 | var impulse = 0.0f
24 | var u = Vector2.Zero
25 | // effective mass for the constraint.
26 | var mass = 0.0f
27 | val frequencyHz = defn.frequencyHz
28 | val dampingRatio = defn.dampingRatio
29 | var gamma = 0.0f
30 | var bias = 0.0f
31 |
32 | def anchor1 = body1.toWorldPoint(localAnchor1)
33 | def anchor2 = body2.toWorldPoint(localAnchor2)
34 | def reactionForce = (impulse * u.x, impulse * u.y)
35 | def reactionTorque = 0.0f
36 |
37 | def initVelocityConstraints(step: TimeStep) {
38 | invDt = step.invDt
39 |
40 | //TODO: fully inline temp Vec2 ops
41 | val b1 = body1
42 | val b2 = body2
43 |
44 | // Compute the effective mass matrix.
45 | val r1 = b1.transform.rot * (localAnchor1 - b1.localCenter)
46 | val r2 = b2.transform.rot * (localAnchor2 - b2.localCenter)
47 | u = (b2.sweep.c.x + r2.x - b1.sweep.c.x - r1.x,
48 | b2.sweep.c.y + r2.y - b1.sweep.c.y - r1.y)
49 |
50 | // Handle singularity.
51 | val len = u.length
52 | if (len > Settings.linearSlop) {
53 | u /= len
54 | } else {
55 | u = Vector2.Zero
56 | }
57 |
58 | val cr1u = r1 × u
59 | val cr2u = r2 × u
60 |
61 | val invMass = b1.invMass + b1.invI * cr1u * cr1u + b2.invMass + b2.invI * cr2u * cr2u
62 | assert(invMass > ε)
63 | mass = 1.0f / invMass
64 |
65 | if (frequencyHz > 0.0f) {
66 | val C = len - length
67 |
68 | // Frequency
69 | val ω = 2 * π * frequencyHz
70 |
71 | // Damping coefficient
72 | val d = 2.0f * mass * dampingRatio * ω
73 |
74 | // Spring stiffness
75 | val k = mass * ω * ω
76 |
77 | // magic formulas
78 | gamma = 1.0f / (step.dt * (d + step.dt * k))
79 | bias = C * step.dt * k * gamma
80 | mass = 1.0f / (invMass + gamma)
81 | }
82 |
83 | if (step.warmStarting) {
84 | impulse *= step.dtRatio
85 | val P = u * impulse
86 | b1.linearVelocity -= P * b1.invMass
87 | b1.angularVelocity -= b1.invI * r1 × P
88 | b2.linearVelocity += P * b2.invMass
89 | b2.angularVelocity += b2.invI * r2 × P
90 | } else {
91 | impulse = 0.0f
92 | }
93 | }
94 |
95 | def solvePositionConstraints(): Boolean = {
96 | if (frequencyHz > 0.0f) {
97 | return true
98 | }
99 |
100 | val b1 = body1
101 | val b2 = body2
102 |
103 | val r1 = b1.transform.rot * (localAnchor1 - b1.localCenter)
104 | val r2 = b2.transform.rot * (localAnchor2 - b2.localCenter)
105 |
106 | var d = Vector2(b2.sweep.c.x + r2.x - b1.sweep.c.x - r1.x,
107 | b2.sweep.c.y + r2.y - b1.sweep.c.y - r1.y)
108 |
109 | val len = d.length
110 | d = d.normalize
111 | val C = (len - length).clamp(-Settings.maxLinearCorrection, Settings.maxLinearCorrection)
112 |
113 | val imp = -mass * C
114 | u = d
115 | val P = imp * u
116 |
117 | b1.sweep.c -= P * b1.invMass
118 | b1.sweep.a -= b1.invI * (r1.x*P.y-r1.y*P.x)//(r1 × P);
119 | b2.sweep.c += P * b2.invMass
120 | b2.sweep.a += b2.invI * (r2.x*P.y-r2.y*P.x)//(r2 × P);
121 |
122 | b1.synchronizeTransform()
123 | b2.synchronizeTransform()
124 |
125 | return C.abs < Settings.linearSlop
126 | }
127 |
128 | def solveVelocityConstraints(step: TimeStep) {
129 | val b1 = body1
130 | val b2 = body2
131 |
132 | val r1 = b1.transform.rot * (localAnchor1 - b1.localCenter)
133 | val r2 = b2.transform.rot * (localAnchor2 - b2.localCenter)
134 |
135 | // Cdot = dot(u, v + cross(w, r))
136 | val v1 = b1.linearVelocity + b1.angularVelocity × r1
137 | val v2 = b2.linearVelocity + b2.angularVelocity × r2
138 | val Cdot = u ∙ (v2 - v1)
139 |
140 | val imp = -mass * (Cdot + bias + gamma * impulse)
141 | impulse += imp
142 |
143 | val P = u * imp
144 | b1.linearVelocity -= P * b1.invMass
145 | b1.angularVelocity -= b1.invI * (r1.x*P.y - r1.y*P.x)//(r1 × P);
146 | b2.linearVelocity += P * b2.invMass
147 | b2.angularVelocity += b2.invI * (r2.x*P.y - r2.y*P.x)//(r2 × P);
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/scalabox2d-testbed/src/main/scala/org/villane/box2d/testbed/slick/SlickDebugDraw.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.testbed.slick
2 |
3 | import vecmath._
4 | import box2d.draw._
5 | import org.newdawn.slick.Color;
6 | import org.newdawn.slick.GameContainer;
7 | import org.newdawn.slick.Graphics;
8 | import org.newdawn.slick.geom.Circle;
9 | import org.newdawn.slick.geom.Polygon;
10 | import org.newdawn.slick.opengl.TextureImpl;
11 |
12 | /**
13 | * Not fully implemented - just enough here to get the Pyramid
14 | * demo to draw.
15 | *
16 | */
17 | class SlickDebugDraw(var g: Graphics, var container: GameContainer) extends DebugDrawHandler {
18 |
19 | def hw = container.getWidth / 2
20 | def hh = container.getHeight / 2
21 | // World 0,0 maps to transX, transY on screen
22 | var transX = 0f
23 | var transY = 0f
24 | var scaleFactor = 10.0f
25 | var yFlip = -1.0f
26 |
27 | implicit def box2dColorToSlickColor(c: Color3f) =
28 | new Color(c.r.toInt, c.g.toInt, c.b.toInt, c.a.toInt)
29 |
30 | override def setCamera(x: Float, y: Float, scale: Float) {
31 | transX = hw + x * scale - 50 // -50 for control panel
32 | transY = hh + yFlip * y * scale
33 | scaleFactor = scale
34 | }
35 |
36 | def map(mapMe: Float, fromLow: Float, fromHigh: Float, toLow: Float, toHigh: Float) = {
37 | val interp = (mapMe - fromLow) / (fromHigh - fromLow)
38 | (interp*toHigh + (1.0f-interp)*toLow)
39 | }
40 |
41 | override def worldToScreen(world: Vector2) = {
42 | val x = map(world.x, 0f, 1f, transX, transX+scaleFactor)
43 | var y = map(world.y, 0f, 1f, transY, transY+scaleFactor)
44 | if (yFlip == -1.0f) y = map(y, 0f, container.getHeight, container.getHeight, 0f)
45 | Vector2(x, y)
46 | }
47 |
48 | override def screenToWorld(screen: Vector2) = {
49 | val x = map(screen.x, transX, transX+scaleFactor, 0f, 1f)
50 | var y = screen.y
51 | if (yFlip == -1.0f) y = map(y, container.getHeight, 0f, 0f, container.getHeight)
52 | y = map(y, transY, transY + scaleFactor, 0f, 1f)
53 | Vector2(x, y)
54 | }
55 |
56 | def drawCircle(center: Vector2, radius: Float, color: Color3f) {
57 | val c = worldToScreen(center)
58 | val circle = new Circle(c.x, c.y, radius * scaleFactor)
59 | g.setColor(color)
60 | g.draw(circle)
61 | }
62 |
63 | def drawPoint(position: Vector2, f: Float, color: Color3f) {
64 | g.setColor(color)
65 | val c = worldToScreen(position)
66 | val radius = 3
67 | // x1, y1 are upper left corner
68 | val x1 = c.x - radius
69 | val y1 = c.y - radius
70 | g.fillOval(x1, y1, 2 * radius, 2 * radius)
71 | }
72 |
73 | def drawPolygon(vertices: Array[Vector2], color: Color3f) {
74 | val polygon = new Polygon()
75 | for (v <- vertices) {
76 | val screenPt = worldToScreen(v)
77 | polygon.addPoint(screenPt.x, screenPt.y)
78 | }
79 | g.setColor(color)
80 | g.draw(polygon)
81 | }
82 |
83 | def drawSolidPolygon(vertices: Array[Vector2], color: Color3f) {
84 | val polygon = new Polygon()
85 | for (v <- vertices) {
86 | val screenPt = worldToScreen(v)
87 | polygon.addPoint(screenPt.x, screenPt.y)
88 | }
89 | val slickColor = box2dColorToSlickColor(color)
90 | g.setColor(slickColor)
91 | g.draw(polygon)
92 | slickColor.a *= 0.5f
93 | g.setColor(slickColor)
94 | g.fill(polygon)
95 | }
96 |
97 | def drawSegment(p1: Vector2, p2: Vector2, color: Color3f) {
98 | g.setColor(color)
99 | TextureImpl.bindNone()
100 | g.setLineWidth(1)
101 | g.setAntiAlias(false)
102 | val screen1 = worldToScreen(p1)
103 | val screen2 = worldToScreen(p2)
104 | g.drawLine(screen1.x,screen1.y,screen2.x,screen2.y)
105 | }
106 |
107 | def drawSolidCircle(center: Vector2, radius: Float, axis: Vector2, color: Color3f) {
108 | val c = worldToScreen(center)
109 | val circle = new Circle(c.x, c.y, radius * scaleFactor)
110 | val slickColor = box2dColorToSlickColor(color)
111 | g.setColor(slickColor)
112 | g.draw(circle)
113 | val screenRadius = scaleFactor * radius
114 | g.drawLine(c.x, c.y, c.x + screenRadius * axis.x, c.y + yFlip * screenRadius * axis.y)
115 | slickColor.a *= 0.5f
116 | g.setColor(slickColor)
117 | g.fill(circle)
118 | }
119 |
120 | def drawString(x: Float, y: Float, s: String, color: Color3f) {
121 | g.setColor(color)
122 | g.drawString(s, x, y)
123 | }
124 |
125 | def drawTransform(xf: Transform2) {
126 | val r = 3
127 | val p1 = xf.pos
128 | val k_axisScale = 0.4f
129 | val p1world = worldToScreen(p1)
130 | val p2 = p1 + (xf.rot.col1 * k_axisScale)
131 | val p2world = worldToScreen(p2)
132 | val p3 = p1 + (xf.rot.col2 * k_axisScale)
133 | val p3world = worldToScreen(p3)
134 | g.setColor(new Color(1f, 0f, 0f))
135 | g.drawLine(p1world.x, p1world.y, p2world.x, p2world.y)
136 | g.setColor(new Color(0f, 1f, 0f))
137 | g.drawLine(p1world.x, p1world.y, p3world.x, p3world.y)
138 | }
139 |
140 | }
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dsl/DSL.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dsl
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes._
6 | import collision._
7 | import dynamics._
8 | import dynamics.joints._
9 | import collection.mutable.ListBuffer
10 |
11 | object DSL {
12 |
13 | implicit def fixtureBuilder2FixtureDef(b: FixtureBuilder) = b.define
14 |
15 | object BodyCtx extends ThreadLocal[BodyCtx]
16 | class BodyCtx {
17 | var massFromShapes = false
18 | val bodyBuilder = new BodyBuilder(new BodyDef)
19 | val fixBuilders = new ListBuffer[FixtureBuilder]
20 | }
21 |
22 | def body[T](block: => T)(implicit world: World) = {
23 | val ctx = new BodyCtx
24 | BodyCtx.set(ctx)
25 | block
26 | val body = world.createBody(ctx.bodyBuilder.define)
27 | for (builder <- ctx.fixBuilders) {
28 | builder.define match {
29 | case fixDef @ FixtureDef(ec: EdgeChainDef) =>
30 | for (edge <- ec.edges) {
31 | fixDef.shapeDef = edge
32 | body.createFixture(fixDef)
33 | }
34 | fixDef.shapeDef = ec
35 | case fixDef => body.createFixture(fixDef)
36 | }
37 | }
38 | if (ctx.massFromShapes) body.computeMassFromShapes
39 | BodyCtx.remove
40 | body
41 | }
42 |
43 | def fixture(fd: FixtureDef) = {
44 | val builder = new FixtureBuilder(fd)
45 | if (BodyCtx.get != null) BodyCtx.get.fixBuilders += builder
46 | builder
47 | }
48 |
49 | def fixture(fb: FixtureBuilder) = {
50 | if (BodyCtx.get != null) BodyCtx.get.fixBuilders += fb
51 | fb
52 | }
53 |
54 | private def fbuilder(sd: ShapeDef) = {
55 | val builder = new FixtureBuilder(FixtureDef(sd))
56 | if (BodyCtx.get != null) BodyCtx.get.fixBuilders += builder
57 | builder
58 | }
59 |
60 | def shape(shapeDef: ShapeDef) = fbuilder(shapeDef)
61 | def circle(radius: Float) = fbuilder(CircleDef(Vector2.Zero, radius))
62 | def circle(pos: Vector2, radius: Float) = fbuilder(CircleDef(pos, radius))
63 | def polygon(vertices: Vector2*) = fbuilder(PolygonDef(vertices.toArray))
64 | def box(halfW: Float, halfH: Float) = fbuilder(PolygonDef.box(halfW, halfH))
65 | def box(halfW: Float, halfH: Float, center: Vector2) =
66 | fbuilder(PolygonDef.box(halfW, halfH, center))
67 | def box(halfW: Float, halfH: Float, center: Vector2, angle: Float) =
68 | fbuilder(PolygonDef.box(halfW, halfH, center, angle))
69 | def edge(vertices: Vector2*) = fbuilder(EdgeChainDef(vertices:_*))
70 | def loop(vertices: Vector2*) = fbuilder(EdgeChainDef.loop(vertices:_*))
71 |
72 | private def bbuilder = BodyCtx.get.bodyBuilder
73 |
74 | def userData = bbuilder.userData _
75 | def mass = bbuilder.mass _
76 | def pos = bbuilder.pos _
77 | def angle = bbuilder.angle _
78 | def linearDamping = bbuilder.linearDamping _
79 | def angularDamping = bbuilder.angularDamping _
80 | def sleepingAllowed = bbuilder.sleepingAllowed _
81 | def initiallySleeping = bbuilder.initiallySleeping _
82 | def fixedRotation = bbuilder.fixedRotation _
83 | def bullet = bbuilder.bullet _
84 |
85 | def massFromShapes = BodyCtx.get.massFromShapes = true
86 |
87 | def joint[JB <: JointBuilder](jBuilder: => JB)(implicit world: World) = {
88 | val jDef = jBuilder.define
89 | world.createJoint(jDef)
90 | }
91 |
92 | def revolute(bodies: (Body, Body)) = new RevoluteJointBuilder(
93 | new RevoluteJointDef, bodies)
94 | def prismatic(bodies: (Body, Body)) = new PrismaticJointBuilder(
95 | new PrismaticJointDef, bodies)
96 | def distance(bodies: (Body, Body)) = new DistanceJointBuilder(
97 | new DistanceJointDef, bodies)
98 | def gear(joints: (Joint, Joint)) = new GearJointBuilder(
99 | new GearJointDef, joints)
100 | }
101 |
102 | class BodyBuilder(b: BodyDef) {
103 | def userData(userData: AnyRef) = { b.userData = userData; this }
104 | def mass(mass: Mass) = { b.mass = mass; this }
105 | def pos(pos: Vector2) = { b.pos = pos; this }
106 | def angle(angle: Float) = { b.angle = angle; this }
107 | def linearDamping(linearDamping: Float) = { b.linearDamping = linearDamping; this }
108 | def angularDamping(angularDamping: Float) = { b.angularDamping = angularDamping; this }
109 | def sleepingAllowed(allowSleep: Boolean) = { b.allowSleep = allowSleep; this }
110 | def initiallySleeping(isSleeping: Boolean) = { b.isSleeping = isSleeping; this }
111 | def fixedRotation(fixedRotation: Boolean) = { b.fixedRotation = fixedRotation; this }
112 | def bullet(isBullet: Boolean) = { b.isBullet = isBullet; this }
113 | def define = b
114 | }
115 |
116 | class FixtureBuilder(s: FixtureDef) {
117 | def userData(userData: AnyRef) = { s.userData = userData; this }
118 | def material(material: Material) = { s.apply(material); this }
119 | def friction(friction: Float) = { s.friction = friction; this }
120 | def restitution(restitution: Float) = { s.restitution = restitution; this }
121 | def density(density: Float) = { s.density = density; this }
122 | def filter(filter: FilterData) = { s.filter = filter; this }
123 | def sensor(isSensor: Boolean) = { s.isSensor = isSensor; this }
124 | def define = s
125 | }
126 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/joints/GearJoint.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics.joints
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 |
6 | /**
7 | * A gear joint is used to connect two joints together. Either joint
8 | * can be a revolute or prismatic joint. You specify a gear ratio
9 | * to bind the motions together:
10 | * coordinate1 + ratio * coordinate2 = constant
11 | * The ratio can be negative or positive. If one joint is a revolute joint
12 | * and the other joint is a prismatic joint, then the ratio will have units
13 | * of length or units of 1/length.
14 | * Warning: The revolute and prismatic joints must be attached to
15 | * fixed bodies (which must be body1 on those joints).
16 | */
17 | class GearJoint(defn: GearJointDef) extends Joint(defn) {
18 | // Gear Joint:
19 | // C0 = (coordinate1 + ratio * coordinate2)_initial
20 | // C = C0 - (cordinate1 + ratio * coordinate2) = 0
21 | // Cdot = -(Cdot1 + ratio * Cdot2)
22 | // J = -[J1 ratio * J2]
23 | // K = J * invM * JT
24 | // = J1 * invM1 * J1T + ratio * ratio * J2 * invM2 * J2T
25 | //
26 | // Revolute:
27 | // coordinate = rotation
28 | // Cdot = angularVelocity
29 | // J = [0 0 1]
30 | // K = J * invM * JT = invI
31 | //
32 | // Prismatic:
33 | // coordinate = dot(p - pg, ug)
34 | // Cdot = dot(v + cross(w, r), ug)
35 | // J = [ug cross(r, ug)]
36 | // K = J * invM * JT = invMass + invI * cross(r, ug)^2
37 |
38 | val joint1 = defn.joint1
39 | val joint2 = defn.joint2
40 | assert(joint1.isInstanceOf[RevoluteJoint] || joint2.isInstanceOf[PrismaticJoint])
41 | assert(joint2.isInstanceOf[RevoluteJoint] || joint2.isInstanceOf[PrismaticJoint])
42 | assert(joint1.body1.isStatic)
43 | assert(joint2.body1.isStatic)
44 |
45 | val ground1 = joint1.body1
46 | val ground2 = joint2.body1
47 | val groundAnchor1 = joint1.localAnchor1
48 | val localAnchor1 = joint1.localAnchor2
49 | val groundAnchor2 = joint2.localAnchor1
50 | val localAnchor2 = joint2.localAnchor2
51 |
52 | def getCoordinate(joint: Joint) = joint match {
53 | case pj: PrismaticJoint => pj.jointTranslation
54 | case rj: RevoluteJoint => rj.jointAngle
55 | }
56 | val coordinate1 = getCoordinate(joint1)
57 | val coordinate2 = getCoordinate(joint2)
58 |
59 | val ratio = defn.ratio
60 | val constant = coordinate1 + ratio * coordinate2
61 |
62 | var J = Jacobian.Zero
63 |
64 | /** Effective mass */
65 | var mass = 0f
66 |
67 | /** Force for accumulation/warm starting. */
68 | var force = 0f
69 |
70 | def anchor1 = body1.toWorldPoint(localAnchor1)
71 | def anchor2 = body2.toWorldPoint(localAnchor2)
72 | // TODO_ERIN not tested
73 | def reactionForce = Vector2(force * J.linear2.x, force * J.linear2.y)
74 | def reactionTorque = {
75 | // TODO_ERIN not tested
76 | val r = body2.transform.rot * (localAnchor2 - body2.localCenter)
77 | val F = reactionForce
78 | force * J.angular2 - (r cross F)
79 | }
80 |
81 | def initVelocityConstraints(step: TimeStep) = {
82 | val g1 = ground1
83 | val g2 = ground2
84 | val b1 = body1
85 | val b2 = body2
86 |
87 | var K = 0.0f
88 | var ug = Vector2.Zero
89 | var r = Vector2.Zero
90 | var ang1 = 0f
91 | var lin1 = Vector2.Zero
92 | var ang2 = 0f
93 | var lin2 = Vector2.Zero
94 |
95 | joint1 match {
96 | case rj: RevoluteJoint =>
97 | ang1 = -1
98 | K += b1.invI
99 | case pj: PrismaticJoint =>
100 | ug = g1.transform.rot * pj.localXAxis1
101 | r = b1.transform.rot * (localAnchor1 - b1.localCenter)
102 | val crug = r cross ug
103 | lin1 = -ug
104 | ang1 = -crug
105 | K += b1.invMass + b1.invI * crug * crug
106 | }
107 |
108 | joint2 match {
109 | case rj: RevoluteJoint =>
110 | ang2 = -ratio
111 | K += ratio * ratio * b2.invI
112 | case pj: PrismaticJoint =>
113 | ug = g2.transform.rot * pj.localXAxis1
114 | r = b2.transform.rot * (localAnchor2 - b2.localCenter)
115 | val crug = r cross ug
116 | ug *= -ratio
117 | lin2 = ug
118 | ang2 = -ratio * crug
119 | K += ratio * ratio * (b2.invMass + b2.invI * crug * crug)
120 | }
121 |
122 | J = Jacobian(lin1, ang1, lin2, ang2)
123 |
124 | // Compute effective mass.
125 | assert (K > 0.0f)
126 | mass = 1.0f / K
127 |
128 | if (step.warmStarting) {
129 | // Warm starting.
130 | updateVelocities(step.dt * force)
131 | } else {
132 | force = 0.0f
133 | }
134 | }
135 |
136 | private def updateVelocities(P: Float) {
137 | body1.linearVelocity += body1.invMass * P * J.linear1
138 | body1.angularVelocity += body1.invI * P * J.angular1
139 | body2.linearVelocity += body2.invMass * P * J.linear2
140 | body2.angularVelocity += body2.invI * P * J.angular2
141 | }
142 |
143 | def solveVelocityConstraints(step: TimeStep) = {
144 | val b1 = body1
145 | val b2 = body2
146 |
147 | val Cdot = J.compute(b1.linearVelocity, b1.angularVelocity,
148 | b2.linearVelocity, b2.angularVelocity)
149 |
150 | var forceL = -step.invDt * mass * Cdot
151 | force += forceL
152 | updateVelocities(step.dt * forceL)
153 | }
154 |
155 | def solvePositionConstraints: Boolean = {
156 | var linearError = 0.0f
157 |
158 | val b1 = body1
159 | val b2 = body2
160 |
161 | val coord1 = getCoordinate(joint1)
162 | val coord2 = getCoordinate(joint2)
163 |
164 | val C = constant - (coord1 + ratio * coord2)
165 | val impulse = -mass * C
166 |
167 | b1.sweep.c += b1.invMass * impulse * J.linear1
168 | b1.sweep.a += b1.invI * impulse * J.angular1
169 | b2.sweep.c += b2.invMass * impulse * J.linear2
170 | b2.sweep.a += b2.invI * impulse * J.angular2
171 |
172 | b1.synchronizeTransform
173 | b2.synchronizeTransform
174 |
175 | linearError < Settings.linearSlop
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/draw/DebugDraw.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.draw
2 |
3 | import collision._
4 | import vecmath._
5 | import vecmath.Preamble._
6 | import shapes._
7 | import dynamics._
8 | import dynamics.joints._
9 | import broadphase._
10 |
11 | object Colors {
12 | val Static = Color3f.ratio(0.5f, 0.9f, 0.5f)
13 | val Sensor = Color3f.ratio(1f, 1f, 0.4f, 0.3f)
14 | val Sleeping = Color3f.ratio(0.5f, 0.5f, 0.9f)
15 | val Dynamic = Color3f.ratio(0.9f, 0.9f, 0.9f)
16 | val Joint = Color3f.ratio(0.5f, 0.8f, 0.8f)
17 | val Core = Color3f.ratio(0.9f, 0.6f, 0.6f)
18 | val Pair = Color3f.ratio(0.9f, 0.9f, 0.3f)
19 | val AABB = Color3f(255, 255, 255)
20 | val WorldAABB = Color3f.ratio(0.3f, 0.9f, 0.9f)
21 | }
22 |
23 | object DrawFlags {
24 | /** draw shapes */
25 | val Shapes = 0x0001
26 | /** draw joint connections */
27 | val Joints = 0x0002
28 | /** @deprecated draw core (TOI) shapes */
29 | val CoreShapes = 0x0004
30 | /** draw axis aligned bounding boxes */
31 | val AABBs = 0x0008
32 | /** draw broad-phase pairs */
33 | val Pairs = 0x0020
34 | /** draw center of mass frame */
35 | val CenterOfMass = 0x0040
36 | /** draw sensor shapes */
37 | val Sensors = 0x0080
38 | }
39 |
40 | /**
41 | * Draws the representation of the world, given a concrete DebugDrawHandler
42 | */
43 | final class DebugDraw(val handler: DebugDrawHandler) {
44 | import DrawFlags._
45 | var flags = 0
46 |
47 | def appendFlags(flags: Int) = this.flags |= flags
48 | def clearFlags(flags: Int) = this.flags &= ~flags
49 | def flag(flag: Int) = (this.flags & flag) == flag
50 |
51 | def drawDebugData(world: World) {
52 | drawDebugData(world.bodyList, world.jointList, world.broadPhase)
53 | }
54 |
55 | /** For internal use */
56 | private def drawDebugData(bodies: Seq[Body],
57 | joints: Seq[Joint],
58 | bp: BroadPhase) {
59 | if (flag(Shapes)) bodies foreach drawBody
60 | if (flag(Joints)) joints foreach drawJoint
61 |
62 | if (flag(Pairs)) {
63 | val invQ = 1f / bp.quantizationFactor
64 |
65 | // ERKKI TODO this was done differently with the custom hash table
66 | for (((proxyId1,proxyId2),pair) <- bp.pairManager.hashTable) {
67 | val p1 = bp.proxyPool(proxyId1)
68 | val p2 = bp.proxyPool(proxyId2)
69 |
70 | val b1 = AABB(
71 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p1.lowerBounds(0)).value,
72 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p1.lowerBounds(1)).value),
73 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p1.upperBounds(0)).value,
74 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p1.upperBounds(1)).value))
75 |
76 | val b2 = AABB(
77 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p2.lowerBounds(0)).value,
78 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p2.lowerBounds(1)).value),
79 | (bp.worldAABB.lowerBound.x + invQ.x * bp.m_bounds(0)(p2.upperBounds(0)).value,
80 | bp.worldAABB.lowerBound.y + invQ.y * bp.m_bounds(1)(p2.upperBounds(1)).value))
81 |
82 | val x1 = 0.5f * (b1.lowerBound + b1.upperBound)
83 | val x2 = 0.5f * (b2.lowerBound + b2.upperBound)
84 |
85 | handler.drawSegment(x1, x2, Colors.Pair)
86 | }
87 | }
88 |
89 | val worldLower = bp.worldAABB.lowerBound
90 | val worldUpper = bp.worldAABB.upperBound
91 |
92 | if (flag(AABBs)) {
93 | val invQ = 1f / bp.quantizationFactor
94 | for (i <- 0 until Settings.maxProxies) {
95 | val p = bp.proxyPool(i)
96 | if (p.isValid) {
97 | val b = AABB(
98 | (worldLower.x + invQ.x * bp.m_bounds(0)(p.lowerBounds(0)).value,
99 | worldLower.y + invQ.y * bp.m_bounds(1)(p.lowerBounds(1)).value),
100 | (worldLower.x + invQ.x * bp.m_bounds(0)(p.upperBounds(0)).value,
101 | worldLower.y + invQ.y * bp.m_bounds(1)(p.upperBounds(1)).value))
102 |
103 | val vs: Array[Vector2] = Array(
104 | b.lowerBound,
105 | (b.upperBound.x, b.lowerBound.y),
106 | b.upperBound,
107 | (b.lowerBound.x, b.upperBound.y)
108 | )
109 | handler.drawPolygon(vs, Colors.AABB)
110 | }
111 | }
112 | }
113 |
114 | val vsw: Array[Vector2] = Array(
115 | worldLower,
116 | (worldUpper.x, worldLower.y),
117 | worldUpper,
118 | (worldLower.x, worldUpper.y)
119 | )
120 | handler.drawPolygon(vsw, Colors.WorldAABB)
121 |
122 | if (flag(CenterOfMass)) {
123 | for (b <- bodies) {
124 | val xf = Transform2(b.worldCenter, b.transform.rot)
125 | handler.drawTransform(xf)
126 | }
127 | }
128 | }
129 |
130 | def drawBody(body: Body) {
131 | val xf = body.transform
132 | for (f <- body.fixtures) {
133 | if (!f.isSensor || flag(Sensors)) {
134 | val color = if (f.isSensor) Colors.Sensor
135 | else if (body.isStatic) Colors.Static
136 | else if (body.isSleeping) Colors.Sleeping
137 | else Colors.Dynamic
138 | drawShape(f.shape, xf, color, flag(CoreShapes))
139 | }
140 | }
141 | }
142 |
143 | def drawShape(shape: Shape, xf: Transform2, color: Color3f, core: Boolean) {
144 | shape match {
145 | case circle: Circle =>
146 | val center = xf * circle.pos
147 | val radius = circle.radius
148 | val axis = xf.rot.col1
149 |
150 | handler.drawSolidCircle(center, radius, axis, color)
151 |
152 | if (core) {
153 | handler.drawCircle(center, radius - Settings.toiSlop, Colors.Core)
154 | }
155 | case poly: Polygon =>
156 | val vertexCount = poly.vertexCount
157 | assert(vertexCount <= Settings.maxPolygonVertices)
158 | val localVertices = poly.vertices
159 | var vertices = localVertices.map(v => xf * v)
160 |
161 | handler.drawSolidPolygon(vertices, color)
162 |
163 | if (core) {
164 | val localCoreVertices = poly.coreVertices
165 | vertices = localCoreVertices.map(v => xf * v)
166 | handler.drawPolygon(vertices, Colors.Core)
167 | }
168 | case edge: Edge =>
169 | val v1 = xf * edge.v1
170 | val v2 = xf * edge.v2
171 | handler.drawSegment(v1, v2, color)
172 | }
173 | }
174 |
175 | def drawJoint(joint: Joint) {
176 | val b1 = joint.body1
177 | val b2 = joint.body2
178 | val xf1 = b1.transform
179 | val xf2 = b2.transform
180 | val x1 = xf1.pos
181 | val x2 = xf2.pos
182 | val p1 = joint.anchor1
183 | val p2 = joint.anchor2
184 |
185 | joint match {
186 | case _: DistanceJoint =>
187 | handler.drawSegment(p1, p2, Colors.Joint)
188 | /* if (type == JointType.PULLEY_JOINT) {
189 | PulleyJoint pulley = (PulleyJoint)joint;
190 | Vec2 s1 = pulley.getGroundAnchor1();
191 | Vec2 s2 = pulley.getGroundAnchor2();
192 | m_debughandler.drawSegment(s1, p1, color);
193 | m_debughandler.drawSegment(s2, p2, color);
194 | m_debughandler.drawSegment(s1, s2, color);
195 | }*/
196 | case _: PointingDeviceJoint =>
197 | handler.drawSegment(p1, p2, Colors.Joint)
198 | case _ =>
199 | handler.drawSegment(x1, p1, Colors.Joint)
200 | handler.drawSegment(p1, p2, Colors.Joint)
201 | handler.drawSegment(x2, p2, Colors.Joint)
202 | }
203 | }
204 |
205 | }
206 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/collision/PolygonEdgeCollider.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.collision
2 |
3 | import vecmath._
4 | import vecmath.Preamble._
5 | import shapes._
6 |
7 | object PolygonEdgeCollider extends Collider[Polygon, Edge] {
8 |
9 | def collide(polygon: Polygon, xf1: Transform2,
10 | edge: Edge, xf2: Transform2): Option[Manifold] = {
11 | val v1 = xf2 * edge.v1
12 | val v2 = xf2 * edge.v2
13 | val n = xf2.rot * edge.normal
14 | val v1Local = xf1 ** v1
15 | val v2Local = xf1 ** v2
16 | val nLocal = xf1.rot ** n
17 |
18 | var separation1 = 0f
19 | var separationIndex1 = -1 // which normal on the poly found the shallowest depth?
20 | var separationMax1 = -Float.MaxValue // the shallowest depth of edge in poly
21 | var separation2 = 0f
22 | var separationIndex2 = -1 // which normal on the poly found the shallowest depth?
23 | var separationMax2 = -Float.MaxValue // the shallowest depth of edge in poly
24 |
25 | var separationMax = -Float.MaxValue // the shallowest depth of edge in poly
26 | var separationV1 = false // is the shallowest depth from edge's v1 or v2 vertex?
27 | var separationIndex = -1 // which normal on the poly found the shallowest depth?
28 |
29 | val vertexCount = polygon.vertexCount
30 | val vertices = polygon.vertices
31 | val normals = polygon.normals
32 |
33 | var enterStartIndex = -1 // the last poly vertex above the edge
34 | var enterEndIndex = -1 // the first poly vertex below the edge
35 | var exitStartIndex = -1 // the last poly vertex below the edge
36 | var exitEndIndex = -1 // the first poly vertex above the edge
37 | //int deepestIndex;
38 |
39 | // the "N" in the following variables refers to the edge's normal.
40 | // these are projections of poly vertices along the edge's normal,
41 | // a.k.a. they are the separation of the poly from the edge.
42 | var prevSepN = 0.0f
43 | var nextSepN = 0.0f
44 | var enterSepN = 0.0f // the depth of enterEndIndex under the edge (stored as a separation, so it's negative)
45 | var exitSepN = 0.0f // the depth of exitStartIndex under the edge (stored as a separation, so it's negative)
46 | var deepestSepN = Float.MaxValue // the depth of the deepest poly vertex under the end (stored as a separation, so it's negative)
47 |
48 | // for each poly normal, get the edge's depth into the poly.
49 | // for each poly vertex, get the vertex's depth into the edge.
50 | // use these calculations to define the remaining variables declared above.
51 | prevSepN = (vertices(vertexCount - 1) - v1Local) dot nLocal
52 |
53 | for (i <- 0 until vertexCount) {
54 | separation1 = (v1Local - vertices(i)) dot normals(i)
55 | separation2 = (v2Local - vertices(i)) dot normals(i)
56 | if (separation2 < separation1) {
57 | if (separation2 > separationMax) {
58 | separationMax = separation2
59 | separationV1 = false
60 | separationIndex = i
61 | }
62 | } else {
63 | if (separation1 > separationMax) {
64 | separationMax = separation1
65 | separationV1 = true
66 | separationIndex = i
67 | }
68 | }
69 | if (separation1 > separationMax1) {
70 | separationMax1 = separation1
71 | separationIndex1 = i
72 | }
73 | if (separation2 > separationMax2) {
74 | separationMax2 = separation2
75 | separationIndex2 = i
76 | }
77 |
78 | nextSepN = (vertices(i) - v1Local) dot nLocal
79 | if (nextSepN >= 0.0f && prevSepN < 0.0f) {
80 | exitStartIndex = if (i == 0) vertexCount - 1 else i - 1
81 | exitEndIndex = i
82 | exitSepN = prevSepN
83 | } else if (nextSepN < 0.0f && prevSepN >= 0.0f) {
84 | enterStartIndex = if (i == 0) vertexCount - 1 else i - 1
85 | enterEndIndex = i
86 | enterSepN = nextSepN
87 | }
88 | if (nextSepN < deepestSepN) {
89 | deepestSepN = nextSepN;
90 | //deepestIndex = i;
91 | }
92 | prevSepN = nextSepN
93 | }
94 |
95 | if (enterStartIndex == -1) {
96 | // poly is entirely below or entirely above edge, return with no contact:
97 | return None
98 | }
99 | if (separationMax > 0.0f) {
100 | // poly is laterally disjoint with edge, return with no contact:
101 | return None
102 | }
103 |
104 | // if the poly is near a convex corner on the edge
105 | if ((separationV1 && edge.corner1Convex) || (!separationV1 && edge.corner2Convex)) {
106 | // if shallowest depth was from edge into poly,
107 | // use the edge's vertex as the contact point:
108 | if (separationMax > deepestSepN + Settings.linearSlop) {
109 | // if -normal angle is closer to adjacent edge than this edge,
110 | // let the adjacent edge handle it and return with no contact:
111 | if (separationV1) {
112 | val temp = xf1.rot ** (xf2.rot * edge.corner1Dir)
113 | if ((normals(separationIndex1) dot temp) >= 0.0f) {
114 | return None
115 | }
116 | } else {
117 | val temp = xf1.rot ** (xf2.rot * edge.corner2Dir)
118 | if ((normals(separationIndex2) dot temp) <= 0.0f) {
119 | return None
120 | }
121 | }
122 |
123 | val id = ContactID(0, separationIndex, ContactID.NullFeature, false)
124 | val normal = xf1.rot * normals(separationIndex)
125 | val (p1, p2) = if (separationV1)
126 | (v1Local, edge.v1)
127 | else
128 | (v2Local, edge.v2)
129 | val points = new Array[ManifoldPoint](1)
130 | points(0) = ManifoldPoint(p1, p2, separationMax, id)
131 | return Some(Manifold(points, normal))
132 | }
133 | }
134 |
135 | // We're going to use the edge's normal now.
136 | val normal = -n
137 |
138 | // Check whether we only need one contact point.
139 | if (enterEndIndex == exitStartIndex) {
140 | val id = ContactID(0, enterEndIndex, ContactID.NullFeature, false)
141 | val p1 = vertices(enterEndIndex)
142 | val p2 = xf1 * p1
143 | val points = new Array[ManifoldPoint](1)
144 | points(0) = ManifoldPoint(p1, p2, enterSepN, id)
145 | return Some(Manifold(points, normal))
146 | }
147 |
148 | // dirLocal should be the edge's direction vector, but in the frame of the polygon.
149 | val dirLocal = -nLocal.normal // TODO: figure out why this optimization didn't work
150 | //Vec2 dirLocal = XForm.mulT(xf1.R, XForm.mul(xf2.R, edge.GetDirectionVector()));
151 |
152 | val dirProj1 = dirLocal dot (vertices(enterEndIndex) - v1Local)
153 | var dirProj2 = 0.0f
154 |
155 | // The contact resolution is more robust if the two manifold points are
156 | // adjacent to each other on the polygon. So pick the first two poly
157 | // vertices that are under the edge:
158 |
159 | exitEndIndex = if (enterEndIndex == vertexCount - 1) 0 else enterEndIndex + 1
160 | if (exitEndIndex != exitStartIndex) {
161 | exitStartIndex = exitEndIndex
162 | exitSepN = nLocal dot (vertices(exitStartIndex) - v1Local)
163 | }
164 | dirProj2 = dirLocal dot (vertices(exitStartIndex) - v1Local)
165 |
166 | val points = new Array[ManifoldPoint](2)
167 | val id1 = ContactID(0, enterEndIndex, ContactID.NullFeature, false)
168 |
169 | val edgeLen = edge.length
170 | if (dirProj1 > edgeLen) {
171 | val ratio = (edgeLen - dirProj2) / (dirProj1 - dirProj2)
172 | val sep = if (ratio > 100.0f * Settings.Epsilon && ratio < 1.0f)
173 | exitSepN * (1.0f - ratio) + enterSepN * ratio
174 | else
175 | enterSepN
176 | points(0) = ManifoldPoint(v2Local, edge.v2, sep, id1)
177 | } else {
178 | val p1 = vertices(enterEndIndex)
179 | val p2 = xf2 ** (xf1 * vertices(enterEndIndex))
180 | points(0) = ManifoldPoint(p1, p2, enterSepN, id1)
181 | }
182 |
183 | val id2 = ContactID(0, exitStartIndex, ContactID.NullFeature, false)
184 |
185 | if (dirProj2 < 0.0f) {
186 | val ratio = (-dirProj1) / (dirProj2 - dirProj1)
187 | val sep = if (ratio > 100.0f * Settings.Epsilon && ratio < 1.0f)
188 | enterSepN * (1.0f - ratio) + exitSepN * ratio
189 | else
190 | exitSepN
191 | points(1) = ManifoldPoint(v1Local, edge.v1, sep, id2)
192 | } else {
193 | val p1 = vertices(exitStartIndex)
194 | val p2 = xf2 ** (xf1 * vertices(exitStartIndex))
195 | points(1) = ManifoldPoint(p1, p2, exitSepN, id2)
196 | }
197 | Some(Manifold(points, normal))
198 | }
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/scalabox2d/src/main/scala/org/villane/box2d/dynamics/Island.scala:
--------------------------------------------------------------------------------
1 | package org.villane.box2d.dynamics
2 |
3 | import collision._
4 | import joints._
5 | import contacts._
6 | import vecmath._
7 | import vecmath.Preamble._
8 |
9 | import collection.jcl.ArrayList
10 |
11 | /**
12 | * Handles much of the heavy lifting of physics solving - for internal use.
13 | */
14 | class Island(val bodyCapacity: Int,
15 | val contactCapacity: Int,
16 | val jointCapacity: Int,
17 | val listener: ContactListener) {
18 | // XXX To reduce code size, replaced the three fixed-size arrays with listbuffers. Perhaps should return later?
19 | val bodies = new ArrayList[Body]//(new java.util.ArrayList[Body](bodyCapacity))
20 | var contacts = new ArrayList[Contact]//(new java.util.ArrayList[Contact](contactCapacity))
21 | var joints = new ArrayList[Joint]//(new java.util.ArrayList[Joint](jointCapacity))
22 |
23 | // ERKKI: This was static, but that seemed like a bug
24 | var positionIterationCount = 0
25 |
26 | var positionError = 0f
27 |
28 | def clear() {
29 | bodies.clear()
30 | contacts.clear()
31 | joints.clear()
32 | }
33 |
34 | def add(body: Body) = bodies += body
35 | def add(contact: Contact) = contacts += contact
36 | def add(joint: Joint) = joints += joint
37 |
38 | def solve(step: TimeStep, gravity: Vector2, correctPositions: Boolean, allowSleep: Boolean) {
39 | // Integrate velocities and apply damping.
40 | for (b <- bodies) {
41 | if (!b.isStatic) {
42 | // Integrate velocities.
43 | b.linearVelocity += (gravity + b.force * b.invMass) * step.dt
44 | b.angularVelocity += step.dt * b.invI * b.torque
45 |
46 | // Reset forces.
47 | b.force = Vector2.Zero
48 | b.torque = 0f
49 |
50 | // Apply damping.
51 | // ODE: dv/dt + c * v = 0
52 | // Solution: v(t) = v0 * exp(-c * t)
53 | // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt)
54 | // v2 = exp(-c * dt) * v1
55 | // Taylor expansion:
56 | // v2 = (1.0f - c * dt) * v1
57 | b.linearVelocity *= (1f - step.dt * b.linearDamping).clamp(0f, 1f)
58 | b.angularVelocity *= (1f - step.dt * b.angularDamping).clamp(0f, 1f)
59 |
60 | // Check for large velocities.
61 | if ((b.linearVelocity ∙ b.linearVelocity) > Settings.maxLinearVelocitySquared) {
62 | b.linearVelocity = b.linearVelocity.normalize * Settings.maxLinearVelocity
63 | }
64 |
65 | if (b.angularVelocity * b.angularVelocity > Settings.maxAngularVelocitySquared) {
66 | if (b.angularVelocity < 0f) {
67 | b.angularVelocity = -Settings.maxAngularVelocity
68 | } else {
69 | b.angularVelocity = Settings.maxAngularVelocity
70 | }
71 | }
72 | }
73 | }
74 |
75 | val contactSolver = new ContactSolver(contacts)
76 |
77 | // Initialize velocity constraints.
78 | contactSolver.initVelocityConstraints(step)
79 |
80 | for (joint <- joints) {
81 | joint.initVelocityConstraints(step)
82 | }
83 |
84 | // Solve velocity constraints.
85 | var i = 0
86 | while (i < step.maxIterations) {
87 | i += 1
88 |
89 | contactSolver.solveVelocityConstraints()
90 |
91 | var j = 0
92 | while (j < joints.length) {
93 | val joint = joints(j)
94 | j += 1
95 |
96 | joint.solveVelocityConstraints(step)
97 | }
98 | }
99 |
100 | // Post-solve (store impulses for warm starting).
101 | contactSolver.finalizeVelocityConstraints()
102 |
103 | // Integrate positions.
104 | for (b <- bodies) {
105 | if (!b.isStatic) {
106 | // Store positions for continuous collision.
107 | // TODO ERKKI : perhaps this should be capsulated as a method in body or sweep?
108 | b.sweep.c0 = b.sweep.c
109 | b.sweep.a0 = b.sweep.a
110 |
111 | // Integrate
112 | b.sweep.c += step.dt * b.linearVelocity
113 | b.sweep.a += step.dt * b.angularVelocity
114 |
115 | // Compute new transform
116 | b.synchronizeTransform()
117 |
118 | // Note: shapes are synchronized later.
119 | }
120 | }
121 |
122 | if (correctPositions) {
123 | // Initialize position constraints.
124 | // Contacts don't need initialization.
125 | for (joint <- joints) {
126 | joint.initPositionConstraints()
127 | }
128 |
129 | // Iterate over constraints.
130 | var loop = true
131 | positionIterationCount = 0
132 | while (loop && positionIterationCount < step.maxIterations) {
133 | val contactsOkay = contactSolver.solvePositionConstraints(Settings.contactBaumgarte)
134 |
135 | var jointsOkay = true
136 | // do we have to call this for all joints always? otherwise we could do
137 | // val jointsOkay = joints.forall(_.solvePositionConstraint())
138 | for (joint <- joints) {
139 | val jointOkay = joint.solvePositionConstraints()
140 | jointsOkay &&= jointOkay
141 | }
142 |
143 | if (contactsOkay && jointsOkay) {
144 | loop = false
145 | } else {
146 | positionIterationCount += 1
147 | }
148 | }
149 | }
150 |
151 | report(contactSolver.constraints)
152 |
153 | if (allowSleep) {
154 | var minSleepTime = Float.MaxValue
155 |
156 | val linTolSqr = Settings.linearSleepTolerance * Settings.linearSleepTolerance
157 | val angTolSqr = Settings.angularSleepTolerance * Settings.angularSleepTolerance
158 |
159 | for (b <- bodies) {
160 | if (b.invMass != 0f) {
161 | if (! b.isAllowSleeping) {
162 | b.sleepTime = 0f
163 | minSleepTime = 0f
164 | }
165 |
166 | if (! b.isAllowSleeping ||
167 | b.angularVelocity * b.angularVelocity > angTolSqr ||
168 | (b.linearVelocity ∙ b.linearVelocity) > linTolSqr) {
169 | b.sleepTime = 0f
170 | minSleepTime = 0f
171 | } else {
172 | b.sleepTime += step.dt
173 | minSleepTime = min(minSleepTime, b.sleepTime)
174 | }
175 | }
176 | }
177 |
178 | if (minSleepTime >= Settings.timeToSleep) {
179 | for (b <- bodies) {
180 | b.flags |= BodyFlags.sleep
181 | b.linearVelocity = Vector2.Zero
182 | b.angularVelocity = 0f
183 | }
184 | }
185 | }
186 | }
187 |
188 | def solveTOI(subStep: TimeStep) {
189 | val contactSolver = new ContactSolver(contacts)
190 |
191 | // No warm starting needed for TOI events.
192 |
193 | // Solve velocity constraints.
194 | for (i <- 0 until subStep.maxIterations) {
195 | contactSolver.solveVelocityConstraints()
196 | }
197 |
198 | // Don't store the TOI contact forces for warm starting
199 | // because they can be quite large.
200 |
201 | // Integrate positions.
202 | for (b <- bodies) {
203 | if (!b.isStatic) {
204 | //System.out.println("(Island::SolveTOI 1) :"+b.m_sweep);
205 | // Store positions for continuous collision.
206 | b.sweep.c0 = b.sweep.c
207 | b.sweep.a0 = b.sweep.a
208 |
209 | // Integrate
210 | b.sweep.c += subStep.dt * b.linearVelocity
211 | b.sweep.a += subStep.dt * b.angularVelocity
212 |
213 | //System.out.println("(Island::SolveTOI 2) :"+b.m_sweep);
214 | // Compute new transform
215 | b.synchronizeTransform();
216 |
217 | // System.out.println("(Island::SolveTOI 3) :"+b.m_sweep);
218 | // Note: shapes are synchronized later.
219 | }
220 | }
221 |
222 | // Solve position constraints.
223 | val k_toiBaumgarte = 0.75f
224 | var contactsOkay = false
225 | var i = 0
226 | while (!contactsOkay && i < subStep.maxIterations) {
227 | val contactsOkay = contactSolver.solvePositionConstraints(k_toiBaumgarte)
228 | i += 1
229 | }
230 |
231 | report(contactSolver.constraints)
232 | }
233 |
234 | def report(constraints: Array[ContactConstraint]): Unit = {
235 | if (listener == null) {
236 | return
237 | }
238 |
239 | var i = 0
240 | while (i < contacts.length) {
241 | val c = contacts(i)
242 | val cc = constraints(i)
243 | i += 1
244 |
245 | val f1 = c.fixture1
246 | val f2 = c.fixture2
247 | val b1 = f1.body
248 | for (manifold <- c.manifolds) {
249 | for (k <- 0 until manifold.points.length) {
250 | val point = manifold.points(k)
251 | val ccp = cc.points(k)
252 | val pos = b1.transform * point.localPoint1
253 |
254 | // TOI constraint results are not stored, so get
255 | // the result from the constraint.
256 | val cr = new ContactResult(f1, f2,
257 | pos, manifold.normal,
258 | ccp.normalImpulse, ccp.tangentImpulse,
259 | point.id)
260 | listener.result(cr)
261 | }
262 | }
263 | }
264 | }
265 |
266 | }
267 |
--------------------------------------------------------------------------------