Streamline KBox2D api by removing unnecessary interfaces

This commit is contained in:
DBotThePony 2022-02-20 14:32:53 +07:00
parent 721d2a2029
commit 27a870fcf0
Signed by: DBot
GPG Key ID: DCC23B5715498507
32 changed files with 1187 additions and 1360 deletions

View File

@ -148,353 +148,3 @@ enum class BodyFlags(val bitmask: Int) {
return other and bitmask.inv()
}
}
interface IBody {
/** Creates a fixture and attach it to this body. Use this function if you need
* to set some fixture parameters, like friction. Otherwise you can create the
* fixture directly from a shape.
* If the density is non-zero, this function automatically updates the mass of the body.
* Contacts are not created until the next time step.
* @param def the fixture definition.
* @warning This function is locked during callbacks.
*/
fun createFixture(def: FixtureDef): IFixture
/**
* Creates a fixture from a shape and attach it to this body.
* This is a convenience function. Use b2FixtureDef if you need to set parameters
* like friction, restitution, user data, or filtering.
* If the density is non-zero, this function automatically updates the mass of the body.
* @param shape the shape to be cloned.
* @param density the shape density (set to zero for static bodies).
* @warning This function is locked during callbacks.
*/
fun createFixture(shape: IShape<*>, density: Double): IFixture {
return createFixture(
FixtureDef(
shape = shape,
density = density
)
)
}
/**
* Destroy a fixture. This removes the fixture from the broad-phase and
* destroys all contacts associated with this fixture. This will
* automatically adjust the mass of the body if the body is dynamic and the
* fixture has positive density.
* All fixtures attached to a body are implicitly destroyed when the body is destroyed.
* @param fixture the fixture to be removed.
* @warning This function is locked during callbacks.
*/
fun destroyFixture(fixture: IFixture)
/**
* Set the position of the body's origin and rotation.
* Manipulating a body's transform may cause non-physical behavior.
* Note: contacts are updated on the next call to b2World::Step.
* @param position the world position of the body's local origin.
* @param angle the world rotation in radians.
*/
fun setTransform(position: Vector2d, angle: Double)
/**
* Get the body transform for the body's origin.
* @return the world transform of the body's origin.
*/
val transform: Transform
/**
* Get the world body origin position.
* @return the world position of the body's origin.
*/
val position: Vector2d
/**
* Get the angle in radians.
* @return the current world rotation angle in radians.
*/
val angle: Double
/**
* Get the world position of the center of mass.
*/
val worldCenter: Vector2d
/**
* Get the local position of the center of mass.
*/
val localCenter: Vector2d
/**
* The linear velocity of the body's origin in world co-ordinates.
*/
var linearVelocity: Vector2d
/**
* The angular velocity of the body.
*/
var angularVelocity: Double
/**
* Apply a force at a world point. If the force is not
* applied at the center of mass, it will generate a torque and
* affect the angular velocity. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyForce(force: Vector2d, point: Vector2d, wake: Boolean = true)
/**
* Apply a force to the center of mass. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param wake also wake up the body
*/
fun applyForceToCenter(force: Vector2d, wake: Boolean = true)
/**
* Apply a torque. This affects the angular velocity
* without affecting the linear velocity of the center of mass.
* @param torque about the z-axis (out of the screen), usually in N-m.
* @param wake also wake up the body
*/
fun applyTorque(torque: Double, wake: Boolean = true)
/**
* Apply an impulse at a point. This immediately modifies the velocity.
* It also modifies the angular velocity if the point of application
* is not at the center of mass. This wakes up the body.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyLinearImpulse(impulse: Vector2d, point: Vector2d, wake: Boolean = true)
/**
* Apply an impulse to the center of mass. This immediately modifies the velocity.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param wake also wake up the body
*/
fun applyLinearImpulseToCenter(impulse: Vector2d, wake: Boolean = true)
/**
* Apply an angular impulse.
* @param impulse the angular impulse in units of kg*m*m/s
* @param wake also wake up the body
*/
fun applyAngularImpulse(impulse: Double, wake: Boolean = true)
/**
* Get the total mass of the body.
* @return the mass, usually in kilograms (kg).
*/
val mass: Double
/**
* Get the rotational inertia of the body about the local origin.
* @return the rotational inertia, usually in kg-m^2.
*/
val inertia: Double
/**
* Get the mass data of the body.
* @return a struct containing the mass, inertia and center of the body.
* Set the mass properties to override the mass properties of the fixtures.
* Note that this changes the center of mass position.
* Note that creating or destroying fixtures can also alter the mass.
* This function has no effect if the body isn't dynamic.
* @param data the mass properties.
*/
var massData: MassData
/**
* This resets the mass properties to the sum of the mass properties of the fixtures.
* This normally does not need to be called unless you called SetMassData to override
* the mass and you later want to reset the mass.
*/
fun resetMassData()
/**
* Get the world coordinates of a point given the local coordinates.
* @param localPoint a point on the body measured relative the the body's origin.
* @return the same point expressed in world coordinates.
*/
fun getWorldPoint(localPoint: Vector2d): Vector2d
/**
* Get the world coordinates of a vector given the local coordinates.
* @param localVector a vector fixed in the body.
* @return the same vector expressed in world coordinates.
*/
fun getWorldVector(localPoint: Vector2d): Vector2d
/**
* Gets a local point relative to the body's origin given a world point.
* @param worldPoint a point in world coordinates.
* @return the corresponding local point relative to the body's origin.
*/
fun getLocalPoint(worldPoint: Vector2d): Vector2d
/**
* Gets a local vector given a world vector.
* @param worldVector a vector in world coordinates.
* @return the corresponding local vector.
*/
fun getLocalVector(worldVector: Vector2d): Vector2d
/**
* Get the world linear velocity of a world point attached to this body.
* @param worldPoint a point in world coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromWorldPoint(worldPoint: Vector2d): Vector2d
/**
* Get the world velocity of a local point.
* @param localPoint a point in local coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromLocalPoint(localPoint: Vector2d): Vector2d
var linearDamping: Double
var angularDamping: Double
var gravityScale: Double
/**
* Set the type of this body. This may alter the mass and velocity.
*/
var type: BodyType
/**
* Should this body be treated like a bullet for continuous collision detection?
*/
var isBullet: Boolean
/**
* You can disable sleeping on this body. If you disable sleeping, the
* body will be woken.
*/
var allowAutoSleep: Boolean
/**
* Set the sleep state of the body. A sleeping body has very
* low CPU cost.
* @param flag set to true to wake the body, false to put it to sleep.
*/
var isAwake: Boolean
/**
* Allow a body to be disabled. A disabled body is not simulated and cannot
* be collided with or woken up.
*
* If you pass a flag of true, all fixtures will be added to the broad-phase.
*
* If you pass a flag of false, all fixtures will be removed from the
* broad-phase and all contacts will be destroyed.
*
* Fixtures and joints are otherwise unaffected. You may continue
* to create/destroy fixtures and joints on disabled bodies.
*
* Fixtures on a disabled body are implicitly disabled and will
* not participate in collisions, ray-casts, or queries.
*
* Joints connected to a disabled body are implicitly disabled.
*
* An diabled body is still owned by a b2World object and remains
* in the body list.
*/
var isEnabled: Boolean
/**
* Set this body to have fixed rotation. This causes the mass to be reset.
*/
var isFixedRotation: Boolean
/**
* Get the list of all fixtures attached to this body.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val fixtureList: IFixture?
val fixtureIterator: Iterator<IFixture> get() {
return object : Iterator<IFixture> {
private var node = fixtureList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IFixture {
val old = node!!
node = old.next
return old
}
}
}
/**
* Get the list of all joints attached to this body.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val jointList: JointEdge?
val jointIterator: Iterator<JointEdge> get() {
return object : Iterator<JointEdge> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): JointEdge {
val old = node!!
node = old.next
return old
}
}
}
/**
* Get the list of all contacts attached to this body.
* @warning this list changes during the time step and you may
* miss some collisions if you don't use b2ContactListener.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val contactEdge: ContactEdge?
val contactEdgeIterator: Iterator<ContactEdge> get() {
return object : Iterator<ContactEdge> {
private var node = contactEdge
override fun hasNext(): Boolean {
return node != null
}
override fun next(): ContactEdge {
val old = node!!
node = old.next
return old
}
}
}
val next: IBody?
val prev: IBody?
val userData: Any?
/**
* Get the parent world of this body.
*/
val world: IB2World
/// Dump this body to a file
fun dump()
}

View File

@ -1,47 +0,0 @@
package ru.dbotthepony.kbox2d.api
typealias b2Pair = Pair<Int, Int>
const val e_nullProxy = -1
/**
* The broad-phase is used for computing pairs and performing volume queries and ray casts.
* This broad-phase does not persist pairs. Instead, this reports potentially new pairs.
* It is up to the client to consume the new pairs and to track subsequent overlap.
*/
interface IBroadPhase : IProxieable, IMovable {
/**
* Call to trigger a re-processing of it's pairs on the next call to UpdatePairs.
*/
fun touchProxy(proxyID: Int)
/**
* Test overlap of fat AABBs.
*/
fun testOverlap(proxyIDA: Int, proxyIDB: Int): Boolean
/**
* Get the number of proxies.
*/
val proxyCount: Int
/**
* Update the pairs. This results in pair callbacks. This can only add pairs.
*/
fun updatePairs(callback: (Any?, Any?) -> Unit)
/**
* Get the height of the embedded tree.
*/
val treeHeight: Int
/**
* Get the balance of the embedded tree.
*/
val treeBalance: Int
/**
* Get the quality metric of the embedded tree.
*/
val treeQuality: Double
}

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
import kotlin.math.sqrt
/// Friction mixing law. The idea is to allow either fixture to drive the friction to zero.
@ -31,8 +33,8 @@ data class ContactRegister(
* nodes, one for each attached body.
*/
data class ContactEdge(
val other: ru.dbotthepony.kbox2d.api.IBody, ///< provides quick access to the other body attached.
val contact: IContact, ///< the contact
val other: Body, ///< provides quick access to the other body attached.
val contact: AbstractContact, ///< the contact
var prev: ContactEdge? = null, ///< the previous contact edge in the body's contact list
var next: ContactEdge? = null, ///< the next contact edge in the body's contact list
)
@ -68,79 +70,3 @@ enum class ContactFlags(val bitmask: Int) {
return other and bitmask.inv()
}
}
/**
* The class manages contact between two shapes. A contact exists for each overlapping
* AABB in the broad-phase (except if filtered). Therefore a contact object may exist
* that has no contact points.
*/
interface IContact {
/// Get the contact manifold. Do not modify the manifold unless you understand the
/// internals of Box2D.
val manifold: Manifold
/// Get the world manifold.
val worldManifold: IWorldManifold
/// Is this contact touching?
val isTouching: Boolean
/// Enable/disable this contact. This can be used inside the pre-solve
/// contact listener. The contact is only disabled for the current
/// time step (or sub-step in continuous collisions).
var isEnabled: Boolean
/// Get the next contact in the world's contact list.
val next: IContact?
val prev: IContact?
/// Get fixture A in this contact.
val fixtureA: IFixture
/// Get the child primitive index for fixture A.
val childIndexA: Int
/// Get fixture B in this contact.
val fixtureB: IFixture
/// Get the child primitive index for fixture B.
val childIndexB: Int
/// Override the default friction mixture. You can call this in b2ContactListener::PreSolve.
/// This value persists until set or reset.
/// Get the friction.
var friction: Double
/// Reset the friction mixture to the default value.
fun resetFriction() {
friction = b2MixFriction(fixtureA.friction, fixtureB.friction)
}
fun flagForFiltering()
/// Override the default restitution mixture. You can call this in b2ContactListener::PreSolve.
/// The value persists until you set or reset.
var restitution: Double
/// Reset the restitution to the default value.
fun resetRestitution() {
restitution = b2MixRestitution(fixtureA.restitution, fixtureB.restitution)
}
/// Override the default restitution velocity threshold mixture. You can call this in b2ContactListener::PreSolve.
/// The value persists until you set or reset.
/// Get the restitution threshold.
var restitutionThreshold: Double
/// Reset the restitution threshold to the default value.
fun resetRestitutionThreshold() {
restitutionThreshold = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold)
}
/// Set the desired tangent speed for a conveyor belt behavior. In meters per second.
/// Get the desired tangent speed. In meters per second.
var tangentSpeed: Double
/// Evaluate this contact with your own manifold and transforms.
fun evaluate(xfA: Transform, xfB: Transform): Manifold
}

View File

@ -1,37 +0,0 @@
package ru.dbotthepony.kbox2d.api
/**
* Delegate of b2World.
*/
interface IContactManager {
fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?)
fun findNewContacts()
fun destroy(contact: IContact)
fun collide()
val broadPhase: IBroadPhase
var contactList: IContact?
val contactListIterator: Iterator<IContact> get() {
return object : Iterator<IContact> {
private var node = contactList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IContact {
val old = node!!
node = old.next
return old
}
}
}
val contactCount: Int
var contactFilter: IContactFilter?
var contactListener: IContactListener?
}

View File

