Files
esengine/thirdparty/rapier.js/src.ts/geometry/shape.ts
2025-12-03 16:24:08 +08:00

1559 lines
43 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {Vector, VectorOps, Rotation, RotationOps} from "../math";
import {RawColliderSet, RawShape, RawShapeType} from "../raw";
import {ShapeContact} from "./contact";
import {PointProjection} from "./point";
import {Ray, RayIntersection} from "./ray";
import {ShapeCastHit} from "./toi";
import {ColliderHandle} from "./collider";
export abstract class Shape {
public abstract intoRaw(): RawShape;
/**
* The concrete type of this shape.
*/
public abstract get type(): ShapeType;
/**
* instant mode without cache
*/
public static fromRaw(
rawSet: RawColliderSet,
handle: ColliderHandle,
): Shape {
const rawType = rawSet.coShapeType(handle);
let extents: Vector;
let borderRadius: number;
let vs: Float32Array;
let indices: Uint32Array;
let halfHeight: number;
let radius: number;
let normal: Vector;
switch (rawType) {
case RawShapeType.Ball:
return new Ball(rawSet.coRadius(handle));
case RawShapeType.Cuboid:
extents = rawSet.coHalfExtents(handle);
// #if DIM2
return new Cuboid(extents.x, extents.y);
// #endif
// #if DIM3
return new Cuboid(extents.x, extents.y, extents.z);
// #endif
case RawShapeType.RoundCuboid:
extents = rawSet.coHalfExtents(handle);
borderRadius = rawSet.coRoundRadius(handle);
// #if DIM2
return new RoundCuboid(extents.x, extents.y, borderRadius);
// #endif
// #if DIM3
return new RoundCuboid(
extents.x,
extents.y,
extents.z,
borderRadius,
);
// #endif
case RawShapeType.Capsule:
halfHeight = rawSet.coHalfHeight(handle);
radius = rawSet.coRadius(handle);
return new Capsule(halfHeight, radius);
case RawShapeType.Segment:
vs = rawSet.coVertices(handle);
// #if DIM2
return new Segment(
VectorOps.new(vs[0], vs[1]),
VectorOps.new(vs[2], vs[3]),
);
// #endif
// #if DIM3
return new Segment(
VectorOps.new(vs[0], vs[1], vs[2]),
VectorOps.new(vs[3], vs[4], vs[5]),
);
// #endif
case RawShapeType.Polyline:
vs = rawSet.coVertices(handle);
indices = rawSet.coIndices(handle);
return new Polyline(vs, indices);
case RawShapeType.Triangle:
vs = rawSet.coVertices(handle);
// #if DIM2
return new Triangle(
VectorOps.new(vs[0], vs[1]),
VectorOps.new(vs[2], vs[3]),
VectorOps.new(vs[4], vs[5]),
);
// #endif
// #if DIM3
return new Triangle(
VectorOps.new(vs[0], vs[1], vs[2]),
VectorOps.new(vs[3], vs[4], vs[5]),
VectorOps.new(vs[6], vs[7], vs[8]),
);
// #endif
case RawShapeType.RoundTriangle:
vs = rawSet.coVertices(handle);
borderRadius = rawSet.coRoundRadius(handle);
// #if DIM2
return new RoundTriangle(
VectorOps.new(vs[0], vs[1]),
VectorOps.new(vs[2], vs[3]),
VectorOps.new(vs[4], vs[5]),
borderRadius,
);
// #endif
// #if DIM3
return new RoundTriangle(
VectorOps.new(vs[0], vs[1], vs[2]),
VectorOps.new(vs[3], vs[4], vs[5]),
VectorOps.new(vs[6], vs[7], vs[8]),
borderRadius,
);
// #endif
case RawShapeType.HalfSpace:
normal = VectorOps.fromRaw(rawSet.coHalfspaceNormal(handle));
return new HalfSpace(normal);
case RawShapeType.Voxels:
const vox_data = rawSet.coVoxelData(handle);
const vox_size = rawSet.coVoxelSize(handle);
return new Voxels(vox_data, vox_size);
case RawShapeType.TriMesh:
vs = rawSet.coVertices(handle);
indices = rawSet.coIndices(handle);
const tri_flags = rawSet.coTriMeshFlags(handle);
return new TriMesh(vs, indices, tri_flags);
case RawShapeType.HeightField:
const scale = rawSet.coHeightfieldScale(handle);
const heights = rawSet.coHeightfieldHeights(handle);
// #if DIM2
return new Heightfield(heights, scale);
// #endif
// #if DIM3
const nrows = rawSet.coHeightfieldNRows(handle);
const ncols = rawSet.coHeightfieldNCols(handle);
const hf_flags = rawSet.coHeightFieldFlags(handle);
return new Heightfield(nrows, ncols, heights, scale, hf_flags);
// #endif
// #if DIM2
case RawShapeType.ConvexPolygon:
vs = rawSet.coVertices(handle);
return new ConvexPolygon(vs, false);
case RawShapeType.RoundConvexPolygon:
vs = rawSet.coVertices(handle);
borderRadius = rawSet.coRoundRadius(handle);
return new RoundConvexPolygon(vs, borderRadius, false);
// #endif
// #if DIM3
case RawShapeType.ConvexPolyhedron:
vs = rawSet.coVertices(handle);
indices = rawSet.coIndices(handle);
return new ConvexPolyhedron(vs, indices);
case RawShapeType.RoundConvexPolyhedron:
vs = rawSet.coVertices(handle);
indices = rawSet.coIndices(handle);
borderRadius = rawSet.coRoundRadius(handle);
return new RoundConvexPolyhedron(vs, indices, borderRadius);
case RawShapeType.Cylinder:
halfHeight = rawSet.coHalfHeight(handle);
radius = rawSet.coRadius(handle);
return new Cylinder(halfHeight, radius);
case RawShapeType.RoundCylinder:
halfHeight = rawSet.coHalfHeight(handle);
radius = rawSet.coRadius(handle);
borderRadius = rawSet.coRoundRadius(handle);
return new RoundCylinder(halfHeight, radius, borderRadius);
case RawShapeType.Cone:
halfHeight = rawSet.coHalfHeight(handle);
radius = rawSet.coRadius(handle);
return new Cone(halfHeight, radius);
case RawShapeType.RoundCone:
halfHeight = rawSet.coHalfHeight(handle);
radius = rawSet.coRadius(handle);
borderRadius = rawSet.coRoundRadius(handle);
return new RoundCone(halfHeight, radius, borderRadius);
// #endif
default:
throw new Error("unknown shape type: " + rawType);
}
}
/**
* Computes the time of impact between two moving shapes.
* @param shapePos1 - The initial position of this sahpe.
* @param shapeRot1 - The rotation of this shape.
* @param shapeVel1 - The velocity of this shape.
* @param shape2 - The second moving shape.
* @param shapePos2 - The initial position of the second shape.
* @param shapeRot2 - The rotation of the second shape.
* @param shapeVel2 - The velocity of the second shape.
* @param targetDistance If the shape moves closer to this distance from a collider, a hit
* will be returned.
* @param maxToi - The maximum time when the impact can happen.
* @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.
* @returns If the two moving shapes collider at some point along their trajectories, this returns the
* time at which the two shape collider as well as the contact information during the impact. Returns
* `null`if the two shapes never collide along their paths.
*/
public castShape(
shapePos1: Vector,
shapeRot1: Rotation,
shapeVel1: Vector,
shape2: Shape,
shapePos2: Vector,
shapeRot2: Rotation,
shapeVel2: Vector,
targetDistance: number,
maxToi: number,
stopAtPenetration: boolean,
): ShapeCastHit | null {
let rawPos1 = VectorOps.intoRaw(shapePos1);
let rawRot1 = RotationOps.intoRaw(shapeRot1);
let rawVel1 = VectorOps.intoRaw(shapeVel1);
let rawPos2 = VectorOps.intoRaw(shapePos2);
let rawRot2 = RotationOps.intoRaw(shapeRot2);
let rawVel2 = VectorOps.intoRaw(shapeVel2);
let rawShape1 = this.intoRaw();
let rawShape2 = shape2.intoRaw();
let result = ShapeCastHit.fromRaw(
null,
rawShape1.castShape(
rawPos1,
rawRot1,
rawVel1,
rawShape2,
rawPos2,
rawRot2,
rawVel2,
targetDistance,
maxToi,
stopAtPenetration,
),
);
rawPos1.free();
rawRot1.free();
rawVel1.free();
rawPos2.free();
rawRot2.free();
rawVel2.free();
rawShape1.free();
rawShape2.free();
return result;
}
/**
* Tests if this shape intersects another shape.
*
* @param shapePos1 - The position of this shape.
* @param shapeRot1 - The rotation of this shape.
* @param shape2 - The second shape to test.
* @param shapePos2 - The position of the second shape.
* @param shapeRot2 - The rotation of the second shape.
* @returns `true` if the two shapes intersect, `false` if they dont.
*/
public intersectsShape(
shapePos1: Vector,
shapeRot1: Rotation,
shape2: Shape,
shapePos2: Vector,
shapeRot2: Rotation,
): boolean {
let rawPos1 = VectorOps.intoRaw(shapePos1);
let rawRot1 = RotationOps.intoRaw(shapeRot1);
let rawPos2 = VectorOps.intoRaw(shapePos2);
let rawRot2 = RotationOps.intoRaw(shapeRot2);
let rawShape1 = this.intoRaw();
let rawShape2 = shape2.intoRaw();
let result = rawShape1.intersectsShape(
rawPos1,
rawRot1,
rawShape2,
rawPos2,
rawRot2,
);
rawPos1.free();
rawRot1.free();
rawPos2.free();
rawRot2.free();
rawShape1.free();
rawShape2.free();
return result;
}
/**
* Computes one pair of contact points between two shapes.
*
* @param shapePos1 - The initial position of this sahpe.
* @param shapeRot1 - The rotation of this shape.
* @param shape2 - The second shape.
* @param shapePos2 - The initial position of the second shape.
* @param shapeRot2 - The rotation of the second shape.
* @param prediction - The prediction value, if the shapes are separated by a distance greater than this value, test will fail.
* @returns `null` if the shapes are separated by a distance greater than prediction, otherwise contact details. The result is given in world-space.
*/
contactShape(
shapePos1: Vector,
shapeRot1: Rotation,
shape2: Shape,
shapePos2: Vector,
shapeRot2: Rotation,
prediction: number,
): ShapeContact | null {
let rawPos1 = VectorOps.intoRaw(shapePos1);
let rawRot1 = RotationOps.intoRaw(shapeRot1);
let rawPos2 = VectorOps.intoRaw(shapePos2);
let rawRot2 = RotationOps.intoRaw(shapeRot2);
let rawShape1 = this.intoRaw();
let rawShape2 = shape2.intoRaw();
let result = ShapeContact.fromRaw(
rawShape1.contactShape(
rawPos1,
rawRot1,
rawShape2,
rawPos2,
rawRot2,
prediction,
),
);
rawPos1.free();
rawRot1.free();
rawPos2.free();
rawRot2.free();
rawShape1.free();
rawShape2.free();
return result;
}
containsPoint(
shapePos: Vector,
shapeRot: Rotation,
point: Vector,
): boolean {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawPoint = VectorOps.intoRaw(point);
let rawShape = this.intoRaw();
let result = rawShape.containsPoint(rawPos, rawRot, rawPoint);
rawPos.free();
rawRot.free();
rawPoint.free();
rawShape.free();
return result;
}
projectPoint(
shapePos: Vector,
shapeRot: Rotation,
point: Vector,
solid: boolean,
): PointProjection {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawPoint = VectorOps.intoRaw(point);
let rawShape = this.intoRaw();
let result = PointProjection.fromRaw(
rawShape.projectPoint(rawPos, rawRot, rawPoint, solid),
);
rawPos.free();
rawRot.free();
rawPoint.free();
rawShape.free();
return result;
}
intersectsRay(
ray: Ray,
shapePos: Vector,
shapeRot: Rotation,
maxToi: number,
): boolean {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawRayOrig = VectorOps.intoRaw(ray.origin);
let rawRayDir = VectorOps.intoRaw(ray.dir);
let rawShape = this.intoRaw();
let result = rawShape.intersectsRay(
rawPos,
rawRot,
rawRayOrig,
rawRayDir,
maxToi,
);
rawPos.free();
rawRot.free();
rawRayOrig.free();
rawRayDir.free();
rawShape.free();
return result;
}
castRay(
ray: Ray,
shapePos: Vector,
shapeRot: Rotation,
maxToi: number,
solid: boolean,
): number {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawRayOrig = VectorOps.intoRaw(ray.origin);
let rawRayDir = VectorOps.intoRaw(ray.dir);
let rawShape = this.intoRaw();
let result = rawShape.castRay(
rawPos,
rawRot,
rawRayOrig,
rawRayDir,
maxToi,
solid,
);
rawPos.free();
rawRot.free();
rawRayOrig.free();
rawRayDir.free();
rawShape.free();
return result;
}
castRayAndGetNormal(
ray: Ray,
shapePos: Vector,
shapeRot: Rotation,
maxToi: number,
solid: boolean,
): RayIntersection {
let rawPos = VectorOps.intoRaw(shapePos);
let rawRot = RotationOps.intoRaw(shapeRot);
let rawRayOrig = VectorOps.intoRaw(ray.origin);
let rawRayDir = VectorOps.intoRaw(ray.dir);
let rawShape = this.intoRaw();
let result = RayIntersection.fromRaw(
rawShape.castRayAndGetNormal(
rawPos,
rawRot,
rawRayOrig,
rawRayDir,
maxToi,
solid,
),
);
rawPos.free();
rawRot.free();
rawRayOrig.free();
rawRayDir.free();
rawShape.free();
return result;
}
}
// #if DIM2
/**
* An enumeration representing the type of a shape.
*/
export enum ShapeType {
Ball = 0,
Cuboid = 1,
Capsule = 2,
Segment = 3,
Polyline = 4,
Triangle = 5,
TriMesh = 6,
HeightField = 7,
// Compound = 8,
ConvexPolygon = 9,
RoundCuboid = 10,
RoundTriangle = 11,
RoundConvexPolygon = 12,
HalfSpace = 13,
Voxels = 14,
}
// #endif
// #if DIM3
/**
* An enumeration representing the type of a shape.
*/
export enum ShapeType {
Ball = 0,
Cuboid = 1,
Capsule = 2,
Segment = 3,
Polyline = 4,
Triangle = 5,
TriMesh = 6,
HeightField = 7,
// Compound = 8,
ConvexPolyhedron = 9,
Cylinder = 10,
Cone = 11,
RoundCuboid = 12,
RoundTriangle = 13,
RoundCylinder = 14,
RoundCone = 15,
RoundConvexPolyhedron = 16,
HalfSpace = 17,
Voxels = 18,
}
// NOTE: this **must** match the bits in the HeightFieldFlags on the rust side.
/**
* Flags controlling the behavior of some operations involving heightfields.
*/
export enum HeightFieldFlags {
/**
* If set, a special treatment will be applied to contact manifold calculation to eliminate
* or fix contacts normals that could lead to incorrect bumps in physics simulation (especially
* on flat surfaces).
*
* This is achieved by taking into account adjacent triangle normals when computing contact
* points for a given triangle.
*/
FIX_INTERNAL_EDGES = 0b0000_0001,
}
// #endif
// NOTE: this **must** match the TriMeshFlags on the rust side.
/**
* Flags controlling the behavior of the triangle mesh creation and of some
* operations involving triangle meshes.
*/
export enum TriMeshFlags {
// NOTE: these two flags are not really useful in JS.
//
// /**
// * If set, the half-edge topology of the trimesh will be computed if possible.
// */
// HALF_EDGE_TOPOLOGY = 0b0000_0001,
// /** If set, the half-edge topology and connected components of the trimesh will be computed if possible.
// *
// * Because of the way it is currently implemented, connected components can only be computed on
// * a mesh where the half-edge topology computation succeeds. It will no longer be the case in the
// * future once we decouple the computations.
// */
// CONNECTED_COMPONENTS = 0b0000_0010,
/**
* If set, any triangle that results in a failing half-hedge topology computation will be deleted.
*/
DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100,
/**
* If set, the trimesh will be assumed to be oriented (with outward normals).
*
* The pseudo-normals of its vertices and edges will be computed.
*/
ORIENTED = 0b0000_1000,
/**
* If set, the duplicate vertices of the trimesh will be merged.
*
* Two vertices with the exact same coordinates will share the same entry on the
* vertex buffer and the index buffer is adjusted accordingly.
*/
MERGE_DUPLICATE_VERTICES = 0b0001_0000,
/**
* If set, the triangles sharing two vertices with identical index values will be removed.
*
* Because of the way it is currently implemented, this methods implies that duplicate
* vertices will be merged. It will no longer be the case in the future once we decouple
* the computations.
*/
DELETE_DEGENERATE_TRIANGLES = 0b0010_0000,
/**
* If set, two triangles sharing three vertices with identical index values (in any order)
* will be removed.
*
* Because of the way it is currently implemented, this methods implies that duplicate
* vertices will be merged. It will no longer be the case in the future once we decouple
* the computations.
*/
DELETE_DUPLICATE_TRIANGLES = 0b0100_0000,
/**
* If set, a special treatment will be applied to contact manifold calculation to eliminate
* or fix contacts normals that could lead to incorrect bumps in physics simulation
* (especially on flat surfaces).
*
* This is achieved by taking into account adjacent triangle normals when computing contact
* points for a given triangle.
*
* /!\ NOT SUPPORTED IN THE 2D VERSION OF RAPIER.
*/
FIX_INTERNAL_EDGES = 0b1000_0000 | TriMeshFlags.MERGE_DUPLICATE_VERTICES,
}
/**
* A shape that is a sphere in 3D and a circle in 2D.
*/
export class Ball extends Shape {
readonly type = ShapeType.Ball;
/**
* The balls radius.
*/
radius: number;
/**
* Creates a new ball with the given radius.
* @param radius - The balls radius.
*/
constructor(radius: number) {
super();
this.radius = radius;
}
public intoRaw(): RawShape {
return RawShape.ball(this.radius);
}
}
export class HalfSpace extends Shape {
readonly type = ShapeType.HalfSpace;
/**
* The outward normal of the half-space.
*/
normal: Vector;
/**
* Creates a new halfspace delimited by an infinite plane.
*
* @param normal - The outward normal of the plane.
*/
constructor(normal: Vector) {
super();
this.normal = normal;
}
public intoRaw(): RawShape {
let n = VectorOps.intoRaw(this.normal);
let result = RawShape.halfspace(n);
n.free();
return result;
}
}
/**
* A shape that is a box in 3D and a rectangle in 2D.
*/
export class Cuboid extends Shape {
readonly type = ShapeType.Cuboid;
/**
* The half extent of the cuboid along each coordinate axis.
*/
halfExtents: Vector;
// #if DIM2
/**
* Creates a new 2D rectangle.
* @param hx - The half width of the rectangle.
* @param hy - The helf height of the rectangle.
*/
constructor(hx: number, hy: number) {
super();
this.halfExtents = VectorOps.new(hx, hy);
}
// #endif
// #if DIM3
/**
* Creates a new 3D cuboid.
* @param hx - The half width of the cuboid.
* @param hy - The half height of the cuboid.
* @param hz - The half depth of the cuboid.
*/
constructor(hx: number, hy: number, hz: number) {
super();
this.halfExtents = VectorOps.new(hx, hy, hz);
}
// #endif
public intoRaw(): RawShape {
// #if DIM2
return RawShape.cuboid(this.halfExtents.x, this.halfExtents.y);
// #endif
// #if DIM3
return RawShape.cuboid(
this.halfExtents.x,
this.halfExtents.y,
this.halfExtents.z,
);
// #endif
}
}
/**
* A shape that is a box in 3D and a rectangle in 2D, with round corners.
*/
export class RoundCuboid extends Shape {
readonly type = ShapeType.RoundCuboid;
/**
* The half extent of the cuboid along each coordinate axis.
*/
halfExtents: Vector;
/**
* The radius of the cuboid's round border.
*/
borderRadius: number;
// #if DIM2
/**
* Creates a new 2D rectangle.
* @param hx - The half width of the rectangle.
* @param hy - The helf height of the rectangle.
* @param borderRadius - The radius of the borders of this cuboid. This will
* effectively increase the half-extents of the cuboid by this radius.
*/
constructor(hx: number, hy: number, borderRadius: number) {
super();
this.halfExtents = VectorOps.new(hx, hy);
this.borderRadius = borderRadius;
}
// #endif
// #if DIM3
/**
* Creates a new 3D cuboid.
* @param hx - The half width of the cuboid.
* @param hy - The half height of the cuboid.
* @param hz - The half depth of the cuboid.
* @param borderRadius - The radius of the borders of this cuboid. This will
* effectively increase the half-extents of the cuboid by this radius.
*/
constructor(hx: number, hy: number, hz: number, borderRadius: number) {
super();
this.halfExtents = VectorOps.new(hx, hy, hz);
this.borderRadius = borderRadius;
}
// #endif
public intoRaw(): RawShape {
// #if DIM2
return RawShape.roundCuboid(
this.halfExtents.x,
this.halfExtents.y,
this.borderRadius,
);
// #endif
// #if DIM3
return RawShape.roundCuboid(
this.halfExtents.x,
this.halfExtents.y,
this.halfExtents.z,
this.borderRadius,
);
// #endif
}
}
/**
* A shape that is a capsule.
*/
export class Capsule extends Shape {
readonly type = ShapeType.Capsule;
/**
* The radius of the capsule's basis.
*/
radius: number;
/**
* The capsule's half height, along the `y` axis.
*/
halfHeight: number;
/**
* Creates a new capsule with the given radius and half-height.
* @param halfHeight - The balls half-height along the `y` axis.
* @param radius - The balls radius.
*/
constructor(halfHeight: number, radius: number) {
super();
this.halfHeight = halfHeight;
this.radius = radius;
}
public intoRaw(): RawShape {
return RawShape.capsule(this.halfHeight, this.radius);
}
}
/**
* A shape that is a segment.
*/
export class Segment extends Shape {
readonly type = ShapeType.Segment;
/**
* The first point of the segment.
*/
a: Vector;
/**
* The second point of the segment.
*/
b: Vector;
/**
* Creates a new segment shape.
* @param a - The first point of the segment.
* @param b - The second point of the segment.
*/
constructor(a: Vector, b: Vector) {
super();
this.a = a;
this.b = b;
}
public intoRaw(): RawShape {
let ra = VectorOps.intoRaw(this.a);
let rb = VectorOps.intoRaw(this.b);
let result = RawShape.segment(ra, rb);
ra.free();
rb.free();
return result;
}
}
/**
* A shape that is a segment.
*/
export class Triangle extends Shape {
readonly type = ShapeType.Triangle;
/**
* The first point of the triangle.
*/
a: Vector;
/**
* The second point of the triangle.
*/
b: Vector;
/**
* The second point of the triangle.
*/
c: Vector;
/**
* Creates a new triangle shape.
*
* @param a - The first point of the triangle.
* @param b - The second point of the triangle.
* @param c - The third point of the triangle.
*/
constructor(a: Vector, b: Vector, c: Vector) {
super();
this.a = a;
this.b = b;
this.c = c;
}
public intoRaw(): RawShape {
let ra = VectorOps.intoRaw(this.a);
let rb = VectorOps.intoRaw(this.b);
let rc = VectorOps.intoRaw(this.c);
let result = RawShape.triangle(ra, rb, rc);
ra.free();
rb.free();
rc.free();
return result;
}
}
/**
* A shape that is a triangle with round borders and a non-zero thickness.
*/
export class RoundTriangle extends Shape {
readonly type = ShapeType.RoundTriangle;
/**
* The first point of the triangle.
*/
a: Vector;
/**
* The second point of the triangle.
*/
b: Vector;
/**
* The second point of the triangle.
*/
c: Vector;
/**
* The radius of the triangles's rounded edges and vertices.
* In 3D, this is also equal to half the thickness of the round triangle.
*/
borderRadius: number;
/**
* Creates a new triangle shape with round corners.
*
* @param a - The first point of the triangle.
* @param b - The second point of the triangle.
* @param c - The third point of the triangle.
* @param borderRadius - The radius of the borders of this triangle. In 3D,
* this is also equal to half the thickness of the triangle.
*/
constructor(a: Vector, b: Vector, c: Vector, borderRadius: number) {
super();
this.a = a;
this.b = b;
this.c = c;
this.borderRadius = borderRadius;
}
public intoRaw(): RawShape {
let ra = VectorOps.intoRaw(this.a);
let rb = VectorOps.intoRaw(this.b);
let rc = VectorOps.intoRaw(this.c);
let result = RawShape.roundTriangle(ra, rb, rc, this.borderRadius);
ra.free();
rb.free();
rc.free();
return result;
}
}
/**
* A shape that is a triangle mesh.
*/
export class Polyline extends Shape {
readonly type = ShapeType.Polyline;
/**
* The vertices of the polyline.
*/
vertices: Float32Array;
/**
* The indices of the segments.
*/
indices: Uint32Array;
/**
* Creates a new polyline shape.
*
* @param vertices - The coordinates of the polyline's vertices.
* @param indices - The indices of the polyline's segments. If this is `null` or not provided, then
* the vertices are assumed to form a line strip.
*/
constructor(vertices: Float32Array, indices?: Uint32Array) {
super();
this.vertices = vertices;
this.indices = indices ?? new Uint32Array(0);
}
public intoRaw(): RawShape {
return RawShape.polyline(this.vertices, this.indices);
}
}
/**
* A shape made of voxels.
*/
export class Voxels extends Shape {
readonly type = ShapeType.Voxels;
/**
* The points or grid coordinates used to initialize the voxels.
*/
data: Float32Array | Int32Array;
/**
* The dimensions of each voxel.
*/
voxelSize: Vector;
/**
* Creates a new shape made of voxels.
*
* @param data - Defines the set of voxels. If this is a `Int32Array` then
* each voxel is defined from its (signed) grid coordinates,
* with 3 (resp 2) contiguous integers per voxel in 3D (resp 2D).
* If this is a `Float32Array`, each voxel will be such that
* they contain at least one point from this array (where each
* point is defined from 3 (resp 2) contiguous numbers per point
* in 3D (resp 2D).
* @param voxelSize - The size of each voxel.
*/
constructor(data: Float32Array | Int32Array, voxelSize: Vector) {
super();
this.data = data;
this.voxelSize = voxelSize;
}
public intoRaw(): RawShape {
let voxelSize = VectorOps.intoRaw(this.voxelSize);
let result;
if (this.data instanceof Int32Array) {
result = RawShape.voxels(voxelSize, this.data);
} else {
result = RawShape.voxelsFromPoints(voxelSize, this.data);
}
voxelSize.free();
return result;
}
}
/**
* A shape that is a triangle mesh.
*/
export class TriMesh extends Shape {
readonly type = ShapeType.TriMesh;
/**
* The vertices of the triangle mesh.
*/
vertices: Float32Array;
/**
* The indices of the triangles.
*/
indices: Uint32Array;
/**
* The triangle mesh flags.
*/
flags: TriMeshFlags;
/**
* Creates a new triangle mesh shape.
*
* @param vertices - The coordinates of the triangle mesh's vertices.
* @param indices - The indices of the triangle mesh's triangles.
*/
constructor(
vertices: Float32Array,
indices: Uint32Array,
flags?: TriMeshFlags,
) {
super();
this.vertices = vertices;
this.indices = indices;
this.flags = flags;
}
public intoRaw(): RawShape {
return RawShape.trimesh(this.vertices, this.indices, this.flags);
}
}
// #if DIM2
/**
* A shape that is a convex polygon.
*/
export class ConvexPolygon extends Shape {
readonly type = ShapeType.ConvexPolygon;
/**
* The vertices of the convex polygon.
*/
vertices: Float32Array;
/**
* Do we want to assume the vertices already form a convex hull?
*/
skipConvexHullComputation: boolean;
/**
* Creates a new convex polygon shape.
*
* @param vertices - The coordinates of the convex polygon's vertices.
* @param skipConvexHullComputation - If set to `true`, the input points will
* be assumed to form a convex polyline and no convex-hull computation will
* be done automatically.
*/
constructor(vertices: Float32Array, skipConvexHullComputation: boolean) {
super();
this.vertices = vertices;
this.skipConvexHullComputation = !!skipConvexHullComputation;
}
public intoRaw(): RawShape {
if (this.skipConvexHullComputation) {
return RawShape.convexPolyline(this.vertices);
} else {
return RawShape.convexHull(this.vertices);
}
}
}
/**
* A shape that is a convex polygon.
*/
export class RoundConvexPolygon extends Shape {
readonly type = ShapeType.RoundConvexPolygon;
/**
* The vertices of the convex polygon.
*/
vertices: Float32Array;
/**
* Do we want to assume the vertices already form a convex hull?
*/
skipConvexHullComputation: boolean;
/**
* The radius of the convex polygon's rounded edges and vertices.
*/
borderRadius: number;
/**
* Creates a new convex polygon shape.
*
* @param vertices - The coordinates of the convex polygon's vertices.
* @param borderRadius - The radius of the borders of this convex polygon.
* @param skipConvexHullComputation - If set to `true`, the input points will
* be assumed to form a convex polyline and no convex-hull computation will
* be done automatically.
*/
constructor(
vertices: Float32Array,
borderRadius: number,
skipConvexHullComputation: boolean,
) {
super();
this.vertices = vertices;
this.borderRadius = borderRadius;
this.skipConvexHullComputation = !!skipConvexHullComputation;
}
public intoRaw(): RawShape {
if (this.skipConvexHullComputation) {
return RawShape.roundConvexPolyline(
this.vertices,
this.borderRadius,
);
} else {
return RawShape.roundConvexHull(this.vertices, this.borderRadius);
}
}
}
/**
* A shape that is a heightfield.
*/
export class Heightfield extends Shape {
readonly type = ShapeType.HeightField;
/**
* The heights of the heightfield, along its local `y` axis.
*/
heights: Float32Array;
/**
* The heightfield's length along its local `x` axis.
*/
scale: Vector;
/**
* Creates a new heightfield shape.
*
* @param heights - The heights of the heightfield, along its local `y` axis.
* @param scale - The scale factor applied to the heightfield.
*/
constructor(heights: Float32Array, scale: Vector) {
super();
this.heights = heights;
this.scale = scale;
}
public intoRaw(): RawShape {
let rawScale = VectorOps.intoRaw(this.scale);
let rawShape = RawShape.heightfield(this.heights, rawScale);
rawScale.free();
return rawShape;
}
}
// #endif
// #if DIM3
/**
* A shape that is a convex polygon.
*/
export class ConvexPolyhedron extends Shape {
readonly type = ShapeType.ConvexPolyhedron;
/**
* The vertices of the convex polygon.
*/
vertices: Float32Array;
/**
* The indices of the convex polygon.
*/
indices?: Uint32Array | null;
/**
* Creates a new convex polygon shape.
*
* @param vertices - The coordinates of the convex polygon's vertices.
* @param indices - The index buffer of this convex mesh. If this is `null`
* or `undefined`, the convex-hull of the input vertices will be computed
* automatically. Otherwise, it will be assumed that the mesh you provide
* is already convex.
*/
constructor(vertices: Float32Array, indices?: Uint32Array | null) {
super();
this.vertices = vertices;
this.indices = indices;
}
public intoRaw(): RawShape {
if (!!this.indices) {
return RawShape.convexMesh(this.vertices, this.indices);
} else {
return RawShape.convexHull(this.vertices);
}
}
}
/**
* A shape that is a convex polygon.
*/
export class RoundConvexPolyhedron extends Shape {
readonly type = ShapeType.RoundConvexPolyhedron;
/**
* The vertices of the convex polygon.
*/
vertices: Float32Array;
/**
* The indices of the convex polygon.
*/
indices?: Uint32Array;
/**
* The radius of the convex polyhedron's rounded edges and vertices.
*/
borderRadius: number;
/**
* Creates a new convex polygon shape.
*
* @param vertices - The coordinates of the convex polygon's vertices.
* @param indices - The index buffer of this convex mesh. If this is `null`
* or `undefined`, the convex-hull of the input vertices will be computed
* automatically. Otherwise, it will be assumed that the mesh you provide
* is already convex.
* @param borderRadius - The radius of the borders of this convex polyhedron.
*/
constructor(
vertices: Float32Array,
indices: Uint32Array | null | undefined,
borderRadius: number,
) {
super();
this.vertices = vertices;
this.indices = indices;
this.borderRadius = borderRadius;
}
public intoRaw(): RawShape {
if (!!this.indices) {
return RawShape.roundConvexMesh(
this.vertices,
this.indices,
this.borderRadius,
);
} else {
return RawShape.roundConvexHull(this.vertices, this.borderRadius);
}
}
}
/**
* A shape that is a heightfield.
*/
export class Heightfield extends Shape {
readonly type = ShapeType.HeightField;
/**
* The number of rows in the heights matrix.
*/
nrows: number;
/**
* The number of columns in the heights matrix.
*/
ncols: number;
/**
* The heights of the heightfield along its local `y` axis,
* provided as a matrix stored in column-major order.
*/
heights: Float32Array;
/**
* The dimensions of the heightfield's local `x,z` plane.
*/
scale: Vector;
/**
* Flags applied to the heightfield.
*/
flags: HeightFieldFlags;
/**
* Creates a new heightfield shape.
*
* @param nrows The number of rows in the heights matrix.
* @param ncols - The number of columns in the heights matrix.
* @param heights - The heights of the heightfield along its local `y` axis,
* provided as a matrix stored in column-major order.
* @param scale - The dimensions of the heightfield's local `x,z` plane.
*/
constructor(
nrows: number,
ncols: number,
heights: Float32Array,
scale: Vector,
flags?: HeightFieldFlags,
) {
super();
this.nrows = nrows;
this.ncols = ncols;
this.heights = heights;
this.scale = scale;
this.flags = flags;
}
public intoRaw(): RawShape {
let rawScale = VectorOps.intoRaw(this.scale);
let rawShape = RawShape.heightfield(
this.nrows,
this.ncols,
this.heights,
rawScale,
this.flags,
);
rawScale.free();
return rawShape;
}
}
/**
* A shape that is a 3D cylinder.
*/
export class Cylinder extends Shape {
readonly type = ShapeType.Cylinder;
/**
* The radius of the cylinder's basis.
*/
radius: number;
/**
* The cylinder's half height, along the `y` axis.
*/
halfHeight: number;
/**
* Creates a new cylinder with the given radius and half-height.
* @param halfHeight - The balls half-height along the `y` axis.
* @param radius - The balls radius.
*/
constructor(halfHeight: number, radius: number) {
super();
this.halfHeight = halfHeight;
this.radius = radius;
}
public intoRaw(): RawShape {
return RawShape.cylinder(this.halfHeight, this.radius);
}
}
/**
* A shape that is a 3D cylinder with round corners.
*/
export class RoundCylinder extends Shape {
readonly type = ShapeType.RoundCylinder;
/**
* The radius of the cylinder's basis.
*/
radius: number;
/**
* The cylinder's half height, along the `y` axis.
*/
halfHeight: number;
/**
* The radius of the cylinder's rounded edges and vertices.
*/
borderRadius: number;
/**
* Creates a new cylinder with the given radius and half-height.
* @param halfHeight - The balls half-height along the `y` axis.
* @param radius - The balls radius.
* @param borderRadius - The radius of the borders of this cylinder.
*/
constructor(halfHeight: number, radius: number, borderRadius: number) {
super();
this.borderRadius = borderRadius;
this.halfHeight = halfHeight;
this.radius = radius;
}
public intoRaw(): RawShape {
return RawShape.roundCylinder(
this.halfHeight,
this.radius,
this.borderRadius,
);
}
}
/**
* A shape that is a 3D cone.
*/
export class Cone extends Shape {
readonly type = ShapeType.Cone;
/**
* The radius of the cone's basis.
*/
radius: number;
/**
* The cone's half height, along the `y` axis.
*/
halfHeight: number;
/**
* Creates a new cone with the given radius and half-height.
* @param halfHeight - The balls half-height along the `y` axis.
* @param radius - The balls radius.
*/
constructor(halfHeight: number, radius: number) {
super();
this.halfHeight = halfHeight;
this.radius = radius;
}
public intoRaw(): RawShape {
return RawShape.cone(this.halfHeight, this.radius);
}
}
/**
* A shape that is a 3D cone with round corners.
*/
export class RoundCone extends Shape {
readonly type = ShapeType.RoundCone;
/**
* The radius of the cone's basis.
*/
radius: number;
/**
* The cone's half height, along the `y` axis.
*/
halfHeight: number;
/**
* The radius of the cylinder's rounded edges and vertices.
*/
borderRadius: number;
/**
* Creates a new cone with the given radius and half-height.
* @param halfHeight - The balls half-height along the `y` axis.
* @param radius - The balls radius.
* @param borderRadius - The radius of the borders of this cone.
*/
constructor(halfHeight: number, radius: number, borderRadius: number) {
super();
this.halfHeight = halfHeight;
this.radius = radius;
this.borderRadius = borderRadius;
}
public intoRaw(): RawShape {
return RawShape.roundCone(
this.halfHeight,
this.radius,
this.borderRadius,
);
}
}
// #endif