planck-js
Version:
2D JavaScript/TypeScript physics engine for cross-platform HTML5 game development
320 lines (271 loc) • 8.22 kB
text/typescript
/*
* Planck.js
*
* Copyright (c) Erin Catto, Ali Shakiba
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { SettingsInternal as Settings } from "../../Settings";
import * as matrix from "../../common/Matrix";
import { Shape } from "../Shape";
import { Transform, TransformValue } from "../../common/Transform";
import { Rot } from "../../common/Rot";
import { Vec2, Vec2Value } from "../../common/Vec2";
import { AABB, AABBValue, RayCastInput, RayCastOutput } from "../AABB";
import { MassData } from "../../dynamics/Body";
import { DistanceProxy } from "../Distance";
/** @internal */ const _CONSTRUCTOR_FACTORY = typeof CONSTRUCTOR_FACTORY === "undefined" ? false : CONSTRUCTOR_FACTORY;
/** @internal */ const v1 = matrix.vec2(0, 0);
/** @internal */ const v2 = matrix.vec2(0, 0);
declare module "./EdgeShape" {
/** @hidden @deprecated Use new keyword. */
// @ts-expect-error
function EdgeShape(v1?: Vec2Value, v2?: Vec2Value): EdgeShape;
}
/**
* A line segment (edge) shape. These can be connected in chains or loops to
* other edge shapes. The connectivity information is used to ensure correct
* contact normals.
*/
// @ts-expect-error
export class EdgeShape extends Shape {
static TYPE = "edge" as const;
/** @hidden */ m_type: "edge";
/** @hidden */ m_radius: number;
// These are the edge vertices
/** @hidden */ m_vertex1: Vec2;
/** @hidden */ m_vertex2: Vec2;
// Optional adjacent vertices. These are used for smooth collision.
// Used by chain shape.
/** @hidden */ m_vertex0: Vec2;
/** @hidden */ m_vertex3: Vec2;
/** @hidden */ m_hasVertex0: boolean;
/** @hidden */ m_hasVertex3: boolean;
constructor(v1?: Vec2Value, v2?: Vec2Value) {
// @ts-ignore
if (_CONSTRUCTOR_FACTORY && !(this instanceof EdgeShape)) {
return new EdgeShape(v1, v2);
}
super();
this.m_type = EdgeShape.TYPE;
this.m_radius = Settings.polygonRadius;
this.m_vertex1 = v1 ? Vec2.clone(v1) : Vec2.zero();
this.m_vertex2 = v2 ? Vec2.clone(v2) : Vec2.zero();
this.m_vertex0 = Vec2.zero();
this.m_vertex3 = Vec2.zero();
this.m_hasVertex0 = false;
this.m_hasVertex3 = false;
}
/** @internal */
_serialize(): object {
return {
type: this.m_type,
vertex1: this.m_vertex1,
vertex2: this.m_vertex2,
vertex0: this.m_vertex0,
vertex3: this.m_vertex3,
hasVertex0: this.m_hasVertex0,
hasVertex3: this.m_hasVertex3,
};
}
/** @internal */
static _deserialize(data: any): EdgeShape {
const shape = new EdgeShape(data.vertex1, data.vertex2);
if (shape.m_hasVertex0) {
shape.setPrevVertex(data.vertex0);
}
if (shape.m_hasVertex3) {
shape.setNextVertex(data.vertex3);
}
return shape;
}
/** @hidden */
_reset(): void {
// noop
}
getRadius(): number {
return this.m_radius;
}
getType(): "edge" {
return this.m_type;
}
/** @internal @deprecated */
setNext(v?: Vec2Value): EdgeShape {
return this.setNextVertex(v);
}
/**
* Optional next vertex, used for smooth collision.
*/
setNextVertex(v?: Vec2Value): EdgeShape {
if (v) {
this.m_vertex3.setVec2(v);
this.m_hasVertex3 = true;
} else {
this.m_vertex3.setZero();
this.m_hasVertex3 = false;
}
return this;
}
/**
* Optional next vertex, used for smooth collision.
*/
getNextVertex(): Vec2 {
return this.m_vertex3;
}
/** @internal @deprecated */
setPrev(v?: Vec2Value): EdgeShape {
return this.setPrevVertex(v);
}
/**
* Optional prev vertex, used for smooth collision.
*/
setPrevVertex(v?: Vec2Value): EdgeShape {
if (v) {
this.m_vertex0.setVec2(v);
this.m_hasVertex0 = true;
} else {
this.m_vertex0.setZero();
this.m_hasVertex0 = false;
}
return this;
}
/**
* Optional prev vertex, used for smooth collision.
*/
getPrevVertex(): Vec2 {
return this.m_vertex0;
}
/**
* Set this as an isolated edge.
*/
_set(v1: Vec2Value, v2: Vec2Value): EdgeShape {
this.m_vertex1.setVec2(v1);
this.m_vertex2.setVec2(v2);
this.m_hasVertex0 = false;
this.m_hasVertex3 = false;
return this;
}
/**
* @internal @deprecated Shapes should be treated as immutable.
*
* clone the concrete shape.
*/
_clone(): EdgeShape {
const clone = new EdgeShape();
clone.m_type = this.m_type;
clone.m_radius = this.m_radius;
clone.m_vertex1.setVec2(this.m_vertex1);
clone.m_vertex2.setVec2(this.m_vertex2);
clone.m_vertex0.setVec2(this.m_vertex0);
clone.m_vertex3.setVec2(this.m_vertex3);
clone.m_hasVertex0 = this.m_hasVertex0;
clone.m_hasVertex3 = this.m_hasVertex3;
return clone;
}
/**
* Get the number of child primitives.
*/
getChildCount(): 1 {
return 1;
}
/**
* 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.
*/
testPoint(xf: TransformValue, p: Vec2Value): false {
return false;
}
/**
* Cast a ray against a child shape.
*
* @param output The ray-cast results.
* @param input The ray-cast input parameters.
* @param xf The transform to be applied to the shape.
* @param childIndex The child shape index
*/
rayCast(output: RayCastOutput, input: RayCastInput, xf: Transform, childIndex: number): boolean {
// p = p1 + t * d
// v = v1 + s * e
// p1 + t * d = v1 + s * e
// s * e - t * d = p1 - v1
// NOT_USED(childIndex);
// Put the ray into the edge's frame of reference.
const p1 = Rot.mulTVec2(xf.q, Vec2.sub(input.p1, xf.p));
const p2 = Rot.mulTVec2(xf.q, Vec2.sub(input.p2, xf.p));
const d = Vec2.sub(p2, p1);
const v1 = this.m_vertex1;
const v2 = this.m_vertex2;
const e = Vec2.sub(v2, v1);
const normal = Vec2.neo(e.y, -e.x);
normal.normalize();
// q = p1 + t * d
// dot(normal, q - v1) = 0
// dot(normal, p1 - v1) + t * dot(normal, d) = 0
const numerator = Vec2.dot(normal, Vec2.sub(v1, p1));
const denominator = Vec2.dot(normal, d);
if (denominator == 0.0) {
return false;
}
const t = numerator / denominator;
if (t < 0.0 || input.maxFraction < t) {
return false;
}
const q = Vec2.add(p1, Vec2.mulNumVec2(t, d));
// q = v1 + s * r
// s = dot(q - v1, r) / dot(r, r)
const r = Vec2.sub(v2, v1);
const rr = Vec2.dot(r, r);
if (rr == 0.0) {
return false;
}
const s = Vec2.dot(Vec2.sub(q, v1), r) / rr;
if (s < 0.0 || 1.0 < s) {
return false;
}
output.fraction = t;
if (numerator > 0.0) {
output.normal = Rot.mulVec2(xf.q, normal).neg();
} else {
output.normal = Rot.mulVec2(xf.q, normal);
}
return true;
}
/**
* 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
*/
computeAABB(aabb: AABBValue, xf: TransformValue, childIndex: number): void {
matrix.transformVec2(v1, xf, this.m_vertex1);
matrix.transformVec2(v2, xf, this.m_vertex2);
AABB.combinePoints(aabb, v1, v2);
AABB.extend(aabb, this.m_radius);
}
/**
* 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.
*/
computeMass(massData: MassData, density?: number): void {
massData.mass = 0.0;
matrix.combine2Vec2(massData.center, 0.5, this.m_vertex1, 0.5, this.m_vertex2);
massData.I = 0.0;
}
computeDistanceProxy(proxy: DistanceProxy): void {
proxy.m_vertices[0] = this.m_vertex1;
proxy.m_vertices[1] = this.m_vertex2;
proxy.m_vertices.length = 2;
proxy.m_count = 2;
proxy.m_radius = this.m_radius;
}
}
export const Edge = EdgeShape;