@ -1,26 +1,8 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.DistanceProxy
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
/**
* A distance proxy is used by the GJK algorithm.
* It encapsulates any shape.
*/
interface IDistanceProxy {
val vertices: List<Vector2d>
val radius: Double
/**
* Get the supporting vertex index in the given direction.
*/
fun getSupport(d: Vector2d): Int
/**
* Get the supporting vertex in the given direction.
*/
fun getSupportVertex(d: Vector2d): Vector2d
}
/**
* Used to warm start b2Distance.
* Set count to zero on first call.
@ -38,8 +20,8 @@ data class SimplexCache(
* in the computation. Even
*/
data class DistanceInput(
var proxyA: IDistanceProxy,
var proxyB: IDistanceProxy,
var proxyA: DistanceProxy,
var proxyB: DistanceProxy,
var transformA: Transform = Transform(),
var transformB: Transform = Transform(),
var useRadii: Boolean = false
@ -57,8 +39,8 @@ data class DistanceOutput(
)
data class ShapeCastInput(
var proxyA: IDistanceProxy,
var proxyB: IDistanceProxy,
var proxyA: DistanceProxy,
var proxyB: DistanceProxy,
var transformA: Transform,
var transformB: Transform,
var translationB: Vector2d,

View File

@ -1,58 +0,0 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.DynamicTree
import ru.dbotthepony.kbox2d.collision.b2_nullNode
import ru.dbotthepony.kvector.util2d.AABB
private const val DEBUG_CYCLIC_REFERENCES = true
/// A node in the dynamic tree. The client does not interact with this directly.
data class TreeNode(
val tree: DynamicTree,
/// Enlarged AABB
var aabb: AABB? = null,
var child1: Int = 0,
var child2: Int = 0,
// leaf = 0, free node = -1
var height: Int = 0,
var moved: Boolean = false,
var userData: Any? = null,
) {
val isLeaf get() = child1 == b2_nullNode
// union
private var _union: Int = b2_nullNode
var parent by this::_union
var next by this::_union
}
interface IDynamicTree : IProxieable, IMovable {
fun wasMoved(proxyID: Int): Boolean
fun clearMoved(proxyID: Int)
/**
* Validate this tree. For testing.
*/
fun validate()
/**
* Compute the height of the binary tree in O(N) time. Should not be
* called often.
*/
val height: Int
/// Get the maximum balance of an node in the tree. The balance is the difference
/// in height of the two children of a node.
val maxBalance: Int
/// Get the ratio of the sum of the node areas to the root area.
val getAreaRatio: Double
/// Build an optimal tree. Very expensive. For testing.
fun rebuildBottomUp()
}

View File

@ -1,5 +1,7 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.e_nullProxy
import ru.dbotthepony.kbox2d.dynamics.Fixture
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
@ -40,26 +42,38 @@ data class ImmutableFilter(
) : IFilter
data class FixtureDef(
/// The shape, this must be set. The shape will be cloned.
/**
* The shape, this must be set. The shape will be cloned.
*/
var shape: IShape<*>? = null,
var friction: Double = 0.2,
/// The restitution (elasticity) usually in the range [0,1].
/**
* The restitution (elasticity) usually in the range [0,1].
*/
var restitution: Double = 0.0,
/// Restitution velocity threshold, usually in m/s. Collisions above this
/// speed have restitution applied (will bounce).
/**
* Restitution velocity threshold, usually in m/s. Collisions above this
* speed have restitution applied (will bounce).
*/
var restitutionThreshold: Double = 1.0 * b2_lengthUnitsPerMeter,
/// The density, usually in kg/m^2.
/**
* The density, usually in kg/m^2.
*/
var density: Double = 0.0,
/// A sensor shape collects contact information but never generates a collision
/// response.
/**
* A sensor shape collects contact information but never generates a collision
* response.
*/
var isSensor: Boolean = false,
/// Use this to store application specific fixture data.
/**
* Use this to store application specific fixture data.
*/
var userData: Any? = null,
val filter: Filter = Filter()
@ -67,7 +81,7 @@ data class FixtureDef(
data class FixtureProxy(
var aabb: AABB,
val fixture: IFixture,
val fixture: Fixture,
val childIndex: Int,
) {
private var setProxyID = false
@ -83,99 +97,3 @@ data class FixtureProxy(
throw IllegalStateException("FixtureProxy should be immutable (tried to set value $value, already having $field)")
}
}
/**
* A fixture is used to attach a shape to a body for collision detection. A fixture
* inherits its transform from its parent. Fixtures hold additional non-geometric data
* such as friction, collision filters, etc.
* Fixtures are created via b2Body::CreateFixture.
* @warning you cannot reuse fixtures.
*/
interface IFixture {
/// Get the type of the child shape. You can use this to down cast to the concrete shape.
/// @return the shape type.
val type: IShape.Type
/// Get the child shape. You can modify the child shape, however you should not change the
/// number of vertices because this will crash some collision caching mechanisms.
/// Manipulating the shape may lead to non-physical behavior.
val shape: IShape<*>
/// Set if this fixture is a sensor.
/// Is this fixture a sensor (non-solid)?
/// @return the true if the shape is a sensor.
var isSensor: Boolean
/// Set the contact filtering data. This will not update contacts until the next time
/// step when either parent body is active and awake.
/// This automatically calls Refilter.
var filter: IFilter
/// Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide.
fun refilter()
/// Get the parent body of this fixture. This is nullptr if the fixture is not attached.
/// @return the parent body.
val body: ru.dbotthepony.kbox2d.api.IBody?
/// Get the next fixture in the parent body's fixture list.
/// @return the next shape.
val next: IFixture?
/// Get the user data that was assigned in the fixture definition. Use this to
/// store your application specific data.
val userData: Any?
/// Test a point for containment in this fixture.
/// @param p a point in world coordinates.
fun testPoint(p: Vector2d): Boolean {
return shape.testPoint(checkNotNull(body) { "Tried to use detached fixture" }.transform, p)
}
/// Cast a ray against this shape.
/// @param output the ray-cast results.
/// @param input the ray-cast input parameters.
/// @param childIndex the child shape index (e.g. edge index)
fun rayCast(input: RayCastInput, childIndex: Int): RayCastOutput {
return shape.rayCast(input, checkNotNull(body) { "Tried to use detached fixture" }.transform, childIndex)
}
/// Get the mass data for this fixture. The mass data is based on the density and
/// the shape. The rotational inertia is about the shape's origin. This operation
/// may be expensive.
fun getMassData(): MassData {
return shape.computeMass(density)
}
/// Set the density of this fixture. This will _not_ automatically adjust the mass
/// of the body. You must call b2Body::ResetMassData to update the body's mass.
/// Get the density of this fixture.
var density: Double
/// Get the coefficient of friction.
/// Set the coefficient of friction. This will _not_ change the friction of
/// existing contacts.
var friction: Double
/// Get the coefficient of restitution.
/// Set the coefficient of restitution. This will _not_ change the restitution of
/// existing contacts.
var restitution: Double
/// Get the restitution velocity threshold.
/// Set the restitution threshold. This will _not_ change the restitution threshold of
/// existing contacts.
var restitutionThreshold: Double
/// Get the fixture's AABB. This AABB may be enlarge and/or stale.
/// If you need a more accurate AABB, compute it using the shape and
/// the body transform.
fun getAABB(childIndex: Int): AABB {
return proxies[childIndex].aabb
}
/// Dump this fixture to the log file. (Log4J)
fun dump(childIndex: Int) { }
val proxies: List<FixtureProxy>
}

View File

@ -2,6 +2,8 @@ package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kbox2d.collision.DynamicTree
import ru.dbotthepony.kbox2d.collision.BroadPhase
fun interface ProxyQueryCallback {
fun invoke(nodeId: Int, userData: Any?): Boolean
@ -11,33 +13,33 @@ fun interface ProxyRayCastCallback {
fun invoke(subInput: RayCastInput, nodeId: Int, userData: Any?): Double
}
sealed interface IProxieable {
interface IProxieable {
/**
* For [IBroadPhase]:
* For [BroadPhase]:
* Create a proxy with an initial AABB. Pairs are not reported until
* UpdatePairs is called.
*
* For [IDynamicTree]:
* For [DynamicTree]:
* Create a proxy. Provide a tight fitting AABB and a userData pointer.
*/
fun createProxy(aabb: AABB, userData: Any?): Int
/**
* For [IBroadPhase]:
* For [BroadPhase]:
* Destroy a proxy. It is up to the client to remove any pairs.
*
* For [IDynamicTree]:
* For [DynamicTree]:
* Destroy a proxy. This asserts if the id is invalid.
*/
fun destroyProxy(proxyID: Int)
/**
* For [IBroadPhase]:
* For [BroadPhase]:
* Call MoveProxy as many times as you like, then when you are done
* call UpdatePairs to finalized the proxy pairs (for your time step).
* @return true
*
* For [IDynamicTree]:
* For [DynamicTree]:
* Move a proxy with a swepted AABB. If the proxy has moved outside of its fattened AABB,
* then the proxy is removed from the tree and re-inserted. Otherwise
* the function returns immediately.

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.joint.AbstractJoint
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import kotlin.math.PI
@ -12,8 +13,8 @@ data class StiffnessResult(val stiffness: Double, val damping: Double)
fun b2LinearStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody?,
bodyB: IBody?,
bodyA: Body?,
bodyB: Body?,
): StiffnessResult {
val massA = bodyA?.mass ?: 0.0
val massB = bodyB?.mass ?: 0.0
@ -41,8 +42,8 @@ fun b2LinearStiffness(
fun b2AngularStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody?,
bodyB: IBody?,
bodyA: Body?,
bodyB: Body?,
): StiffnessResult {
val inertiaA = bodyA?.inertia ?: 0.0
val inertiaB = bodyB?.inertia ?: 0.0
@ -91,13 +92,13 @@ data class Jacobian(
* nodes, one for each attached body.
*/
class JointEdge(
other: IBody?, ///< provides quick access to the other body attached.
val joint: IJoint, ///< the joint
other: Body?, ///< provides quick access to the other body attached.
val joint: AbstractJoint, ///< the joint
var prev: JointEdge? = null, ///< the previous joint edge in the body's joint list
var next: JointEdge? = null ///< the next joint edge in the body's joint list
) {
val otherNullable: IBody? = other
val other: IBody get() = checkNotNull(otherNullable) { "Other body is not present" }
val otherNullable: Body? = other
val other: Body get() = checkNotNull(otherNullable) { "Other body is not present" }
}
sealed interface IJointDef {
@ -109,12 +110,12 @@ sealed interface IJointDef {
/**
* The first attached body.
*/
val bodyA: IBody?
val bodyA: Body?
/**
* The second attached body.
*/
val bodyB: IBody?
val bodyB: Body?
/**
* Set this flag to true if the attached bodies should collide.
@ -128,8 +129,8 @@ sealed interface IJointDef {
}
class DistanceJointDef(
b1: IBody,
b2: IBody,
b1: Body,
b2: Body,
anchor1: Vector2d,
anchor2: Vector2d
) : IJointDef {
@ -160,8 +161,8 @@ class DistanceJointDef(
*/
val damping: Double = 0.0
override var bodyA: IBody = b1
override var bodyB: IBody = b2
override var bodyA: Body = b1
override var bodyB: Body = b2
override var collideConnected: Boolean = false
override var userData: Any? = null
@ -185,13 +186,13 @@ class DistanceJointDef(
}
class RevoluteJointDef(
b1: IBody,
b2: IBody,
b1: Body,
b2: Body,
anchor: Vector2d,
) : IJointDef {
override val type: JointType = JointType.REVOLUTE
override var bodyA: IBody = b1
override var bodyB: IBody = b2
override var bodyA: Body = b1
override var bodyB: Body = b2
override var collideConnected: Boolean = false
override var userData: Any? = null
@ -244,14 +245,14 @@ class RevoluteJointDef(
}
class PrismaticJointDef(
b1: IBody,
b2: IBody,
b1: Body,
b2: Body,
anchor: Vector2d,
axis: Vector2d,
) : IJointDef {
override val type: JointType = JointType.PRISMATIC
override var bodyA: IBody = b1
override var bodyB: IBody = b2
override var bodyA: Body = b1
override var bodyB: Body = b2
override var collideConnected: Boolean = false
override var userData: Any? = null
@ -311,8 +312,8 @@ class PrismaticJointDef(
* two dynamic body anchor points, and a pulley ratio.
*/
class PulleyJointDef(
b1: IBody,
b2: IBody,
b1: Body,
b2: Body,
/**
* The first ground anchor in world coordinates. This point never moves.
@ -329,8 +330,8 @@ class PulleyJointDef(
ratio: Double,
) : IJointDef {
override val type: JointType = JointType.PULLEY
override var bodyA: IBody = b1
override var bodyB: IBody = b2
override var bodyA: Body = b1
override var bodyB: Body = b2
override var collideConnected: Boolean = false
override var userData: Any? = null
@ -375,8 +376,8 @@ class PulleyJointDef(
* @warning bodyB on the input joints must both be dynamic
*/
class GearJointDef(
override var bodyA: IBody,
override var bodyB: IBody,
override var bodyA: Body,
override var bodyB: Body,
/**
* The first revolute/prismatic joint attached to the gear joint.
@ -416,8 +417,8 @@ class MouseJointDef(
*/
var target: Vector2d,
override var bodyB: IBody,
override val bodyA: IBody? = null,
override var bodyB: Body,
override val bodyA: Body? = null,
/**
* The maximum constraint force that can be exerted
@ -436,8 +437,8 @@ class MouseJointDef(
fun linearStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody?,
bodyB: IBody?
bodyA: Body?,
bodyB: Body?
): MouseJointDef {
val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness
@ -448,8 +449,8 @@ class MouseJointDef(
fun angularStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody?,
bodyB: IBody?
bodyA: Body?,
bodyB: Body?
): MouseJointDef {
val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness
@ -467,8 +468,8 @@ class MouseJointDef(
* anchors and a local axis helps when saving and loading a game.
*/
class WheelJointDef(
override var bodyA: IBody,
override var bodyB: IBody,
override var bodyA: Body,
override var bodyB: Body,
anchor: Vector2d,
axis: Vector2d,
@ -534,8 +535,8 @@ class WheelJointDef(
fun linearStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody = this.bodyA,
bodyB: IBody = this.bodyB
bodyA: Body = this.bodyA,
bodyB: Body = this.bodyB
): WheelJointDef {
val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness
@ -546,8 +547,8 @@ class WheelJointDef(
fun angularStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody = this.bodyA,
bodyB: IBody = this.bodyB
bodyA: Body = this.bodyA,
bodyB: Body = this.bodyB
): WheelJointDef {
val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness
@ -562,8 +563,8 @@ class WheelJointDef(
* of the anchor points is important for computing the reaction torque.
*/
class WeldJointDef(
override var bodyA: IBody,
override var bodyB: IBody,
override var bodyA: Body,
override var bodyB: Body,
anchor: Vector2d,
/**
@ -598,8 +599,8 @@ class WeldJointDef(
fun linearStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody = this.bodyA,
bodyB: IBody = this.bodyB
bodyA: Body = this.bodyA,
bodyB: Body = this.bodyB
): WeldJointDef {
val (stiffness, damping) = b2LinearStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness
@ -610,8 +611,8 @@ class WeldJointDef(
fun angularStiffness(
frequencyHertz: Double,
dampingRatio: Double,
bodyA: IBody = this.bodyA,
bodyB: IBody = this.bodyB
bodyA: Body = this.bodyA,
bodyB: Body = this.bodyB
): WeldJointDef {
val (stiffness, damping) = b2AngularStiffness(frequencyHertz, dampingRatio, bodyA, bodyB)
this.stiffness = stiffness
@ -624,8 +625,8 @@ class WeldJointDef(
* Friction joint definition.
*/
class FrictionJointDef(
override var bodyA: IBody,
override var bodyB: IBody,
override var bodyA: Body,
override var bodyB: Body,
anchor: Vector2d,
/**
@ -655,8 +656,8 @@ class FrictionJointDef(
}
class MotorJointDef(
override var bodyA: IBody,
override var bodyB: IBody,
override var bodyA: Body,
override var bodyB: Body,
/**
* The maximum motor force in N.
@ -692,77 +693,3 @@ class MotorJointDef(
field = value
}
}
interface IJoint : IMovable {
/**
* Get the type of the concrete joint.
*/
val type: JointType
/**
* Get the first body attached to this joint.
*/
val bodyA: IBody
/**
* Get the second body attached to this joint.
*/
val bodyB: IBody
/**
* Get the anchor point on bodyA in world coordinates.
*/
val anchorA: Vector2d
/**
* Get the anchor point on bodyB in world coordinates.
*/
val anchorB: Vector2d
/**
* Get the reaction force on bodyB at the joint anchor in Newtons.
*/
fun getReactionForce(inv_dt: Double): Vector2d
/**
* Get the reaction torque on bodyB in N*m.
*/
fun getReactionTorque(inv_dt: Double): Double
/**
* Get the next joint the world joint list.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
val next: IJoint?
val prev: IJoint?
fun destroy() {
bodyA.world.destroyJoint(this)
}
/**
* Short-cut function to determine if either body is enabled.
*/
val isEnabled: Boolean get() = bodyA.isEnabled || bodyB.isEnabled
/**
* Get collide connected.
* Note: modifying the collide connect flag won't work correctly because
* the flag is only checked when fixture AABBs begin to overlap.
*/
val collideConnected: Boolean
/**
* Dump this joint to the log file.
*/
fun dump() { }
/**
* Debug draw this joint
*/
fun draw(draw: IDebugDraw) { }
var userData: Any?
}

View File

@ -9,9 +9,12 @@ data class MassData(
val inertia: Double,
)
/// A shape is used for collision detection. You can create a shape however you like.
/// Shapes used for simulation in b2World are created automatically when a b2Fixture
/// is created. Shapes may encapsulate a one or more child shapes.
/**
* A shape is used for collision detection. You can create a shape however you like.
* Shapes used for simulation in b2World are created automatically when a b2Fixture
* is created. Shapes may encapsulate a one or more child shapes.
*/
interface IShape<S : IShape<S>> {
enum class Type {
CIRCLE,
@ -20,41 +23,58 @@ interface IShape<S : IShape<S>> {
CHAIN,
}
/// Clone the concrete shape.
/**
* Clone the concrete shape.
*/
fun copy(): S
/// Get the type of this shape. You can use this to down cast to the concrete shape.
/// @return the shape type.
/**
* Get the type of this shape. You can use this to down cast to the concrete shape.
* @return the shape type.
*/
val type: Type
/// Get the number of child primitives.
/**
* Get the number of child primitives.
*/
val childCount: Int
/// Test a point for containment in this shape. This only works for convex shapes.
/// @param xf the shape world transform.
/// @param p a point in world coordinates.
/**
* Test a point for containment in this shape. This only works for convex shapes.
* @param xf the shape world transform.
* @param p a point in world coordinates.
*/
fun testPoint(transform: Transform, p: Vector2d): Boolean { return false }
/// Cast a ray against a child shape.
/// @param output the ray-cast results.
/// @param input the ray-cast input parameters.
/// @param transform the transform to be applied to the shape.
/// @param childIndex the child shape index
/**
* Cast a ray against a child shape.
* @param output the ray-cast results.
* @param input the ray-cast input parameters.
* @param transform the transform to be applied to the shape.
* @param childIndex the child shape index
*/
fun rayCast(input: RayCastInput, transform: Transform, childIndex: Int): RayCastOutput
/// Given a transform, compute the associated axis aligned bounding box for a child shape.
/// @param aabb returns the axis aligned box.
/// @param xf the world transform of the shape.
/// @param childIndex the child shape
/**
* Given a transform, compute the associated axis aligned bounding box for a child shape.
* @param aabb returns the axis aligned box.
* @param xf the world transform of the shape.
* @param childIndex the child shape
*/
fun computeAABB(transform: Transform, childIndex: Int): AABB
/// Compute the mass properties of this shape using its dimensions and density.
/// The inertia tensor is computed about the local origin.
/// @param massData returns the mass data for this shape.
/// @param density the density in kilograms per meter squared.
/**
* Compute the mass properties of this shape using its dimensions and density.
* The inertia tensor is computed about the local origin.
* @param massData returns the mass data for this shape.
* @param density the density in kilograms per meter squared.
*/
fun computeMass(density: Double): MassData
/// Radius of a shape. For polygonal shapes this must be b2_polygonRadius. There is no support for
/// making rounded polygons.
/**
* Radius of a shape. For polygonal shapes this must be b2_polygonRadius. There is no support for
* making rounded polygons.
*/
var radius: Double
}

View File

@ -1,2 +0,0 @@
package ru.dbotthepony.kbox2d.api

View File

@ -1,11 +1,13 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.collision.DistanceProxy
/**
* Input parameters for b2TimeOfImpact
*/
data class TOIInput(
var proxyA: IDistanceProxy,
var proxyB: IDistanceProxy,
var proxyA: DistanceProxy,
var proxyB: DistanceProxy,
var sweepA: Sweep,
var sweepB: Sweep,
var tMax: Double, // defines sweep interval [0, tMax]

View File

@ -1,245 +0,0 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
/**
* The world class manages all physics entities, dynamic simulation,
* and asynchronous queries. The world also contains efficient memory
* management facilities.
*/
interface IB2World : IMovable {
/**
* Register a destruction listener. The listener is owned by you and must
* remain in scope.
*/
var destructionListener: IDestructionListener?
/**
* Register a contact filter to provide specific control over collision.
* Otherwise the default filter is used (b2_defaultFilter). The listener is
* owned by you and must remain in scope.
*/
var contactFilter: IContactFilter?
/**
* Register a contact event listener. The listener is owned by you and must
* remain in scope.
*/
var contactListener: IContactListener?
/**
* Register a routine for debug drawing. The debug draw functions are called
* inside with b2World::DebugDraw method. The debug draw object is owned
* by you and must remain in scope.
*/
var debugDraw: IDebugDraw?
/**
* Create a rigid body given a definition. No reference to the definition
* is retained.
* @warning This function is locked during callbacks.
*/
fun createBody(bodyDef: ru.dbotthepony.kbox2d.api.BodyDef): ru.dbotthepony.kbox2d.api.IBody
/**
* Destroy a rigid body given a definition. No reference to the definition
* is retained. This function is locked during callbacks.
* @warning This automatically deletes all associated shapes and joints.
* @warning This function is locked during callbacks.
*/
fun destroyBody(body: ru.dbotthepony.kbox2d.api.IBody)
/**
* Create a joint to constrain bodies together. No reference to the definition
* is retained. This may cause the connected bodies to cease colliding.
* @warning This function is locked during callbacks.
*/
fun createJoint(jointDef: IJointDef): IJoint
/**
* Destroy a joint. This may cause the connected bodies to begin colliding.
* @warning This function is locked during callbacks.
*/
fun destroyJoint(joint: IJoint)
/**
* Take a time step. This performs collision detection, integration,
* and constraint solution.
* @param dt the amount of time to simulate, this should not vary.
* @param velocityIterations for the velocity constraint solver.
* @param positionIterations for the position constraint solver.
*/
fun step(dt: Double, velocityIterations: Int, positionIterations: Int)
/**
* Manually clear the force buffer on all bodies. By default, forces are cleared automatically
* after each call to Step. The default behavior is modified by calling SetAutoClearForces.
* The purpose of this function is to support sub-stepping. Sub-stepping is often used to maintain
* a fixed sized time step under a variable frame-rate.
* When you perform sub-stepping you will disable auto clearing of forces and instead call
* ClearForces after all sub-steps are complete in one pass of your game loop.
* @see SetAutoClearForces
*/
fun clearForces()
/**
* Call this to draw shapes and other debug draw data. This is intentionally non-const.
*/
fun debugDraw()
/**
* Query the world for all fixtures that potentially overlap the
* provided AABB.
* @param callback a user implemented callback class.
* @param aabb the query box.
*/
fun queryAABB(aabb: AABB, callback: IQueryCallback)
/**
* Ray-cast the world for all fixtures in the path of the ray. Your callback
* controls whether you get the closest point, any point, or n-points.
* The ray-cast ignores shapes that contain the starting point.
* @param callback a user implemented callback class.
* @param point1 the ray starting point
* @param point2 the ray ending point
*/
fun rayCast(point1: Vector2d, point2: Vector2d, callback: IRayCastCallback)
/**
* Get the world body list. With the returned body, use b2Body::GetNext to get
* the next body in the world list. A nullptr body indicates the end of the list.
* @return the head of the world body list.
*/
val bodyList: ru.dbotthepony.kbox2d.api.IBody?
val bodyListIterator: Iterator<ru.dbotthepony.kbox2d.api.IBody> get() {
return object : Iterator<ru.dbotthepony.kbox2d.api.IBody> {
private var node = bodyList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): ru.dbotthepony.kbox2d.api.IBody {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Get the world joint list. With the returned joint, use b2Joint::GetNext to get
* the next joint in the world list. A nullptr joint indicates the end of the list.
* @return the head of the world joint list.
*/
val jointList: IJoint?
val jointListIterator: Iterator<IJoint> get() {
return object : Iterator<IJoint> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IJoint {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Get the world contact list. With the returned contact, use b2Contact::GetNext to get
* the next contact in the world list. A nullptr contact indicates the end of the list.
* @return the head of the world contact list.
* @warning contacts are created and destroyed in the middle of a time step.
* Use b2ContactListener to avoid missing contacts.
*/
val contactList: IContact? get() = contactManager.contactList
val contactListIterator: Iterator<IContact> get() {
return object : Iterator<IContact> {
private var node = contactList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): IContact {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Enable/disable sleep.
*/
var allowAutoSleep: Boolean
/**
* Enable/disable warm starting. For testing.
*/
var warmStarting: Boolean
/**
* Enable/disable continuous physics. For testing.
*/
var continuousPhysics: Boolean
/**
* Enable/disable single stepped continuous physics. For testing.
*/
var enableSubStepping: Boolean
val proxyCount: Int get() = contactManager.broadPhase.proxyCount
val bodyCount: Int
val jointCount: Int
val contactCount: Int get() = contactManager.contactCount
val treeHeight: Int get() = contactManager.broadPhase.treeHeight
val treeBalance: Int get() = contactManager.broadPhase.treeBalance
/**
* Get the quality metric of the dynamic tree. The smaller the better.
* The minimum is 1.
*/
val treeQuality: Double get() = contactManager.broadPhase.treeQuality
/**
* Change the global gravity vector.
*/
var gravity: Vector2d
/**
* Is the world locked (in the middle of a time step).
*/
val isLocked: Boolean
/**
* Set flag to control automatic clearing of forces after each time step.
*/
var autoClearForces: Boolean
/**
* Get the current profile.
*/
val profileData: ru.dbotthepony.kbox2d.api.IProfileData
/**
* Dump the world into the log file.
* @warning this should be called outside of a time step.
*/
fun dump()
val contactManager: IContactManager
fun notifyNewContacts()
}

View File

@ -1,5 +1,8 @@
package ru.dbotthepony.kbox2d.api
import ru.dbotthepony.kbox2d.dynamics.Fixture
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
import ru.dbotthepony.kbox2d.dynamics.joint.AbstractJoint
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
/**
@ -11,7 +14,7 @@ interface IContactFilter {
* Return true if contact calculations should be performed between these two shapes.
* @warning for performance reasons this is only called when the AABBs begin to overlap.
*/
fun shouldCollide(fixtureA: IFixture, fixtureB: IFixture): Boolean
fun shouldCollide(fixtureA: Fixture, fixtureB: Fixture): Boolean
}
/**
@ -24,13 +27,13 @@ interface IDestructionListener {
* Called when any joint is about to be destroyed due
* to the destruction of one of its attached bodies.
*/
fun sayGoodbye(joint: IJoint)
fun sayGoodbye(joint: AbstractJoint)
/**
* Called when any fixture is about to be destroyed due
* to the destruction of its parent body.
*/
fun sayGoodbye(fixture: IFixture)
fun sayGoodbye(fixture: Fixture)
}
/**
@ -60,12 +63,12 @@ interface IContactListener {
/**
* Called when two fixtures begin to touch.
*/
fun beginContact(contact: IContact)
fun beginContact(contact: AbstractContact)
/**
* Called when two fixtures cease to touch.
*/
fun endContact(contact: IContact)
fun endContact(contact: AbstractContact)
/**
* This is called after a contact is updated. This allows you to inspect a
@ -84,7 +87,7 @@ interface IContactListener {
* get an EndContact callback. However, you may get a BeginContact callback
* the next step.
*/
fun preSolve(contact: IContact, oldManifold: Manifold)
fun preSolve(contact: AbstractContact, oldManifold: Manifold)
/**
* This lets you inspect a contact after the solver is finished. This is useful
@ -96,7 +99,7 @@ interface IContactListener {
*
* Note: this is only called for contacts that are touching, solid, and awake.
*/
fun postSolve(contact: IContact, impulse: ContactImpulse)
fun postSolve(contact: AbstractContact, impulse: ContactImpulse)
}
/**
@ -108,7 +111,7 @@ fun interface IQueryCallback {
* Called for each fixture found in the query AABB.
* @return false to terminate the query.
*/
fun reportFixture(fixture: IFixture): Boolean
fun reportFixture(fixture: Fixture): Boolean
}
/**
@ -135,5 +138,5 @@ fun interface IRayCastCallback {
* @return -1 to filter, 0 to terminate, fraction to clip the ray for
* closest hit, 1 to continue
*/
fun reportFixture(fixture: IFixture, point: Vector2d, normal: Vector2d, fraction: Double): Double
fun reportFixture(fixture: Fixture, point: Vector2d, normal: Vector2d, fraction: Double): Double
}

View File

@ -4,6 +4,10 @@ import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
typealias b2Pair = Pair<Int, Int>
const val e_nullProxy = -1
private class IntArrayList {
private var buffer = IntArray(16)
var size: Int = 0
@ -33,17 +37,28 @@ private class IntArrayList {
}
}
class BroadPhase : IBroadPhase {
/**
* The broad-phase is used for computing pairs and performing volume queries and ray casts.
* This broad-phase does not persist pairs. Instead, this reports potentially new pairs.
* It is up to the client to consume the new pairs and to track subsequent overlap.
*/
class BroadPhase : IProxieable, IMovable {
private val moveBuffer = IntArrayList()
private val pairBuffer = ArrayList<b2Pair>()
private var moveCount = 0
private val tree = DynamicTree()
override var proxyCount: Int = 0
/**
* Get the number of proxies.
*/
var proxyCount: Int = 0
private set
override fun touchProxy(proxyID: Int) {
/**
* Call to trigger a re-processing of it's pairs on the next call to UpdatePairs.
*/
fun touchProxy(proxyID: Int) {
bufferMove(proxyID)
}
@ -107,16 +122,28 @@ class BroadPhase : IBroadPhase {
return tree.getFatAABB(proxyID)
}
override val treeHeight: Int
/**
* Get the height of the embedded tree.
*/
val treeHeight: Int
get() = tree.height
override val treeBalance: Int
/**
* Get the balance of the embedded tree.
*/
val treeBalance: Int
get() = tree.maxBalance
override val treeQuality: Double
/**
* Get the quality metric of the embedded tree.
*/
val treeQuality: Double
get() = tree.getAreaRatio
override fun testOverlap(proxyIDA: Int, proxyIDB: Int): Boolean {
/**
* Test overlap of fat AABBs.
*/
fun testOverlap(proxyIDA: Int, proxyIDB: Int): Boolean {
return tree.getFatAABB(proxyIDA).intersect(tree.getFatAABB(proxyIDB))
}
@ -143,7 +170,10 @@ class BroadPhase : IBroadPhase {
return true
}
override fun updatePairs(callback: (Any?, Any?) -> Unit) {
/**
* Update the pairs. This results in pair callbacks. This can only add pairs.
*/
fun updatePairs(callback: (Any?, Any?) -> Unit) {
pairBuffer.clear()
for (i in 0 until moveCount) {

View File

@ -17,9 +17,13 @@ var b2_gjkIters = 0
var b2_gjkMaxIters = 0
private set
class DistanceProxy : IDistanceProxy {
override val vertices: List<Vector2d>
override val radius: Double
/**
* A distance proxy is used by the GJK algorithm.
* It encapsulates any shape.
*/
class DistanceProxy {
val vertices: List<Vector2d>
val radius: Double
constructor(shape: IShape<*>, index: Int) {
when (shape.type) {
@ -63,7 +67,10 @@ class DistanceProxy : IDistanceProxy {
this.radius = radius
}
override fun getSupport(d: Vector2d): Int {
/**
* Get the supporting vertex index in the given direction.
*/
fun getSupport(d: Vector2d): Int {
var bestIndex = 0
var bestValue = b2Dot(vertices[0], d)
@ -79,7 +86,10 @@ class DistanceProxy : IDistanceProxy {
return bestIndex
}
override fun getSupportVertex(d: Vector2d): Vector2d {
/**
* Get the supporting vertex in the given direction.
*/
fun getSupportVertex(d: Vector2d): Vector2d {
return vertices[getSupport(d)]
}
}
@ -116,9 +126,9 @@ class Simplex() {
constructor(
cache: SimplexCache,
proxyA: IDistanceProxy,
proxyA: DistanceProxy,
transformA: Transform,
proxyB: IDistanceProxy,
proxyB: DistanceProxy,
transformB: Transform,
) : this() {
check(cache.count <= 3)
@ -413,8 +423,8 @@ private const val k_maxIters = 20
*/
fun b2Distance(
cache: SimplexCache,
proxyA: IDistanceProxy,
proxyB: IDistanceProxy,
proxyA: DistanceProxy,
proxyB: DistanceProxy,
transformA: Transform,
transformB: Transform,
useRadii: Boolean = false
@ -553,8 +563,8 @@ private const val gjk_tolerance = 0.5 * b2_linearSlop
*/
fun b2ShapeCast(
output: ShapeCastOutput,
proxyA: IDistanceProxy,
proxyB: IDistanceProxy,
proxyA: DistanceProxy,
proxyB: DistanceProxy,
xfA: Transform,
xfB: Transform,
r: Vector2d,

View File

@ -5,9 +5,36 @@ import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import kotlin.math.absoluteValue
/**
* A node in the dynamic tree. The client does not interact with this directly.
*/
private data class TreeNode(
val tree: DynamicTree,
/// Enlarged AABB
var aabb: AABB? = null,
var child1: Int = 0,
var child2: Int = 0,
// leaf = 0, free node = -1
var height: Int = 0,
var moved: Boolean = false,
var userData: Any? = null,
) {
val isLeaf get() = child1 == b2_nullNode
// union
private var _union: Int = b2_nullNode
var parent by this::_union
var next by this::_union
}
const val b2_nullNode = -1
class DynamicTree : IDynamicTree {
class DynamicTree : IProxieable, IMovable {
private var root: Int = b2_nullNode
private val nodeCapacity get() = nodes.size
@ -15,7 +42,7 @@ class DynamicTree : IDynamicTree {
private var freeList = 0
private var insertionCount = 0
internal var nodes = Array(16) { TreeNode(this) }
private var nodes = Array(16) { TreeNode(this) }
init {
// Build a linked list for the free list.
@ -162,11 +189,11 @@ class DynamicTree : IDynamicTree {
return nodes[proxyID].userData
}
override fun wasMoved(proxyID: Int): Boolean {
fun wasMoved(proxyID: Int): Boolean {
return nodes[proxyID].moved
}
override fun clearMoved(proxyID: Int) {
fun clearMoved(proxyID: Int) {
nodes[proxyID].moved = false
}
@ -476,9 +503,16 @@ class DynamicTree : IDynamicTree {
return iA
}
override val height: Int get() = if (root == b2_nullNode) 0 else nodes[root].height
/**
* Compute the height of the binary tree in O(N) time. Should not be
* called often.
*/
val height: Int get() = if (root == b2_nullNode) 0 else nodes[root].height
override val getAreaRatio: Double get() {
/**
* Get the ratio of the sum of the node areas to the root area.
*/
val getAreaRatio: Double get() {
if (root == b2_nullNode)
return 0.0
@ -562,7 +596,10 @@ class DynamicTree : IDynamicTree {
validateMetrics(child2)
}
override fun validate() {
/**
* Validate this tree. For testing.
*/
fun validate() {
validateStructure(root)
validateMetrics(root)
@ -578,7 +615,11 @@ class DynamicTree : IDynamicTree {
check(nodeCount + freeCount == nodeCapacity)
}
override val maxBalance: Int get() {
/**
* Get the maximum balance of an node in the tree. The balance is the difference
* in height of the two children of a node.
*/
val maxBalance: Int get() {
var maxBalance = 0
for (node in nodes) {
@ -596,7 +637,10 @@ class DynamicTree : IDynamicTree {
return maxBalance
}
override fun rebuildBottomUp() {
/**
* Build an optimal tree. Very expensive. For testing.
*/
fun rebuildBottomUp() {
TODO("Not Yet Implemented")
}

View File

@ -34,8 +34,8 @@ private data class MinSeparationResult(
private class SeparationFunction(
cache: SimplexCache,
val proxyA: IDistanceProxy,
val proxyB: IDistanceProxy,
val proxyA: DistanceProxy,
val proxyB: DistanceProxy,
val sweepA: Sweep,
val sweepB: Sweep,
t1: Double
@ -228,8 +228,8 @@ const val k_maxIterations = 20
* Note: use [b2Distance] to compute the contact point and normal at the time of impact.
*/
fun b2TimeOfImpact(
proxyA: IDistanceProxy,
proxyB: IDistanceProxy,
proxyA: DistanceProxy,
proxyB: DistanceProxy,
_sweepA: Sweep,
_sweepB: Sweep,
tMax: Double, // defines sweep interval [0, tMax]

View File

@ -1,3 +1,6 @@
@file:Suppress("unused")
package ru.dbotthepony.kbox2d.dynamics
import ru.dbotthepony.kbox2d.api.*
@ -15,30 +18,141 @@ import ru.dbotthepony.kvector.vector.Color
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import ru.dbotthepony.kvector.vector.ndouble.times
class B2World(override var gravity: Vector2d) : IB2World {
override var bodyCount: Int = 0
/**
* The world class manages all physics entities, dynamic simulation,
* and asynchronous queries. The world also contains efficient memory
* management facilities.
*/
class B2World(
/**
* Change the global gravity vector.
*/
var gravity: Vector2d
) : IMovable {
var bodyCount: Int = 0
private set
override var jointCount: Int = 0
var jointCount: Int = 0
private set
override val contactManager: IContactManager = ContactManager()
val contactManager = ContactManager()
override var destructionListener: IDestructionListener? = null
override var contactFilter: IContactFilter? by contactManager::contactFilter
override var contactListener: IContactListener? by contactManager::contactListener
/**
* Get the world contact list. With the returned contact, use b2Contact::GetNext to get
* the next contact in the world list. A nullptr contact indicates the end of the list.
* @return the head of the world contact list.
* @warning contacts are created and destroyed in the middle of a time step.
* Use b2ContactListener to avoid missing contacts.
*/
val contactList: AbstractContact? get() = contactManager.contactList
val contactListIterator: Iterator<AbstractContact> get() = contactManager.contactListIterator
override var debugDraw: IDebugDraw? = null
/**
* Register a destruction listener. The listener is owned by you and must
* remain in scope.
*/
var destructionListener: IDestructionListener? = null
override var bodyList: IBody? = null
private set
override var jointList: IJoint? = null
/**
* Register a contact filter to provide specific control over collision.
* Otherwise the default filter is used (b2_defaultFilter). The listener is
* owned by you and must remain in scope.
*/
var contactFilter: IContactFilter? by contactManager::contactFilter
/**
* Register a contact event listener. The listener is owned by you and must
* remain in scope.
*/
var contactListener: IContactListener? by contactManager::contactListener
/**
* Register a routine for debug drawing. The debug draw functions are called
* inside with b2World::DebugDraw method. The debug draw object is owned
* by you and must remain in scope.
*/
var debugDraw: IDebugDraw? = null
/**
* Get the world body list. With the returned body, use b2Body::GetNext to get
* the next body in the world list. A nullptr body indicates the end of the list.
* @return the head of the world body list.
*/
var bodyList: Body? = null
private set
override var warmStarting: Boolean = true
override var continuousPhysics: Boolean = true
override var enableSubStepping: Boolean = false
/**
* Get the world joint list. With the returned joint, use b2Joint::GetNext to get
* the next joint in the world list. A nullptr joint indicates the end of the list.
* @return the head of the world joint list.
*/
var jointList: AbstractJoint? = null
private set
override var allowAutoSleep: Boolean = true
val bodyListIterator: Iterator<Body> get() {
return object : Iterator<Body> {
private var node = bodyList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): Body {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
val jointListIterator: Iterator<AbstractJoint> get() {
return object : Iterator<AbstractJoint> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): AbstractJoint {
val old = node!!
node = old.next
check(node != old) { "Hard loop detected at $old" }
return old
}
}
}
/**
* Enable/disable warm starting. For testing.
*/
var warmStarting: Boolean = true
/**
* Enable/disable continuous physics. For testing.
*/
var continuousPhysics: Boolean = true
/**
* Enable/disable single stepped continuous physics. For testing.
*/
var enableSubStepping: Boolean = false
/**
* Get the quality metric of the dynamic tree. The smaller the better.
* The minimum is 1.
*/
val treeQuality: Double get() = contactManager.broadPhase.treeQuality
val proxyCount: Int get() = contactManager.broadPhase.proxyCount
val contactCount: Int get() = contactManager.contactCount
val treeHeight: Int get() = contactManager.broadPhase.treeHeight
val treeBalance: Int get() = contactManager.broadPhase.treeBalance
/**
* Enable/disable sleep.
*/
var allowAutoSleep: Boolean = true
set(value) {
if (value == field)
return
@ -52,40 +166,57 @@ class B2World(override var gravity: Vector2d) : IB2World {
}
}
override var isLocked: Boolean = false
/**
* Is the world locked (in the middle of a time step).
*/
var isLocked: Boolean = false
private set
override var autoClearForces: Boolean = true
/**
* Set flag to control automatic clearing of forces after each time step.
*/
var autoClearForces: Boolean = true
private var stepComplete = true
private var newContacts = false
private val profile = ProfileData()
override fun notifyNewContacts() {
fun notifyNewContacts() {
newContacts = true
}
override fun createBody(bodyDef: BodyDef): IBody {
/**
* Create a rigid body given a definition. No reference to the definition
* is retained.
* @warning This function is locked during callbacks.
*/
fun createBody(bodyDef: BodyDef): Body {
if (isLocked)
throw ConcurrentModificationException()
val body = Body(bodyDef, this)
body.next = bodyList
(bodyList as Body?)?.prev = body
bodyList?.prev = body
bodyList = body
bodyCount++
return body
}
override fun destroyBody(body: IBody) {
/**
* Destroy a rigid body given a definition. No reference to the definition
* is retained. This function is locked during callbacks.
* @warning This automatically deletes all associated shapes and joints.
* @warning This function is locked during callbacks.
*/
fun destroyBody(body: Body) {
if (isLocked)
throw ConcurrentModificationException()
check(body.world == this) { "$body does not belong to $this" }
check(bodyCount > 0) { "I have ${bodyCount} bodies, can't remove one" }
check(bodyCount > 0) { "I have $bodyCount bodies, can't remove one" }
// Delete the attached joints.
for (jointEdge in body.jointIterator) {
@ -101,12 +232,12 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Delete the attached fixtures. This destroys broad-phase proxies.
for (fixture in body.fixtureIterator) {
destructionListener?.sayGoodbye(fixture)
(fixture as Fixture).destroyProxies(contactManager.broadPhase)
fixture.destroyProxies(contactManager.broadPhase)
}
// Remove world body list.
val prev = body.prev as Body?
val next = body.next as Body?
val prev = body.prev
val next = body.next
prev?.next = next
next?.prev = prev
@ -116,10 +247,15 @@ class B2World(override var gravity: Vector2d) : IB2World {
}
bodyCount--
(body as Body).unlink()
body.unlink()
}
override fun createJoint(jointDef: IJointDef): AbstractJoint {
/**
* Create a joint to constrain bodies together. No reference to the definition
* is retained. This may cause the connected bodies to cease colliding.
* @warning This function is locked during callbacks.
*/
fun createJoint(jointDef: IJointDef): AbstractJoint {
if (isLocked)
throw ConcurrentModificationException()
@ -127,7 +263,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Connect to the world list.
joint.next = jointList
(jointList as AbstractJoint?)?.prev = joint
jointList?.prev = joint
jointList = joint
jointCount++
@ -150,12 +286,15 @@ class B2World(override var gravity: Vector2d) : IB2World {
return joint
}
override fun destroyJoint(joint: IJoint) {
/**
* Destroy a joint. This may cause the connected bodies to begin colliding.
* @warning This function is locked during callbacks.
*/
fun destroyJoint(joint: AbstractJoint) {
if (isLocked)
throw ConcurrentModificationException()
check(jointCount > 0) { "No joints tracked to remove" }
joint as AbstractJoint
require((joint.nullableBodyA?.world == this || joint.nullableBodyA == null) && (joint.nullableBodyB?.world == this || joint.nullableBodyB == null)) { "$joint does not belong to $this" }
if (!joint.isValid) {
@ -164,8 +303,8 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Remove from the doubly linked list.
this.run {
val prev = joint.prev as AbstractJoint?
val next = joint.next as AbstractJoint?
val prev = joint.prev
val next = joint.next
prev?.next = next
next?.prev = prev
@ -241,25 +380,20 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Clear all the island flags.
for (body in bodyListIterator) {
body as Body
body.isOnIsland = false
}
for (contact in contactListIterator) {
contact as AbstractContact
contact.isOnIsland = false
}
for (joint in jointListIterator) {
joint as AbstractJoint
check(joint.isValid) { "$joint is no longer valid, but present in linked list" }
joint.isOnIsland = false
}
// Build and simulate all awake islands.
for (seed in bodyListIterator) {
seed as Body
if (seed.type == BodyType.STATIC || !seed.isAwake || !seed.isEnabled || seed.isOnIsland) {
continue
}
@ -287,7 +421,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Search all contacts connected to this body.
for (ce in body.contactEdgeIterator) {
val contact = ce.contact as AbstractContact
val contact = ce.contact
// Has this contact already been added to an island?
if (contact.isOnIsland)
@ -304,7 +438,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
island.add(contact)
contact.isOnIsland = true
val other = ce.other as Body
val other = ce.other
// Was the other body already added to this island?
if (!other.isOnIsland) {
@ -315,13 +449,13 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Search all joints connect to this body.
for (je in body.jointIterator) {
val joint = je.joint as AbstractJoint
val joint = je.joint
check(joint.isValid) { "$joint is no longer valid, but present in linked list of $body" }
if (joint.isOnIsland)
continue
val other = je.otherNullable as Body?
val other = je.otherNullable
// Don't simulate joints connected to disabled bodies.
if (other != null && !other.isEnabled)
@ -357,8 +491,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Synchronize fixtures, check for out of range bodies.
for (body in bodyListIterator) {
body as Body
// If a body was not in an island then it did not move.
if (!body.isOnIsland || body.type == BodyType.STATIC) {
continue
@ -381,14 +513,12 @@ class B2World(override var gravity: Vector2d) : IB2World {
if (stepComplete) {
for (body in bodyListIterator) {
body as Body
body.isOnIsland = false
body.sweep.alpha0 = 0.0
}
for (c in contactManager.contactListIterator) {
// Invalidate TOI
c as AbstractContact
c.isOnIsland = false
c.toiFlag = false
c.toiCount = 0
@ -406,13 +536,11 @@ class B2World(override var gravity: Vector2d) : IB2World {
continue
}
c as AbstractContact
if (c.toiCount > b2_maxSubSteps) {
continue
}
var alpha = 1.0
var alpha: Double
if (c.toiFlag) {
// This contact has a valid cached TOI.
@ -550,7 +678,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
if (body.type == BodyType.DYNAMIC) {
for (ce in body.contactEdgeIterator) {
val contact = ce.contact as AbstractContact
val contact = ce.contact
// Has this contact already been added to the island?
if (contact.isOnIsland) {
@ -569,8 +697,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
continue
}
other as Body
// Tentatively advance the body to the TOI.
val backup = other.sweep
if (!other.isOnIsland) {
@ -639,7 +765,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
// Invalidate all contact TOIs on this displaced body.
for (ce in body.contactEdgeIterator) {
val contact = ce.contact as AbstractContact
val contact = ce.contact
contact.toiFlag = false
contact.isOnIsland = false
}
@ -658,8 +784,15 @@ class B2World(override var gravity: Vector2d) : IB2World {
private var m_inv_dt0 = 0.0
override fun step(dt: Double, velocityIterations: Int, positionIterations: Int) {
var stepTimer = System.nanoTime()
/**
* Take a time step. This performs collision detection, integration,
* and constraint solution.
* @param dt the amount of time to simulate, this should not vary.
* @param velocityIterations for the velocity constraint solver.
* @param positionIterations for the position constraint solver.
*/
fun step(dt: Double, velocityIterations: Int, positionIterations: Int) {
val stepTimer = System.nanoTime()
// If new fixtures were added, we need to find the new contacts.
if (newContacts) {
@ -724,19 +857,41 @@ class B2World(override var gravity: Vector2d) : IB2World {
profile.step = System.nanoTime() - stepTimer
}
override fun clearForces() {
/**
* Manually clear the force buffer on all bodies. By default, forces are cleared automatically
* after each call to Step. The default behavior is modified by calling SetAutoClearForces.
* The purpose of this function is to support sub-stepping. Sub-stepping is often used to maintain
* a fixed sized time step under a variable frame-rate.
* When you perform sub-stepping you will disable auto clearing of forces and instead call
* ClearForces after all sub-steps are complete in one pass of your game loop.
* @see autoClearForces
*/
fun clearForces() {
for (body in bodyListIterator) {
body as Body
body.force = Vector2d.ZERO
body.torque = 0.0
}
}
override fun queryAABB(aabb: AABB, callback: IQueryCallback) {
/**
* Query the world for all fixtures that potentially overlap the
* provided AABB.
* @param callback a user implemented callback class.
* @param aabb the query box.
*/
fun queryAABB(aabb: AABB, callback: IQueryCallback) {
contactManager.broadPhase.query(aabb) { nodeId, userData -> callback.reportFixture((userData as FixtureProxy).fixture) }
}
override fun rayCast(point1: Vector2d, point2: Vector2d, callback: IRayCastCallback) {
/**
* Ray-cast the world for all fixtures in the path of the ray. Your callback
* controls whether you get the closest point, any point, or n-points.
* The ray-cast ignores shapes that contain the starting point.
* @param callback a user implemented callback class.
* @param point1 the ray starting point
* @param point2 the ray ending point
*/
fun rayCast(point1: Vector2d, point2: Vector2d, callback: IRayCastCallback) {
val input = RayCastInput(point1, point2, 1.0)
contactManager.broadPhase.rayCast(input, object : ProxyRayCastCallback {
@ -756,7 +911,7 @@ class B2World(override var gravity: Vector2d) : IB2World {
})
}
private fun drawShape(fixture: IFixture, xf: Transform, color: Color) {
private fun drawShape(fixture: Fixture, xf: Transform, color: Color) {
when (fixture.type) {
IShape.Type.CIRCLE -> {
val circle = fixture.shape as CircleShape
@ -799,7 +954,10 @@ class B2World(override var gravity: Vector2d) : IB2World {
}
}
override fun debugDraw() {
/**
* Call this to draw shapes and other debug draw data. This is intentionally non-const.
*/
fun debugDraw() {
val debugDraw = debugDraw ?: return
if (debugDraw.drawShapes) {
@ -872,17 +1030,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
}
}
companion object {
private val BAD_BODY_COLOR = Color(1f, 0f, 0f)
private val DISABLED_BODY_COLOR = Color(0.5f, 0.5f, 0.3f)
private val STATIC_BODY_COLOR = Color(0.5f, 0.9f, 0.5f)
private val KINEMATIC_BODY_COLOR = Color(0.5f, 0.5f, 0.9f)
private val SLEEPING_BODY_COLOR = Color(0.6f, 0.6f, 0.6f)
private val NORMAL_BODY_COLOR = Color(0.9f, 0.7f, 0.7f)
private val PAIR_COLOR = Color(0.3f, 0.9f, 0.9f)
private val AABB_COLOR = Color(0.9f, 0.3f, 0.9f)
}
override fun shiftOrigin(newOrigin: Vector2d) {
if (isLocked)
throw ConcurrentModificationException()
@ -891,7 +1038,6 @@ class B2World(override var gravity: Vector2d) : IB2World {
try {
for (body in bodyListIterator) {
body as Body
body.transform.p -= newOrigin
body.sweep.c0 -= newOrigin
body.sweep.c -= newOrigin
@ -907,10 +1053,29 @@ class B2World(override var gravity: Vector2d) : IB2World {
}
}
override val profileData: IProfileData
/**
* Get the current profile.
*/
val profileData: IProfileData
get() = profile.snapshot()
override fun dump() {
/**
* Dump the world into the log file.
* @warning this should be called outside of a time step.
*/
fun dump() {
TODO("Not yet implemented")
}
companion object {
private val BAD_BODY_COLOR = Color(1f, 0f, 0f)
private val DISABLED_BODY_COLOR = Color(0.5f, 0.5f, 0.3f)
private val STATIC_BODY_COLOR = Color(0.5f, 0.9f, 0.5f)
private val KINEMATIC_BODY_COLOR = Color(0.5f, 0.5f, 0.9f)
private val SLEEPING_BODY_COLOR = Color(0.6f, 0.6f, 0.6f)
private val NORMAL_BODY_COLOR = Color(0.9f, 0.7f, 0.7f)
private val PAIR_COLOR = Color(0.3f, 0.9f, 0.9f)
private val AABB_COLOR = Color(0.9f, 0.3f, 0.9f)
}
}

View File

@ -3,10 +3,13 @@ package ru.dbotthepony.kbox2d.dynamics
import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
open class Body(def: BodyDef, world: IB2World) : IBody {
private var _world: IB2World? = world
class Body(def: BodyDef, world: B2World) {
private var _world: B2World? = world
final override val world: IB2World
/**
* Get the parent world of this body.
*/
val world: B2World
get() = _world ?: throw IllegalStateException("Tried to use removed body")
internal var flags: Int = 0
@ -43,21 +46,37 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
internal val sweep: Sweep = Sweep()
final override val transform: Transform = Transform(def.position, def.angle)
/**
* Get the body transform for the body's origin.
* @return the world transform of the body's origin.
*/
val transform: Transform = Transform(def.position, def.angle)
val xf by this::transform
override val position: Vector2d
/**
* Get the world body origin position.
* @return the world position of the body's origin.
*/
val position: Vector2d
get() = transform.position
override val angle: Double
/**
* Get the angle in radians.
* @return the current world rotation angle in radians.
*/
val angle: Double
get() = sweep.a
override val userData: Any? = def.userData
val userData: Any? = def.userData
internal var sleepTime: Double = 0.0
internal var torque: Double = 0.0
internal var force: Vector2d = Vector2d.ZERO
override var linearVelocity: Vector2d = def.linearVelocity
/**
* The linear velocity of the body's origin in world co-ordinates.
*/
var linearVelocity: Vector2d = def.linearVelocity
set(value) {
if (type == BodyType.STATIC)
return
@ -68,7 +87,10 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
field = value
}
override var angularVelocity: Double = def.angularVelocity
/**
* The angular velocity of the body.
*/
var angularVelocity: Double = def.angularVelocity
set(value) {
if (type == BodyType.STATIC)
return
@ -79,27 +101,95 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
field = value
}
override var fixtureList: Fixture? = null
var fixtureList: Fixture? = null
protected set
protected var fixtureCount: Int = 0
var fixtureCount: Int = 0
private set
override var jointList: JointEdge? = null
internal set
override var contactEdge: ContactEdge? = null
/**
* Get the list of all joints attached to this body.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
var jointList: JointEdge? = null
internal set
override var next: IBody? = null
internal set
override var prev: IBody? = null
/**
* Get the list of all contacts attached to this body.
* @warning this list changes during the time step and you may
* miss some collisions if you don't use b2ContactListener.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
var contactEdge: ContactEdge? = null
internal set
override var linearDamping: Double = def.linearDamping
override var angularDamping: Double = def.angularDamping
override var gravityScale: Double = def.gravityScale
val contactEdgeIterator: Iterator<ContactEdge> get() {
return object : Iterator<ContactEdge> {
private var node = contactEdge
override var mass: Double = 0.0
protected set
override fun hasNext(): Boolean {
return node != null
}
override fun next(): ContactEdge {
val old = node!!
node = old.next
return old
}
}
}
val jointIterator: Iterator<JointEdge> get() {
return object : Iterator<JointEdge> {
private var node = jointList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): JointEdge {
val old = node!!
node = old.next
return old
}
}
}
var next: Body? = null
internal set
var prev: Body? = null
internal set
val fixtureIterator: Iterator<Fixture> get() {
return object : Iterator<Fixture> {
private var node = fixtureList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): Fixture {
val old = node!!
node = old.next
return old
}
}
}
var linearDamping: Double = def.linearDamping
var angularDamping: Double = def.angularDamping
var gravityScale: Double = def.gravityScale
/**
* Get the total mass of the body.
* @return the mass, usually in kilograms (kg).
*/
var mass: Double = 0.0
private set
internal var invMass: Double = 0.0
@ -116,11 +206,19 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
internal var I by this::rotInertia
internal var invI by this::rotInertiaInv
override var isBullet: Boolean
/**
* Should this body be treated like a bullet for continuous collision detection?
*/
var isBullet: Boolean
get() = BodyFlags.BULLET.isit(flags)
set(value) { flags = BodyFlags.BULLET.update(flags, value) }
override var isAwake: Boolean
/**
* Set the sleep state of the body. A sleeping body has very
* low CPU cost.
* @param flag set to true to wake the body, false to put it to sleep.
*/
var isAwake: Boolean
get() = BodyFlags.AWAKE.isit(flags)
set(value) {
if (type == BodyType.STATIC || BodyFlags.AWAKE.isit(flags) == value)
@ -139,7 +237,27 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override var isEnabled: Boolean
/**
* Allow a body to be disabled. A disabled body is not simulated and cannot
* be collided with or woken up.
*
* If you pass a flag of true, all fixtures will be added to the broad-phase.
*
* If you pass a flag of false, all fixtures will be removed from the
* broad-phase and all contacts will be destroyed.
*
* Fixtures and joints are otherwise unaffected. You may continue
* to create/destroy fixtures and joints on disabled bodies.
*
* Fixtures on a disabled body are implicitly disabled and will
* not participate in collisions, ray-casts, or queries.
*
* Joints connected to a disabled body are implicitly disabled.
*
* An diabled body is still owned by a b2World object and remains
* in the body list.
*/
var isEnabled: Boolean
get() = BodyFlags.ENABLED.isit(flags)
set(value) {
if (world.isLocked)
@ -178,7 +296,10 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override var isFixedRotation: Boolean
/**
* Set this body to have fixed rotation. This causes the mass to be reset.
*/
var isFixedRotation: Boolean
get() = BodyFlags.FIXED_ROTATION.isit(flags)
set(value) {
if (value == isFixedRotation)
@ -190,7 +311,11 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
resetMassData()
}
override var allowAutoSleep: Boolean
/**
* You can disable sleeping on this body. If you disable sleeping, the
* body will be woken.
*/
var allowAutoSleep: Boolean
get() = BodyFlags.AUTO_SLEEP.isit(flags)
set(value) {
flags = BodyFlags.AUTO_SLEEP.update(flags, value)
@ -200,7 +325,10 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override var type: BodyType = def.type
/**
* Set the type of this body. This may alter the mass and velocity.
*/
var type: BodyType = def.type
set(value) {
if (world.isLocked)
throw ConcurrentModificationException()
@ -234,7 +362,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
contactEdge = null
val broadPhase = world.contactManager.broadPhase
var f: IFixture? = fixtureList
var f: Fixture? = fixtureList
while (f != null) {
for (proxy in f.proxies) {
@ -245,7 +373,15 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun createFixture(def: FixtureDef): IFixture {
/** Creates a fixture and attach it to this body. Use this function if you need
* to set some fixture parameters, like friction. Otherwise you can create the
* fixture directly from a shape.
* If the density is non-zero, this function automatically updates the mass of the body.
* Contacts are not created until the next time step.
* @param def the fixture definition.
* @warning This function is locked during callbacks.
*/
fun createFixture(def: FixtureDef): Fixture {
if (world.isLocked)
throw ConcurrentModificationException()
@ -271,21 +407,48 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
return fixture
}
override fun destroyFixture(fixture: IFixture) {
/**
* Creates a fixture from a shape and attach it to this body.
* This is a convenience function. Use b2FixtureDef if you need to set parameters
* like friction, restitution, user data, or filtering.
* If the density is non-zero, this function automatically updates the mass of the body.
* @param shape the shape to be cloned.
* @param density the shape density (set to zero for static bodies).
* @warning This function is locked during callbacks.
*/
fun createFixture(shape: IShape<*>, density: Double): Fixture {
return createFixture(
FixtureDef(
shape = shape,
density = density
)
)
}
/**
* Destroy a fixture. This removes the fixture from the broad-phase and
* destroys all contacts associated with this fixture. This will
* automatically adjust the mass of the body if the body is dynamic and the
* fixture has positive density.
* All fixtures attached to a body are implicitly destroyed when the body is destroyed.
* @param fixture the fixture to be removed.
* @warning This function is locked during callbacks.
*/
fun destroyFixture(fixture: Fixture) {
if (world.isLocked)
throw ConcurrentModificationException()
require(fixture.body == this) { "$fixture does not belong to $this (belongs to ${fixture.body})" }
check(fixtureCount > 0) { "Having no tracked fixtures, but $fixture belongs to us" }
var node: IFixture? = fixtureList
var node: Fixture? = fixtureList
var found = false
var previous: IFixture? = null
var previous: Fixture? = null
while (node != null) {
if (node == fixture) {
// TODO: Это должно работать
(previous as Fixture?)?.next = node.next
previous?.next = node.next
found = true
break
} else {
@ -330,7 +493,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun resetMassData() {
/**
* This resets the mass properties to the sum of the mass properties of the fixtures.
* This normally does not need to be called unless you called SetMassData to override
* the mass and you later want to reset the mass.
*/
fun resetMassData() {
// Compute mass data from shapes. Each shape has its own density.
mass = 0.0
invMass = 0.0
@ -387,7 +555,16 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
linearVelocity += b2Cross(angularVelocity, sweep.c - oldCenter)
}
override var massData: MassData
/**
* Get the mass data of the body.
* @return a struct containing the mass, inertia and center of the body.
* Set the mass properties to override the mass properties of the fixtures.
* Note that this changes the center of mass position.
* Note that creating or destroying fixtures can also alter the mass.
* This function has no effect if the body isn't dynamic.
* @param data the mass properties.
*/
var massData: MassData
get() = MassData(
mass = mass,
inertia = inertia,
@ -428,7 +605,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
linearVelocity += b2Cross(angularVelocity, sweep.c - oldCenter)
}
internal fun shouldCollide(other: IBody): Boolean {
internal fun shouldCollide(other: Body): Boolean {
// At least one body should be dynamic.
if (type != BodyType.DYNAMIC && other.type != BodyType.DYNAMIC)
return false
@ -454,7 +631,14 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
sweep.a = def.angle
}
override fun setTransform(position: Vector2d, angle: Double) {
/**
* Set the position of the body's origin and rotation.
* Manipulating a body's transform may cause non-physical behavior.
* Note: contacts are updated on the next call to b2World::Step.
* @param position the world position of the body's local origin.
* @param angle the world rotation in radians.
*/
fun setTransform(position: Vector2d, angle: Double) {
if (world.isLocked)
throw ConcurrentModificationException()
@ -477,40 +661,88 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
world.notifyNewContacts()
}
override val inertia: Double
/**
* Get the rotational inertia of the body about the local origin.
* @return the rotational inertia, usually in kg-m^2.
*/
val inertia: Double
get() = rotInertia + mass * sweep.localCenter.dot(sweep.localCenter)
override val localCenter: Vector2d
/**
* Get the local position of the center of mass.
*/
val localCenter: Vector2d
get() = sweep.localCenter
override val worldCenter: Vector2d
/**
* Get the world position of the center of mass.
*/
val worldCenter: Vector2d
get() = sweep.c
override fun getWorldPoint(localPoint: Vector2d): Vector2d {
/**
* Get the world coordinates of a point given the local coordinates.
* @param localPoint a point on the body measured relative the the body's origin.
* @return the same point expressed in world coordinates.
*/
fun getWorldPoint(localPoint: Vector2d): Vector2d {
return transform.times(localPoint)
}
override fun getWorldVector(localPoint: Vector2d): Vector2d {
/**
* Get the world coordinates of a vector given the local coordinates.
* @param localVector a vector fixed in the body.
* @return the same vector expressed in world coordinates.
*/
fun getWorldVector(localPoint: Vector2d): Vector2d {
return transform.rotation.times(localPoint)
}
override fun getLocalPoint(worldPoint: Vector2d): Vector2d {
/**
* Gets a local point relative to the body's origin given a world point.
* @param worldPoint a point in world coordinates.
* @return the corresponding local point relative to the body's origin.
*/
fun getLocalPoint(worldPoint: Vector2d): Vector2d {
return transform.timesT(worldPoint)
}
override fun getLocalVector(worldVector: Vector2d): Vector2d {
/**
* Gets a local vector given a world vector.
* @param worldVector a vector in world coordinates.
* @return the corresponding local vector.
*/
fun getLocalVector(worldVector: Vector2d): Vector2d {
return transform.rotation.timesT(worldVector)
}
override fun getLinearVelocityFromWorldPoint(worldPoint: Vector2d): Vector2d {
/**
* Get the world linear velocity of a world point attached to this body.
* @param worldPoint a point in world coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromWorldPoint(worldPoint: Vector2d): Vector2d {
return linearVelocity + b2Cross(angularVelocity, worldPoint - sweep.c)
}
override fun getLinearVelocityFromLocalPoint(localPoint: Vector2d): Vector2d {
/**
* Get the world velocity of a local point.
* @param localPoint a point in local coordinates.
* @return the world velocity of a point.
*/
fun getLinearVelocityFromLocalPoint(localPoint: Vector2d): Vector2d {
return getLinearVelocityFromWorldPoint(getWorldPoint(localPoint))
}
override fun applyForce(force: Vector2d, point: Vector2d, wake: Boolean) {
/**
* Apply a force at a world point. If the force is not
* applied at the center of mass, it will generate a torque and
* affect the angular velocity. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyForce(force: Vector2d, point: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC)
return
@ -524,7 +756,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun applyForceToCenter(force: Vector2d, wake: Boolean) {
/**
* Apply a force to the center of mass. This wakes up the body.
* @param force the world force vector, usually in Newtons (N).
* @param wake also wake up the body
*/
fun applyForceToCenter(force: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC)
return
@ -537,7 +774,13 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun applyTorque(torque: Double, wake: Boolean) {
/**
* Apply a torque. This affects the angular velocity
* without affecting the linear velocity of the center of mass.
* @param torque about the z-axis (out of the screen), usually in N-m.
* @param wake also wake up the body
*/
fun applyTorque(torque: Double, wake: Boolean) {
if (type != BodyType.DYNAMIC)
return
@ -549,7 +792,15 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun applyLinearImpulse(impulse: Vector2d, point: Vector2d, wake: Boolean) {
/**
* Apply an impulse at a point. This immediately modifies the velocity.
* It also modifies the angular velocity if the point of application
* is not at the center of mass. This wakes up the body.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param point the world position of the point of application.
* @param wake also wake up the body
*/
fun applyLinearImpulse(impulse: Vector2d, point: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC)
return
@ -562,7 +813,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun applyLinearImpulseToCenter(impulse: Vector2d, wake: Boolean) {
/**
* Apply an impulse to the center of mass. This immediately modifies the velocity.
* @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
* @param wake also wake up the body
*/
fun applyLinearImpulseToCenter(impulse: Vector2d, wake: Boolean) {
if (type != BodyType.DYNAMIC)
return
@ -574,7 +830,12 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun applyAngularImpulse(impulse: Double, wake: Boolean) {
/**
* Apply an angular impulse.
* @param impulse the angular impulse in units of kg*m*m/s
* @param wake also wake up the body
*/
fun applyAngularImpulse(impulse: Double, wake: Boolean) {
if (type != BodyType.DYNAMIC)
return
@ -616,7 +877,7 @@ open class Body(def: BodyDef, world: IB2World) : IBody {
}
}
override fun dump() {
fun dump() {
TODO("Not yet implemented")
}
}

View File

@ -4,18 +4,35 @@ import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kbox2d.collision.BroadPhase
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
class ContactManager : IContactManager {
override val broadPhase: IBroadPhase = BroadPhase()
override var contactList: IContact? = null
override var contactCount: Int = 0
/**
* Delegate of b2World.
*/
class ContactManager {
val broadPhase = BroadPhase()
var contactList: AbstractContact? = null
var contactCount: Int = 0
private set
override var contactFilter: IContactFilter? = null
override var contactListener: IContactListener? = null
var contactFilter: IContactFilter? = null
var contactListener: IContactListener? = null
override fun destroy(contact: IContact) {
contact as AbstractContact
val contactListIterator: Iterator<AbstractContact> get() {
return object : Iterator<AbstractContact> {
private var node = contactList
override fun hasNext(): Boolean {
return node != null
}
override fun next(): AbstractContact {
val old = node!!
node = old.next
return old
}
}
}
fun destroy(contact: AbstractContact) {
val fixtureA = contact.fixtureA
val fixtureB = contact.fixtureB
val bodyA = fixtureA.body as Body
@ -72,7 +89,7 @@ class ContactManager : IContactManager {
* all the narrow phase collision is processed for the world
* contact list.
*/
override fun collide() {
fun collide() {
// Update awake contacts.
for (c in contactListIterator) {
@ -123,11 +140,11 @@ class ContactManager : IContactManager {
}
}
override fun findNewContacts() {
fun findNewContacts() {
broadPhase.updatePairs(this::addPair)
}
override fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?) {
fun addPair(proxyUserDataA: Any?, proxyUserDataB: Any?) {
val proxyA = proxyUserDataA as FixtureProxy
val proxyB = proxyUserDataB as FixtureProxy

View File

@ -1,28 +1,77 @@
package ru.dbotthepony.kbox2d.dynamics
import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kbox2d.collision.BroadPhase
import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
import java.util.*
import kotlin.collections.ArrayList
open class Fixture(
body: ru.dbotthepony.kbox2d.api.IBody,
/**
* A fixture is used to attach a shape to a body for collision detection. A fixture
* inherits its transform from its parent. Fixtures hold additional non-geometric data
* such as friction, collision filters, etc.
* Fixtures are created via b2Body::CreateFixture.
* @warning you cannot reuse fixtures.
*/
class Fixture(
body: Body,
def: FixtureDef
) : IFixture {
final override var body: ru.dbotthepony.kbox2d.api.IBody? = body
protected set
) {
/**
* Get the parent body of this fixture. This is nullptr if the fixture is not attached.
* @return the parent body.
*/
var body: Body? = body
private set
final override var next: IFixture? = null
/**
* Get the next fixture in the parent body's fixture list.
* @return the next shape.
*/
var next: Fixture? = null
override val userData: Any? = def.userData
override var friction: Double = def.friction
override var restitution: Double = def.restitution
override var restitutionThreshold: Double = def.restitutionThreshold
/**
* Get the user data that was assigned in the fixture definition. Use this to
* store your application specific data.
*/
var userData: Any? = def.userData
override val type: IShape.Type get() = shape.type
/**
* Get the coefficient of friction.
* Set the coefficient of friction. This will _not_ change the friction of
* existing contacts.
*/
var friction: Double = def.friction
override var isSensor: Boolean = def.isSensor
/**
* Get the coefficient of restitution.
* Set the coefficient of restitution. This will _not_ change the restitution of
* existing contacts.
*/
var restitution: Double = def.restitution
/**
* Get the restitution velocity threshold.
* Set the restitution threshold. This will _not_ change the restitution threshold of
* existing contacts.
*/
var restitutionThreshold: Double = def.restitutionThreshold
/**
* Get the type of the child shape. You can use this to down cast to the concrete shape.
* @return the shape type.
*/
val type: IShape.Type get() = shape.type
/**
* Set if this fixture is a sensor.
* Is this fixture a sensor (non-solid)?
* @return the true if the shape is a sensor.
*/
var isSensor: Boolean = def.isSensor
set(value) {
if (field != value) {
checkNotNull(body) { "Already destroyed" }.isAwake = true
@ -30,7 +79,12 @@ open class Fixture(
}
}
final override val shape: IShape<*> = requireNotNull(def.shape) { "null shape provided" }.copy()
/**
* Get the child shape. You can modify the child shape, however you should not change the
* number of vertices because this will crash some collision caching mechanisms.
* Manipulating the shape may lead to non-physical behavior.
*/
val shape: IShape<*> = requireNotNull(def.shape) { "null shape provided" }.copy()
init {
if (shape is PolygonShape) {
@ -42,10 +96,15 @@ open class Fixture(
}
}
protected val internalProxies = ArrayList<FixtureProxy>(shape.childCount)
final override val proxies: List<FixtureProxy> = Collections.unmodifiableList(internalProxies)
private val internalProxies = ArrayList<FixtureProxy>(shape.childCount)
val proxies: List<FixtureProxy> = Collections.unmodifiableList(internalProxies)
override var density: Double = def.density
/**
* Set the density of this fixture. This will _not_ automatically adjust the mass
* of the body. You must call b2Body::ResetMassData to update the body's mass.
* Get the density of this fixture.
*/
var density: Double = def.density
set(value) {
require(value.isFinite()) { "Infinite density" }
require(!value.isNaN()) { "NaN density" }
@ -53,7 +112,12 @@ open class Fixture(
field = value
}
override var filter: IFilter = def.filter.immutable()
/**
* Set the contact filtering data. This will not update contacts until the next time
* step when either parent body is active and awake.
* This automatically calls Refilter.
*/
var filter: IFilter = def.filter.immutable()
set(value) {
if (value is ImmutableFilter)
field = value
@ -76,7 +140,7 @@ open class Fixture(
/**
* These support body activation/deactivation.
*/
internal fun createProxies(broadPhase: IBroadPhase, xf: Transform) {
internal fun createProxies(broadPhase: BroadPhase, xf: Transform) {
check(body != null) { "Already destroyed" }
check(internalProxies.isEmpty()) { "Already having proxies" }
@ -97,7 +161,7 @@ open class Fixture(
/**
* These support body activation/deactivation.
*/
internal fun destroyProxies(broadPhase: IBroadPhase) {
internal fun destroyProxies(broadPhase: BroadPhase) {
check(body != null) { "Already destroyed" }
// Destroy proxies in the broad-phase.
@ -108,7 +172,7 @@ open class Fixture(
internalProxies.clear()
}
internal fun synchronize(broadPhase: IBroadPhase, transform1: Transform, transform2: Transform) {
internal fun synchronize(broadPhase: BroadPhase, transform1: Transform, transform2: Transform) {
check(body != null) { "Already destroyed" }
for (proxy in internalProxies) {
@ -121,7 +185,10 @@ open class Fixture(
}
}
override fun refilter() {
/**
* Call this if you want to establish collision that was previously disabled by b2ContactFilter::ShouldCollide.
*/
fun refilter() {
val body = body
check(body != null) { "Already destroyed" }
var edge = body.contactEdge
@ -144,4 +211,45 @@ open class Fixture(
broadPhase.touchProxy(proxy.proxyId)
}
}
/**
* Test a point for containment in this fixture.
* @param p a point in world coordinates.
*/
fun testPoint(p: Vector2d): Boolean {
return shape.testPoint(checkNotNull(body) { "Tried to use detached fixture" }.transform, p)
}
/**
* Cast a ray against this shape.
* @param output the ray-cast results.
* @param input the ray-cast input parameters.
* @param childIndex the child shape index (e.g. edge index)
*/
fun rayCast(input: RayCastInput, childIndex: Int): RayCastOutput {
return shape.rayCast(input, checkNotNull(body) { "Tried to use detached fixture" }.transform, childIndex)
}
/**
* Get the mass data for this fixture. The mass data is based on the density and
* the shape. The rotational inertia is about the shape's origin. This operation
* may be expensive.
*/
fun getMassData(): MassData {
return shape.computeMass(density)
}
/**
* Get the fixture's AABB. This AABB may be enlarge and/or stale.
* If you need a more accurate AABB, compute it using the shape and
* the body transform.
*/
fun getAABB(childIndex: Int): AABB {
return proxies[childIndex].aabb
}
/**
* Dump this fixture to the log file.
*/
fun dump(childIndex: Int) { }
}

View File

@ -4,30 +4,61 @@ import ru.dbotthepony.kbox2d.api.*
import ru.dbotthepony.kbox2d.collision.WorldManifold
import ru.dbotthepony.kbox2d.collision.b2TestOverlap
import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.Fixture
import java.util.*
import kotlin.collections.HashMap
fun interface ContactFactory {
fun factorize(fixtureA: IFixture, childIndexA: Int, fixtureB: IFixture, childIndexB: Int): AbstractContact
fun factorize(fixtureA: Fixture, childIndexA: Int, fixtureB: Fixture, childIndexB: Int): AbstractContact
}
/**
* The class manages contact between two shapes. A contact exists for each overlapping
* AABB in the broad-phase (except if filtered). Therefore a contact object may exist
* that has no contact points.
*/
sealed class AbstractContact(
final override val fixtureA: IFixture,
final override val childIndexA: Int,
final override val fixtureB: IFixture,
final override val childIndexB: Int,
) : IContact {
/**
* Get fixture A in this contact.
*/
val fixtureA: Fixture,
/**
* Get the child primitive index for fixture A.
*/
val childIndexA: Int,
/**
* Get fixture B in this contact.
*/
val fixtureB: Fixture,
/**
* Get the child primitive index for fixture B.
*/
val childIndexB: Int,
) {
internal var flags: Int = ContactFlags.ENABLED.bitmask
internal var isOnIsland = false
internal var toiFlag = false
final override var manifold: Manifold = Manifold()
/**
* Get the contact manifold. Do not modify the manifold unless you understand the
* internals of Box2D.
*/
var manifold: Manifold = Manifold()
private set
override var next: IContact? = null
/**
* Get the next contact in the world's contact list.
*/
var next: AbstractContact? = null
internal set
override var prev: IContact? = null
/**
* Get the previous contact in the world's contact list.
*/
var prev: AbstractContact? = null
internal set
internal var isFlaggedForFiltering: Boolean
@ -51,15 +82,44 @@ sealed class AbstractContact(
bodyB.contactEdge = nodeB
}
override var friction: Double = b2MixFriction(fixtureA.friction, fixtureB.friction)
override var restitution: Double = b2MixRestitution(fixtureA.restitution, fixtureB.restitution)
override var restitutionThreshold: Double = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold)
override var tangentSpeed: Double = 0.0
/**
* Override the default friction mixture. You can call this in b2ContactListener::PreSolve.
* This value persists until set or reset.
* Get the friction.
*/
var friction: Double = b2MixFriction(fixtureA.friction, fixtureB.friction)
/**
* Override the default restitution mixture. You can call this in b2ContactListener::PreSolve.
* The value persists until you set or reset.
*/
var restitution: Double = b2MixRestitution(fixtureA.restitution, fixtureB.restitution)
/**
* Override the default restitution velocity threshold mixture. You can call this in b2ContactListener::PreSolve.
* The value persists until you set or reset.
* Get the restitution threshold.
*/
var restitutionThreshold: Double = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold)
/**
* Set the desired tangent speed for a conveyor belt behavior. In meters per second.
* Get the desired tangent speed. In meters per second.
*/
var tangentSpeed: Double = 0.0
internal var toiCount: Int = 0
internal var toi: Double = 0.0
override val worldManifold: IWorldManifold get() {
/**
* Evaluate this contact with your own manifold and transforms.
*/
abstract fun evaluate(xfA: Transform, xfB: Transform): Manifold
/**
* Get the world manifold.
*/
val worldManifold: IWorldManifold get() {
val bodyA = checkNotNull(fixtureA.body) { "FixtureA has no body attached" }
val bodyB = checkNotNull(fixtureB.body) { "FixtureB has no body attached" }
@ -69,15 +129,23 @@ sealed class AbstractContact(
return WorldManifold(manifold, bodyA.transform, shapeA.radius, bodyB.transform, shapeB.radius)
}
override var isTouching: Boolean
/**
* Is this contact touching?
*/
var isTouching: Boolean
get() = (flags and ContactFlags.TOUCHING.bitmask) == ContactFlags.TOUCHING.bitmask
internal set(value) { flags = ContactFlags.TOUCHING.update(flags, value) }
override var isEnabled: Boolean
/**
* Enable/disable this contact. This can be used inside the pre-solve
* contact listener. The contact is only disabled for the current
* time step (or sub-step in continuous collisions).
*/
var isEnabled: Boolean
get() = (flags and ContactFlags.ENABLED.bitmask) == ContactFlags.ENABLED.bitmask
set(value) { flags = ContactFlags.ENABLED.update(flags, value) }
override fun flagForFiltering() {
fun flagForFiltering() {
flags = flags or ContactFlags.FILTER.bitmask
}
@ -159,6 +227,27 @@ sealed class AbstractContact(
}
}
/**
* Reset the friction mixture to the default value.
*/
fun resetFriction() {
friction = b2MixFriction(fixtureA.friction, fixtureB.friction)
}
/**
* Reset the restitution to the default value.
*/
fun resetRestitution() {
restitution = b2MixRestitution(fixtureA.restitution, fixtureB.restitution)
}
/**
* Reset the restitution threshold to the default value.
*/
fun resetRestitutionThreshold() {
restitutionThreshold = b2MixRestitutionThreshold(fixtureA.restitutionThreshold, fixtureB.restitutionThreshold)
}
companion object {
private val registry =
EnumMap<IShape.Type, EnumMap<IShape.Type, ContactFactory>>(IShape.Type::class.java)
@ -168,9 +257,9 @@ sealed class AbstractContact(
}
internal fun create(
fixtureA: IFixture,
fixtureA: Fixture,
indexA: Int,
fixtureB: IFixture,
fixtureB: Fixture,
indexB: Int,
): AbstractContact {
val type1 = fixtureA.type
@ -182,31 +271,31 @@ sealed class AbstractContact(
}
init {
register(IShape.Type.POLYGON, IShape.Type.POLYGON) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int ->
register(IShape.Type.POLYGON, IShape.Type.POLYGON) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register PolygonContact(fixtureA, fixtureB)
}
register(IShape.Type.POLYGON, IShape.Type.CIRCLE) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int ->
register(IShape.Type.POLYGON, IShape.Type.CIRCLE) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register PolygonCircleContact(fixtureA, fixtureB)
}
register(IShape.Type.CIRCLE, IShape.Type.CIRCLE) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int ->
register(IShape.Type.CIRCLE, IShape.Type.CIRCLE) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register CircleContact(fixtureA, fixtureB)
}
register(IShape.Type.EDGE, IShape.Type.CIRCLE) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int ->
register(IShape.Type.EDGE, IShape.Type.CIRCLE) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register EdgeCircleContact(fixtureA, fixtureB)
}
register(IShape.Type.EDGE, IShape.Type.POLYGON) { fixtureA: IFixture, _: Int, fixtureB: IFixture, _: Int ->
register(IShape.Type.EDGE, IShape.Type.POLYGON) { fixtureA: Fixture, _: Int, fixtureB: Fixture, _: Int ->
return@register EdgePolygonContact(fixtureA, fixtureB)
}
register(IShape.Type.CHAIN, IShape.Type.POLYGON) { fixtureA: IFixture, indexA: Int, fixtureB: IFixture, indexB: Int ->
register(IShape.Type.CHAIN, IShape.Type.POLYGON) { fixtureA: Fixture, indexA: Int, fixtureB: Fixture, indexB: Int ->
return@register ChainPolygonContact(fixtureA, indexA, fixtureB, indexB)
}
register(IShape.Type.CHAIN, IShape.Type.CIRCLE) { fixtureA: IFixture, indexA: Int, fixtureB: IFixture, indexB: Int ->
register(IShape.Type.CHAIN, IShape.Type.CIRCLE) { fixtureA: Fixture, indexA: Int, fixtureB: Fixture, indexB: Int ->
return@register ChainCircleContact(fixtureA, indexA, fixtureB, indexB)
}
}

View File

@ -1,17 +1,17 @@
package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle
import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class ChainCircleContact(
fixtureA: IFixture,
fixtureA: Fixture,
childIndexA: Int,
fixtureB: IFixture,
fixtureB: Fixture,
childIndexB: Int,
) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) {
init {

View File

@ -1,17 +1,17 @@
package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon
import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class ChainPolygonContact(
fixtureA: IFixture,
fixtureA: Fixture,
childIndexA: Int,
fixtureB: IFixture,
fixtureB: Fixture,
childIndexB: Int,
) : AbstractContact(fixtureA, childIndexA, fixtureB, childIndexB) {
init {

View File

@ -1,15 +1,15 @@
package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideCircles
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class CircleContact(
fixtureA: IFixture,
fixtureB: IFixture,
fixtureA: Fixture,
fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init {
require(fixtureA.type == IShape.Type.CIRCLE) { "Fixture A is of type ${fixtureA.type}" }

View File

@ -1,16 +1,16 @@
package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndCircle
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class EdgeCircleContact(
fixtureA: IFixture,
fixtureB: IFixture,
fixtureA: Fixture,
fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init {
require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" }

View File

@ -1,16 +1,16 @@
package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollideEdgeAndPolygon
import ru.dbotthepony.kbox2d.collision.shapes.EdgeShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class EdgePolygonContact(
fixtureA: IFixture,
fixtureB: IFixture,
fixtureA: Fixture,
fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init {
require(fixtureA.type == IShape.Type.EDGE) { "Fixture A is of type ${fixtureA.type}, expected EDGE" }

View File

@ -1,16 +1,16 @@
package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygonAndCircle
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class PolygonCircleContact(
fixtureA: IFixture,
fixtureB: IFixture,
fixtureA: Fixture,
fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init {
require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A is of type ${fixtureA.type}, expected POLYGON" }

View File

@ -1,15 +1,15 @@
package ru.dbotthepony.kbox2d.dynamics.contact
import ru.dbotthepony.kbox2d.api.IFixture
import ru.dbotthepony.kbox2d.api.IShape
import ru.dbotthepony.kbox2d.api.Manifold
import ru.dbotthepony.kbox2d.api.Transform
import ru.dbotthepony.kbox2d.collision.handler.b2CollidePolygons
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.Fixture
class PolygonContact(
fixtureA: IFixture,
fixtureB: IFixture,
fixtureA: Fixture,
fixtureB: Fixture,
) : AbstractContact(fixtureA, 0, fixtureB, 0) {
init {
require(fixtureA.type == IShape.Type.POLYGON) { "Fixture A has type of ${fixtureA.type}" }

View File

@ -9,7 +9,7 @@ fun interface JointFactory {
fun factorize(jointDef: IJointDef): AbstractJoint
}
sealed class AbstractJoint(def: IJointDef) : IJoint {
sealed class AbstractJoint(def: IJointDef) : IMovable {
init {
require(def.bodyA != def.bodyB) { "Tried to create join on same body" }
}
@ -18,9 +18,28 @@ sealed class AbstractJoint(def: IJointDef) : IJoint {
protected var index: Int = 0
override val collideConnected: Boolean = def.collideConnected
override val type: JointType = def.type
override var userData: Any? = def.userData
/**
* Get collide connected.
* Note: modifying the collide connect flag won't work correctly because
* the flag is only checked when fixture AABBs begin to overlap.
*/
val collideConnected: Boolean = def.collideConnected
/**
* Dump this joint to the log file.
*/
open fun dump() { }
/**
* Debug draw this joint
*/
open fun draw(draw: IDebugDraw) { }
/**
* Get the type of the concrete joint.
*/
val type: JointType = def.type
var userData: Any? = def.userData
// KBox2D: In original code, nothing expects bodies to be null
// but certain joints (notably, mouse joint) have no meaningful
@ -31,8 +50,35 @@ sealed class AbstractJoint(def: IJointDef) : IJoint {
protected var _bodyA: Body? = def.bodyA as Body?
protected var _bodyB: Body? = def.bodyB as Body?
final override val bodyA: Body get() = checkNotNull(_bodyA) { "Body A is not present" }
final override val bodyB: Body get() = checkNotNull(_bodyB) { "Body B is not present" }
/**
* Get the first body attached to this joint.
*/
val bodyA: Body get() = checkNotNull(_bodyA) { "Body A is not present" }
/**
* Get the second body attached to this joint.
*/
val bodyB: Body get() = checkNotNull(_bodyB) { "Body B is not present" }
/**
* Get the anchor point on bodyA in world coordinates.
*/
abstract val anchorA: Vector2d
/**
* Get the anchor point on bodyB in world coordinates.
*/
abstract val anchorB: Vector2d
/**
* Get the reaction force on bodyB at the joint anchor in Newtons.
*/
abstract fun getReactionForce(inv_dt: Double): Vector2d
/**
* Get the reaction torque on bodyB in N*m.
*/
abstract fun getReactionTorque(inv_dt: Double): Double
val hasBodyA: Boolean get() = _bodyA != null
val hasBodyB: Boolean get() = _bodyB != null
@ -58,11 +104,26 @@ sealed class AbstractJoint(def: IJointDef) : IJoint {
_bodyB?.jointList = edgeB
}
final override var next: IJoint? = null
/**
* Get the next joint the world joint list.
*
* Kotlin: This is not a list, but, as in C++ impl, a custom
* linked list.
*/
var next: AbstractJoint? = null
internal set
final override var prev: IJoint? = null
var prev: AbstractJoint? = null
internal set
fun destroy() {
bodyA.world.destroyJoint(this)
}
/**
* Short-cut function to determine if either body is enabled.
*/
val isEnabled: Boolean get() = bodyA.isEnabled || bodyB.isEnabled
/**
* Signals that this joint was destroyed, invalidate stuff
* to fail-fast this object

View File

@ -8,6 +8,7 @@ import ru.dbotthepony.kbox2d.collision.shapes.ChainShape
import ru.dbotthepony.kbox2d.collision.shapes.CircleShape
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.B2World
import ru.dbotthepony.kbox2d.dynamics.Body
import ru.dbotthepony.kbox2d.dynamics.joint.MouseJoint
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.world.Chunk
@ -53,7 +54,7 @@ fun main() {
ground.createFixture(groundPoly, 0.0)
val boxes = ArrayList<ru.dbotthepony.kbox2d.api.IBody>()
val boxes = ArrayList<Body>()
/*run {
val movingDef = BodyDef(