chore: 添加第三方依赖库

This commit is contained in:
yhh
2025-12-03 16:24:08 +08:00
parent cb1b171216
commit 83aee02540
172 changed files with 27480 additions and 0 deletions

View File

@@ -0,0 +1,520 @@
import {RawBroadPhase, RawRayColliderIntersection} from "../raw";
import {RigidBodyHandle, RigidBodySet} from "../dynamics";
import {ColliderSet} from "./collider_set";
import {Ray, RayColliderHit, RayColliderIntersection} from "./ray";
import {InteractionGroups} from "./interaction_groups";
import {ColliderHandle} from "./collider";
import {Rotation, RotationOps, Vector, VectorOps} from "../math";
import {Shape} from "./shape";
import {PointColliderProjection} from "./point";
import {ColliderShapeCastHit} from "./toi";
import {QueryFilterFlags} from "../pipeline";
import {NarrowPhase} from "./narrow_phase";
/**
* The broad-phase used for coarse collision-detection.
*
* To avoid leaking WASM resources, this MUST be freed manually with `broadPhase.free()`
* once you are done using it.
*/
export class BroadPhase {
raw: RawBroadPhase;
/**
* Release the WASM memory occupied by this broad-phase.
*/
public free() {
if (!!this.raw) {
this.raw.free();
}
this.raw = undefined;
}
constructor(raw?: RawBroadPhase) {
this.raw = raw || new RawBroadPhase();
}
/**
* Find the closest intersection between a ray and a set of collider.
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param ray - The ray to cast.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the length of the ray to `ray.dir.norm() * maxToi`.
* @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
* origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
* whereas `false` implies that all shapes are hollow for this ray-cast.
* @param groups - Used to filter the colliders that can or cannot be hit by the ray.
* @param filter - The callback to filter out which collider will be hit.
*/
public castRay(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
ray: Ray,
maxToi: number,
solid: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
): RayColliderHit | null {
let rawOrig = VectorOps.intoRaw(ray.origin);
let rawDir = VectorOps.intoRaw(ray.dir);
let result = RayColliderHit.fromRaw(
colliders,
this.raw.castRay(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawOrig,
rawDir,
maxToi,
solid,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
),
);
rawOrig.free();
rawDir.free();
return result;
}
/**
* Find the closest intersection between a ray and a set of collider.
*
* This also computes the normal at the hit point.
* @param colliders - The set of colliders taking part in this pipeline.
* @param ray - The ray to cast.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the length of the ray to `ray.dir.norm() * maxToi`.
* @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
* origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
* whereas `false` implies that all shapes are hollow for this ray-cast.
* @param groups - Used to filter the colliders that can or cannot be hit by the ray.
*/
public castRayAndGetNormal(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
ray: Ray,
maxToi: number,
solid: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
): RayColliderIntersection | null {
let rawOrig = VectorOps.intoRaw(ray.origin);
let rawDir = VectorOps.intoRaw(ray.dir);
let result = RayColliderIntersection.fromRaw(
colliders,
this.raw.castRayAndGetNormal(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawOrig,
rawDir,
maxToi,
solid,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
),
);
rawOrig.free();
rawDir.free();
return result;
}
/**
* Cast a ray and collects all the intersections between a ray and the scene.
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param ray - The ray to cast.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the length of the ray to `ray.dir.norm() * maxToi`.
* @param solid - If `false` then the ray will attempt to hit the boundary of a shape, even if its
* origin already lies inside of a shape. In other terms, `true` implies that all shapes are plain,
* whereas `false` implies that all shapes are hollow for this ray-cast.
* @param groups - Used to filter the colliders that can or cannot be hit by the ray.
* @param callback - The callback called once per hit (in no particular order) between a ray and a collider.
* If this callback returns `false`, then the cast will stop and no further hits will be detected/reported.
*/
public intersectionsWithRay(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
ray: Ray,
maxToi: number,
solid: boolean,
callback: (intersect: RayColliderIntersection) => boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
) {
let rawOrig = VectorOps.intoRaw(ray.origin);
let rawDir = VectorOps.intoRaw(ray.dir);
let rawCallback = (rawInter: RawRayColliderIntersection) => {
return callback(
RayColliderIntersection.fromRaw(colliders, rawInter),
);
};
this.raw.intersectionsWithRay(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawOrig,
rawDir,
maxToi,
solid,
rawCallback,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
);
rawOrig.free();
rawDir.free();
}
/**
* Gets the handle of up to one collider intersecting the given shape.
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param shapePos - The position of the shape used for the intersection test.
* @param shapeRot - The orientation of the shape used for the intersection test.
* @param shape - The shape used for the intersection test.
* @param groups - The bit groups and filter associated to the ray, in order to only
* hit the colliders with collision groups compatible with the ray's group.
*/
public intersectionWithShape(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
shapePos: Vector,
shapeRot: Rotation,
shape: Shape,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
): ColliderHandle | null {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawShape = shape.intoRaw();
let result = this.raw.intersectionWithShape(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawPos,
rawRot,
rawShape,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
);
rawPos.free();
rawRot.free();
rawShape.free();
return result;
}
/**
* Find the projection of a point on the closest collider.
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param point - The point to project.
* @param solid - If this is set to `true` then the collider shapes are considered to
* be plain (if the point is located inside of a plain shape, its projection is the point
* itself). If it is set to `false` the collider shapes are considered to be hollow
* (if the point is located inside of an hollow shape, it is projected on the shape's
* boundary).
* @param groups - The bit groups and filter associated to the point to project, in order to only
* project on colliders with collision groups compatible with the ray's group.
*/
public projectPoint(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
point: Vector,
solid: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
): PointColliderProjection | null {
let rawPoint = VectorOps.intoRaw(point);
let result = PointColliderProjection.fromRaw(
colliders,
this.raw.projectPoint(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawPoint,
solid,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
),
);
rawPoint.free();
return result;
}
/**
* Find the projection of a point on the closest collider.
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param point - The point to project.
* @param groups - The bit groups and filter associated to the point to project, in order to only
* project on colliders with collision groups compatible with the ray's group.
*/
public projectPointAndGetFeature(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
point: Vector,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
): PointColliderProjection | null {
let rawPoint = VectorOps.intoRaw(point);
let result = PointColliderProjection.fromRaw(
colliders,
this.raw.projectPointAndGetFeature(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawPoint,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
),
);
rawPoint.free();
return result;
}
/**
* Find all the colliders containing the given point.
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param point - The point used for the containment test.
* @param groups - The bit groups and filter associated to the point to test, in order to only
* test on colliders with collision groups compatible with the ray's group.
* @param callback - A function called with the handles of each collider with a shape
* containing the `point`.
*/
public intersectionsWithPoint(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
point: Vector,
callback: (handle: ColliderHandle) => boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
) {
let rawPoint = VectorOps.intoRaw(point);
this.raw.intersectionsWithPoint(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawPoint,
callback,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
);
rawPoint.free();
}
/**
* Casts a shape at a constant linear velocity and retrieve the first collider it hits.
* This is similar to ray-casting except that we are casting a whole shape instead of
* just a point (the ray origin).
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param shapePos - The initial position of the shape to cast.
* @param shapeRot - The initial rotation of the shape to cast.
* @param shapeVel - The constant velocity of the shape to cast (i.e. the cast direction).
* @param shape - The shape to cast.
* @param targetDistance If the shape moves closer to this distance from a collider, a hit
* will be returned.
* @param maxToi - The maximum time-of-impact that can be reported by this cast. This effectively
* limits the distance traveled by the shape to `shapeVel.norm() * maxToi`.
* @param stopAtPenetration - If set to `false`, the linear shape-cast wont immediately stop if
* the shape is penetrating another shape at its starting point **and** its trajectory is such
* that its on a path to exit that penetration state.
* @param groups - The bit groups and filter associated to the shape to cast, in order to only
* test on colliders with collision groups compatible with this group.
*/
public castShape(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
shapePos: Vector,
shapeRot: Rotation,
shapeVel: Vector,
shape: Shape,
targetDistance: number,
maxToi: number,
stopAtPenetration: boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
): ColliderShapeCastHit | null {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawVel = VectorOps.intoRaw(shapeVel);
let rawShape = shape.intoRaw();
let result = ColliderShapeCastHit.fromRaw(
colliders,
this.raw.castShape(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawPos,
rawRot,
rawVel,
rawShape,
targetDistance,
maxToi,
stopAtPenetration,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
),
);
rawPos.free();
rawRot.free();
rawVel.free();
rawShape.free();
return result;
}
/**
* Retrieve all the colliders intersecting the given shape.
*
* @param colliders - The set of colliders taking part in this pipeline.
* @param shapePos - The position of the shape to test.
* @param shapeRot - The orientation of the shape to test.
* @param shape - The shape to test.
* @param groups - The bit groups and filter associated to the shape to test, in order to only
* test on colliders with collision groups compatible with this group.
* @param callback - A function called with the handles of each collider intersecting the `shape`.
*/
public intersectionsWithShape(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
shapePos: Vector,
shapeRot: Rotation,
shape: Shape,
callback: (handle: ColliderHandle) => boolean,
filterFlags?: QueryFilterFlags,
filterGroups?: InteractionGroups,
filterExcludeCollider?: ColliderHandle,
filterExcludeRigidBody?: RigidBodyHandle,
filterPredicate?: (collider: ColliderHandle) => boolean,
) {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawShape = shape.intoRaw();
this.raw.intersectionsWithShape(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawPos,
rawRot,
rawShape,
callback,
filterFlags,
filterGroups,
filterExcludeCollider,
filterExcludeRigidBody,
filterPredicate,
);
rawPos.free();
rawRot.free();
rawShape.free();
}
/**
* Finds the handles of all the colliders with an AABB intersecting the given AABB.
*
* @param aabbCenter - The center of the AABB to test.
* @param aabbHalfExtents - The half-extents of the AABB to test.
* @param callback - The callback that will be called with the handles of all the colliders
* currently intersecting the given AABB.
*/
public collidersWithAabbIntersectingAabb(
narrowPhase: NarrowPhase,
bodies: RigidBodySet,
colliders: ColliderSet,
aabbCenter: Vector,
aabbHalfExtents: Vector,
callback: (handle: ColliderHandle) => boolean,
) {
let rawCenter = VectorOps.intoRaw(aabbCenter);
let rawHalfExtents = VectorOps.intoRaw(aabbHalfExtents);
this.raw.collidersWithAabbIntersectingAabb(
narrowPhase.raw,
bodies.raw,
colliders.raw,
rawCenter,
rawHalfExtents,
callback,
);
rawCenter.free();
rawHalfExtents.free();
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
import {RawColliderSet} from "../raw";
import {Coarena} from "../coarena";
import {RotationOps, VectorOps} from "../math";
import {Collider, ColliderDesc, ColliderHandle} from "./collider";
import {ImpulseJointHandle, IslandManager, RigidBodyHandle} from "../dynamics";
import {RigidBodySet} from "../dynamics";
/**
* A set of rigid bodies that can be handled by a physics pipeline.
*
* To avoid leaking WASM resources, this MUST be freed manually with `colliderSet.free()`
* once you are done using it (and all the rigid-bodies it created).
*/
export class ColliderSet {
raw: RawColliderSet;
private map: Coarena<Collider>;
/**
* Release the WASM memory occupied by this collider set.
*/
public free() {
if (!!this.raw) {
this.raw.free();
}
this.raw = undefined;
if (!!this.map) {
this.map.clear();
}
this.map = undefined;
}
constructor(raw?: RawColliderSet) {
this.raw = raw || new RawColliderSet();
this.map = new Coarena<Collider>();
// Initialize the map with the existing elements, if any.
if (raw) {
raw.forEachColliderHandle((handle: ColliderHandle) => {
this.map.set(handle, new Collider(this, handle, null));
});
}
}
/** @internal */
public castClosure<Res>(
f?: (collider: Collider) => Res,
): (handle: ColliderHandle) => Res | undefined {
return (handle) => {
if (!!f) {
return f(this.get(handle));
} else {
return undefined;
}
};
}
/** @internal */
public finalizeDeserialization(bodies: RigidBodySet) {
this.map.forEach((collider) =>
collider.finalizeDeserialization(bodies),
);
}
/**
* Creates a new collider and return its integer handle.
*
* @param bodies - The set of bodies where the collider's parent can be found.
* @param desc - The collider's description.
* @param parentHandle - The integer handle of the rigid-body this collider is attached to.
*/
public createCollider(
bodies: RigidBodySet,
desc: ColliderDesc,
parentHandle: RigidBodyHandle,
): Collider {
let hasParent = parentHandle != undefined && parentHandle != null;
if (hasParent && isNaN(parentHandle))
throw Error(
"Cannot create a collider with a parent rigid-body handle that is not a number.",
);
let rawShape = desc.shape.intoRaw();
let rawTra = VectorOps.intoRaw(desc.translation);
let rawRot = RotationOps.intoRaw(desc.rotation);
let rawCom = VectorOps.intoRaw(desc.centerOfMass);
// #if DIM3
let rawPrincipalInertia = VectorOps.intoRaw(
desc.principalAngularInertia,
);
let rawInertiaFrame = RotationOps.intoRaw(
desc.angularInertiaLocalFrame,
);
// #endif
let handle = this.raw.createCollider(
desc.enabled,
rawShape,
rawTra,
rawRot,
desc.massPropsMode,
desc.mass,
rawCom,
// #if DIM2
desc.principalAngularInertia,
// #endif
// #if DIM3
rawPrincipalInertia,
rawInertiaFrame,
// #endif
desc.density,
desc.friction,
desc.restitution,
desc.frictionCombineRule,
desc.restitutionCombineRule,
desc.isSensor,
desc.collisionGroups,
desc.solverGroups,
desc.activeCollisionTypes,
desc.activeHooks,
desc.activeEvents,
desc.contactForceEventThreshold,
desc.contactSkin,
hasParent,
hasParent ? parentHandle : 0,
bodies.raw,
);
rawShape.free();
rawTra.free();
rawRot.free();
rawCom.free();
// #if DIM3
rawPrincipalInertia.free();
rawInertiaFrame.free();
// #endif
let parent = hasParent ? bodies.get(parentHandle) : null;
let collider = new Collider(this, handle, parent, desc.shape);
this.map.set(handle, collider);
return collider;
}
/**
* Remove a collider from this set.
*
* @param handle - The integer handle of the collider to remove.
* @param bodies - The set of rigid-body containing the rigid-body the collider is attached to.
* @param wakeUp - If `true`, the rigid-body the removed collider is attached to will be woken-up automatically.
*/
public remove(
handle: ColliderHandle,
islands: IslandManager,
bodies: RigidBodySet,
wakeUp: boolean,
) {
this.raw.remove(handle, islands.raw, bodies.raw, wakeUp);
this.unmap(handle);
}
/**
* Internal function, do not call directly.
* @param handle
*/
public unmap(handle: ImpulseJointHandle) {
this.map.delete(handle);
}
/**
* Gets the rigid-body with the given handle.
*
* @param handle - The handle of the rigid-body to retrieve.
*/
public get(handle: ColliderHandle): Collider | null {
return this.map.get(handle);
}
/**
* The number of colliders on this set.
*/
public len(): number {
return this.map.len();
}
/**
* Does this set contain a collider with the given handle?
*
* @param handle - The collider handle to check.
*/
public contains(handle: ColliderHandle): boolean {
return this.get(handle) != null;
}
/**
* Applies the given closure to each collider contained by this set.
*
* @param f - The closure to apply.
*/
public forEach(f: (collider: Collider) => void) {
this.map.forEach(f);
}
/**
* Gets all colliders in the list.
*
* @returns collider list.
*/
public getAll(): Collider[] {
return this.map.getAll();
}
}

View File

@@ -0,0 +1,62 @@
import {Vector, VectorOps} from "../math";
import {RawShapeContact} from "../raw";
/**
* The contact info between two shapes.
*/
export class ShapeContact {
/**
* Distance between the two contact points.
* If this is negative, this contact represents a penetration.
*/
distance: number;
/**
* Position of the contact on the first shape.
*/
point1: Vector;
/**
* Position of the contact on the second shape.
*/
point2: Vector;
/**
* Contact normal, pointing towards the exterior of the first shape.
*/
normal1: Vector;
/**
* Contact normal, pointing towards the exterior of the second shape.
* If these contact data are expressed in world-space, this normal is equal to -normal1.
*/
normal2: Vector;
constructor(
dist: number,
point1: Vector,
point2: Vector,
normal1: Vector,
normal2: Vector,
) {
this.distance = dist;
this.point1 = point1;
this.point2 = point2;
this.normal1 = normal1;
this.normal2 = normal2;
}
public static fromRaw(raw: RawShapeContact): ShapeContact {
if (!raw) return null;
const result = new ShapeContact(
raw.distance(),
VectorOps.fromRaw(raw.point1()),
VectorOps.fromRaw(raw.point2()),
VectorOps.fromRaw(raw.normal1()),
VectorOps.fromRaw(raw.normal2()),
);
raw.free();
return result;
}
}

View File

@@ -0,0 +1,16 @@
// #if DIM2
export enum FeatureType {
Vertex,
Face,
Unknown,
}
// #endif
// #if DIM3
export enum FeatureType {
Vertex,
Edge,
Face,
Unknown,
}
// #endif

View File

@@ -0,0 +1,11 @@
export * from "./broad_phase";
export * from "./narrow_phase";
export * from "./shape";
export * from "./collider";
export * from "./collider_set";
export * from "./feature";
export * from "./ray";
export * from "./point";
export * from "./toi";
export * from "./interaction_groups";
export * from "./contact";

View File

@@ -0,0 +1,18 @@
/**
* Pairwise filtering using bit masks.
*
* This filtering method is based on two 16-bit values:
* - The interaction groups (the 16 left-most bits of `self.0`).
* - The interaction mask (the 16 right-most bits of `self.0`).
*
* An interaction is allowed between two filters `a` and `b` two conditions
* are met simultaneously:
* - The interaction groups of `a` has at least one bit set to `1` in common with the interaction mask of `b`.
* - The interaction groups of `b` has at least one bit set to `1` in common with the interaction mask of `a`.
* In other words, interactions are allowed between two filter iff. the following condition is met:
*
* ```
* ((a >> 16) & b) != 0 && ((b >> 16) & a) != 0
* ```
*/
export type InteractionGroups = number;

View File

@@ -0,0 +1,203 @@
import {RawNarrowPhase, RawContactManifold} from "../raw";
import {ColliderHandle} from "./collider";
import {Vector, VectorOps} from "../math";
/**
* The narrow-phase used for precise collision-detection.
*
* To avoid leaking WASM resources, this MUST be freed manually with `narrowPhase.free()`
* once you are done using it.
*/
export class NarrowPhase {
raw: RawNarrowPhase;
tempManifold: TempContactManifold;
/**
* Release the WASM memory occupied by this narrow-phase.
*/
public free() {
if (!!this.raw) {
this.raw.free();
}
this.raw = undefined;
}
constructor(raw?: RawNarrowPhase) {
this.raw = raw || new RawNarrowPhase();
this.tempManifold = new TempContactManifold(null);
}
/**
* Enumerates all the colliders potentially in contact with the given collider.
*
* @param collider1 - The second collider involved in the contact.
* @param f - Closure that will be called on each collider that is in contact with `collider1`.
*/
public contactPairsWith(
collider1: ColliderHandle,
f: (collider2: ColliderHandle) => void,
) {
this.raw.contact_pairs_with(collider1, f);
}
/**
* Enumerates all the colliders intersecting the given colliders, assuming one of them
* is a sensor.
*/
public intersectionPairsWith(
collider1: ColliderHandle,
f: (collider2: ColliderHandle) => void,
) {
this.raw.intersection_pairs_with(collider1, f);
}
/**
* Iterates through all the contact manifolds between the given pair of colliders.
*
* @param collider1 - The first collider involved in the contact.
* @param collider2 - The second collider involved in the contact.
* @param f - Closure that will be called on each contact manifold between the two colliders. If the second argument
* passed to this closure is `true`, then the contact manifold data is flipped, i.e., methods like `localNormal1`
* actually apply to the `collider2` and fields like `localNormal2` apply to the `collider1`.
*/
public contactPair(
collider1: ColliderHandle,
collider2: ColliderHandle,
f: (manifold: TempContactManifold, flipped: boolean) => void,
) {
const rawPair = this.raw.contact_pair(collider1, collider2);
if (!!rawPair) {
const flipped = rawPair.collider1() != collider1;
let i;
for (i = 0; i < rawPair.numContactManifolds(); ++i) {
this.tempManifold.raw = rawPair.contactManifold(i);
if (!!this.tempManifold.raw) {
f(this.tempManifold, flipped);
}
// SAFETY: The RawContactManifold stores a raw pointer that will be invalidated
// at the next timestep. So we must be sure to free the pair here
// to avoid unsoundness in the Rust code.
this.tempManifold.free();
}
rawPair.free();
}
}
/**
* Returns `true` if `collider1` and `collider2` intersect and at least one of them is a sensor.
* @param collider1 The first collider involved in the intersection.
* @param collider2 The second collider involved in the intersection.
*/
public intersectionPair(
collider1: ColliderHandle,
collider2: ColliderHandle,
): boolean {
return this.raw.intersection_pair(collider1, collider2);
}
}
export class TempContactManifold {
raw: RawContactManifold;
public free() {
if (!!this.raw) {
this.raw.free();
}
this.raw = undefined;
}
constructor(raw: RawContactManifold) {
this.raw = raw;
}
public normal(): Vector {
return VectorOps.fromRaw(this.raw.normal());
}
public localNormal1(): Vector {
return VectorOps.fromRaw(this.raw.local_n1());
}
public localNormal2(): Vector {
return VectorOps.fromRaw(this.raw.local_n2());
}
public subshape1(): number {
return this.raw.subshape1();
}
public subshape2(): number {
return this.raw.subshape2();
}
public numContacts(): number {
return this.raw.num_contacts();
}
public localContactPoint1(i: number): Vector | null {
return VectorOps.fromRaw(this.raw.contact_local_p1(i));
}
public localContactPoint2(i: number): Vector | null {
return VectorOps.fromRaw(this.raw.contact_local_p2(i));
}
public contactDist(i: number): number {
return this.raw.contact_dist(i);
}
public contactFid1(i: number): number {
return this.raw.contact_fid1(i);
}
public contactFid2(i: number): number {
return this.raw.contact_fid2(i);
}
public contactImpulse(i: number): number {
return this.raw.contact_impulse(i);
}
// #if DIM2
public contactTangentImpulse(i: number): number {
return this.raw.contact_tangent_impulse(i);
}
// #endif
// #if DIM3
public contactTangentImpulseX(i: number): number {
return this.raw.contact_tangent_impulse_x(i);
}
public contactTangentImpulseY(i: number): number {
return this.raw.contact_tangent_impulse_y(i);
}
// #endif
public numSolverContacts(): number {
return this.raw.num_solver_contacts();
}
public solverContactPoint(i: number): Vector {
return VectorOps.fromRaw(this.raw.solver_contact_point(i));
}
public solverContactDist(i: number): number {
return this.raw.solver_contact_dist(i);
}
public solverContactFriction(i: number): number {
return this.raw.solver_contact_friction(i);
}
public solverContactRestitution(i: number): number {
return this.raw.solver_contact_restitution(i);
}
public solverContactTangentVelocity(i: number): Vector {
return VectorOps.fromRaw(this.raw.solver_contact_tangent_velocity(i));
}
}

View File

@@ -0,0 +1,98 @@
import {Collider, ColliderHandle} from "./collider";
import {Vector, VectorOps} from "../math";
import {
RawFeatureType,
RawPointColliderProjection,
RawPointProjection,
} from "../raw";
import {FeatureType} from "./feature";
import {ColliderSet} from "./collider_set";
/**
* The projection of a point on a collider.
*/
export class PointProjection {
/**
* The projection of the point on the collider.
*/
point: Vector;
/**
* Is the point inside of the collider?
*/
isInside: boolean;
constructor(point: Vector, isInside: boolean) {
this.point = point;
this.isInside = isInside;
}
public static fromRaw(raw: RawPointProjection): PointProjection {
if (!raw) return null;
const result = new PointProjection(
VectorOps.fromRaw(raw.point()),
raw.isInside(),
);
raw.free();
return result;
}
}
/**
* The projection of a point on a collider (includes the collider handle).
*/
export class PointColliderProjection {
/**
* The collider hit by the ray.
*/
collider: Collider;
/**
* The projection of the point on the collider.
*/
point: Vector;
/**
* Is the point inside of the collider?
*/
isInside: boolean;
/**
* The type of the geometric feature the point was projected on.
*/
featureType = FeatureType.Unknown;
/**
* The id of the geometric feature the point was projected on.
*/
featureId: number | undefined = undefined;
constructor(
collider: Collider,
point: Vector,
isInside: boolean,
featureType?: FeatureType,
featureId?: number,
) {
this.collider = collider;
this.point = point;
this.isInside = isInside;
if (featureId !== undefined) this.featureId = featureId;
if (featureType !== undefined) this.featureType = featureType;
}
public static fromRaw(
colliderSet: ColliderSet,
raw: RawPointColliderProjection,
): PointColliderProjection {
if (!raw) return null;
const result = new PointColliderProjection(
colliderSet.get(raw.colliderHandle()),
VectorOps.fromRaw(raw.point()),
raw.isInside(),
raw.featureType() as number as FeatureType,
raw.featureId(),
);
raw.free();
return result;
}
}

View File

@@ -0,0 +1,192 @@
import {Vector, VectorOps} from "../math";
import {
RawFeatureType,
RawRayColliderIntersection,
RawRayColliderHit,
RawRayIntersection,
} from "../raw";
import {Collider} from "./collider";
import {FeatureType} from "./feature";
import {ColliderSet} from "./collider_set";
/**
* A ray. This is a directed half-line.
*/
export class Ray {
/**
* The starting point of the ray.
*/
public origin: Vector;
/**
* The direction of propagation of the ray.
*/
public dir: Vector;
/**
* Builds a ray from its origin and direction.
*
* @param origin - The ray's starting point.
* @param dir - The ray's direction of propagation.
*/
constructor(origin: Vector, dir: Vector) {
this.origin = origin;
this.dir = dir;
}
public pointAt(t: number): Vector {
return {
x: this.origin.x + this.dir.x * t,
y: this.origin.y + this.dir.y * t,
// #if DIM3
z: this.origin.z + this.dir.z * t,
// #endif
};
}
}
/**
* The intersection between a ray and a collider.
*/
export class RayIntersection {
/**
* The time-of-impact of the ray with the collider.
*
* The hit point is obtained from the ray's origin and direction: `origin + dir * timeOfImpact`.
*/
timeOfImpact: number;
/**
* The normal of the collider at the hit point.
*/
normal: Vector;
/**
* The type of the geometric feature the point was projected on.
*/
featureType = FeatureType.Unknown;
/**
* The id of the geometric feature the point was projected on.
*/
featureId: number | undefined = undefined;
constructor(
timeOfImpact: number,
normal: Vector,
featureType?: FeatureType,
featureId?: number,
) {
this.timeOfImpact = timeOfImpact;
this.normal = normal;
if (featureId !== undefined) this.featureId = featureId;
if (featureType !== undefined) this.featureType = featureType;
}
public static fromRaw(raw: RawRayIntersection): RayIntersection {
if (!raw) return null;
const result = new RayIntersection(
raw.time_of_impact(),
VectorOps.fromRaw(raw.normal()),
raw.featureType() as number as FeatureType,
raw.featureId(),
);
raw.free();
return result;
}
}
/**
* The intersection between a ray and a collider (includes the collider handle).
*/
export class RayColliderIntersection {
/**
* The collider hit by the ray.
*/
collider: Collider;
/**
* The time-of-impact of the ray with the collider.
*
* The hit point is obtained from the ray's origin and direction: `origin + dir * timeOfImpact`.
*/
timeOfImpact: number;
/**
* The normal of the collider at the hit point.
*/
normal: Vector;
/**
* The type of the geometric feature the point was projected on.
*/
featureType = FeatureType.Unknown;
/**
* The id of the geometric feature the point was projected on.
*/
featureId: number | undefined = undefined;
constructor(
collider: Collider,
timeOfImpact: number,
normal: Vector,
featureType?: FeatureType,
featureId?: number,
) {
this.collider = collider;
this.timeOfImpact = timeOfImpact;
this.normal = normal;
if (featureId !== undefined) this.featureId = featureId;
if (featureType !== undefined) this.featureType = featureType;
}
public static fromRaw(
colliderSet: ColliderSet,
raw: RawRayColliderIntersection,
): RayColliderIntersection {
if (!raw) return null;
const result = new RayColliderIntersection(
colliderSet.get(raw.colliderHandle()),
raw.time_of_impact(),
VectorOps.fromRaw(raw.normal()),
raw.featureType() as number as FeatureType,
raw.featureId(),
);
raw.free();
return result;
}
}
/**
* The time of impact between a ray and a collider.
*/
export class RayColliderHit {
/**
* The handle of the collider hit by the ray.
*/
collider: Collider;
/**
* The time-of-impact of the ray with the collider.
*
* The hit point is obtained from the ray's origin and direction: `origin + dir * timeOfImpact`.
*/
timeOfImpact: number;
constructor(collider: Collider, timeOfImpact: number) {
this.collider = collider;
this.timeOfImpact = timeOfImpact;
}
public static fromRaw(
colliderSet: ColliderSet,
raw: RawRayColliderHit,
): RayColliderHit {
if (!raw) return null;
const result = new RayColliderHit(
colliderSet.get(raw.colliderHandle()),
raw.timeOfImpact(),
);
raw.free();
return result;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
import {Collider} from "./collider";
import {Vector, VectorOps} from "../math";
import {RawShapeCastHit, RawColliderShapeCastHit} from "../raw";
import {ColliderSet} from "./collider_set";
/**
* The intersection between a ray and a collider.
*/
export class ShapeCastHit {
/**
* The time of impact of the two shapes.
*/
time_of_impact: number;
/**
* The local-space contact point on the first shape, at
* the time of impact.
*/
witness1: Vector;
/**
* The local-space contact point on the second shape, at
* the time of impact.
*/
witness2: Vector;
/**
* The local-space normal on the first shape, at
* the time of impact.
*/
normal1: Vector;
/**
* The local-space normal on the second shape, at
* the time of impact.
*/
normal2: Vector;
constructor(
time_of_impact: number,
witness1: Vector,
witness2: Vector,
normal1: Vector,
normal2: Vector,
) {
this.time_of_impact = time_of_impact;
this.witness1 = witness1;
this.witness2 = witness2;
this.normal1 = normal1;
this.normal2 = normal2;
}
public static fromRaw(
colliderSet: ColliderSet,
raw: RawShapeCastHit,
): ShapeCastHit {
if (!raw) return null;
const result = new ShapeCastHit(
raw.time_of_impact(),
VectorOps.fromRaw(raw.witness1()),
VectorOps.fromRaw(raw.witness2()),
VectorOps.fromRaw(raw.normal1()),
VectorOps.fromRaw(raw.normal2()),
);
raw.free();
return result;
}
}
/**
* The intersection between a ray and a collider.
*/
export class ColliderShapeCastHit extends ShapeCastHit {
/**
* The handle of the collider hit by the ray.
*/
collider: Collider;
constructor(
collider: Collider,
time_of_impact: number,
witness1: Vector,
witness2: Vector,
normal1: Vector,
normal2: Vector,
) {
super(time_of_impact, witness1, witness2, normal1, normal2);
this.collider = collider;
}
public static fromRaw(
colliderSet: ColliderSet,
raw: RawColliderShapeCastHit,
): ColliderShapeCastHit {
if (!raw) return null;
const result = new ColliderShapeCastHit(
colliderSet.get(raw.colliderHandle()),
raw.time_of_impact(),
VectorOps.fromRaw(raw.witness1()),
VectorOps.fromRaw(raw.witness2()),
VectorOps.fromRaw(raw.normal1()),
VectorOps.fromRaw(raw.normal2()),
);
raw.free();
return result;
}
}