UNPKG

planck-js

Version:

2D JavaScript/TypeScript physics engine for cross-platform HTML5 game development

370 lines (321 loc) 10.8 kB
/* * 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 * as matrix from "../../common/Matrix"; import type { MassData } from "../../dynamics/Body"; import { AABBValue, RayCastOutput, RayCastInput, AABB } from "../AABB"; import { DistanceProxy } from "../Distance"; import { Transform, TransformValue } from "../../common/Transform"; import { Vec2, Vec2Value } from "../../common/Vec2"; import { SettingsInternal as Settings } from "../../Settings"; import { Shape } from "../Shape"; import { EdgeShape } from "./EdgeShape"; /** @internal */ const _ASSERT = typeof ASSERT === "undefined" ? false : ASSERT; /** @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 "./ChainShape" { /** @hidden @deprecated Use new keyword. */ // @ts-expect-error function ChainShape(vertices?: Vec2Value[], loop?: boolean): ChainShape; } /** * A chain shape is a free form sequence of line segments. The chain has * two-sided collision, so you can use inside and outside collision. Therefore, * you may use any winding order. Connectivity information is used to create * smooth collisions. * * WARNING: The chain will not collide properly if there are self-intersections. */ // @ts-expect-error export class ChainShape extends Shape { static TYPE = "chain" as const; /** @hidden */ m_type: "chain"; /** @hidden */ m_radius: number; /** @hidden */ m_vertices: Vec2[]; /** @hidden */ m_count: number; /** @hidden */ m_prevVertex: Vec2 | null; /** @hidden */ m_nextVertex: Vec2 | null; /** @hidden */ m_hasPrevVertex: boolean; /** @hidden */ m_hasNextVertex: boolean; /** @hidden */ m_isLoop: boolean; constructor(vertices?: Vec2Value[], loop?: boolean) { // @ts-ignore if (_CONSTRUCTOR_FACTORY && !(this instanceof ChainShape)) { return new ChainShape(vertices, loop); } super(); this.m_type = ChainShape.TYPE; this.m_radius = Settings.polygonRadius; this.m_vertices = []; this.m_count = 0; this.m_prevVertex = null; this.m_nextVertex = null; this.m_hasPrevVertex = false; this.m_hasNextVertex = false; this.m_isLoop = !!loop; if (vertices && vertices.length) { if (loop) { this._createLoop(vertices); } else { this._createChain(vertices); } } } /** @internal */ _serialize(): object { const data = { type: this.m_type, vertices: this.m_isLoop ? this.m_vertices.slice(0, this.m_vertices.length - 1) : this.m_vertices, isLoop: this.m_isLoop, hasPrevVertex: this.m_hasPrevVertex, hasNextVertex: this.m_hasNextVertex, prevVertex: null as Vec2 | null, nextVertex: null as Vec2 | null, }; if (this.m_prevVertex) { data.prevVertex = this.m_prevVertex; } if (this.m_nextVertex) { data.nextVertex = this.m_nextVertex; } return data; } /** @internal */ static _deserialize(data: any, fixture: any, restore: any): ChainShape { const vertices: Vec2[] = []; if (data.vertices) { for (let i = 0; i < data.vertices.length; i++) { vertices.push(restore(Vec2, data.vertices[i])); } } const shape = new ChainShape(vertices, data.isLoop); if (data.prevVertex) { shape.setPrevVertex(data.prevVertex); } if (data.nextVertex) { shape.setNextVertex(data.nextVertex); } return shape; } // clear() { // this.m_vertices.length = 0; // this.m_count = 0; // } getType(): "chain" { return this.m_type; } getRadius(): number { return this.m_radius; } /** * @internal * Create a loop. This automatically adjusts connectivity. * * @param vertices an array of vertices, these are copied */ _createLoop(vertices: Vec2Value[]): ChainShape { if (_ASSERT) console.assert(this.m_vertices.length == 0 && this.m_count == 0); if (_ASSERT) console.assert(vertices.length >= 3); if (vertices.length < 3) { return; } for (let i = 1; i < vertices.length; ++i) { const v1 = vertices[i - 1]; const v2 = vertices[i]; // If the code crashes here, it means your vertices are too close together. if (_ASSERT) console.assert(Vec2.distanceSquared(v1, v2) > Settings.linearSlopSquared); } this.m_vertices = []; this.m_count = vertices.length + 1; for (let i = 0; i < vertices.length; ++i) { this.m_vertices[i] = Vec2.clone(vertices[i]); } this.m_vertices[vertices.length] = Vec2.clone(vertices[0]); this.m_prevVertex = this.m_vertices[this.m_count - 2]; this.m_nextVertex = this.m_vertices[1]; this.m_hasPrevVertex = true; this.m_hasNextVertex = true; return this; } /** * @internal * Create a chain with isolated end vertices. * * @param vertices an array of vertices, these are copied */ _createChain(vertices: Vec2Value[]): ChainShape { if (_ASSERT) console.assert(this.m_vertices.length == 0 && this.m_count == 0); if (_ASSERT) console.assert(vertices.length >= 2); for (let i = 1; i < vertices.length; ++i) { const v1 = vertices[i - 1]; const v2 = vertices[i]; // If the code crashes here, it means your vertices are too close together. if (_ASSERT) console.assert(Vec2.distanceSquared(v1, v2) > Settings.linearSlopSquared); } this.m_vertices = []; this.m_count = vertices.length; for (let i = 0; i < vertices.length; ++i) { this.m_vertices[i] = Vec2.clone(vertices[i]); } this.m_prevVertex = null; this.m_nextVertex = null; this.m_hasPrevVertex = false; this.m_hasNextVertex = false; return this; } /** @hidden */ _reset(): void { if (this.m_isLoop) { this._createLoop(this.m_vertices.slice(0, this.m_vertices.length - 1)); } else { this._createChain(this.m_vertices); } } /** * Establish connectivity to a vertex that precedes the first vertex. Don't call * this for loops. */ setPrevVertex(prevVertex: Vec2): void { // todo: copy or reference this.m_prevVertex = prevVertex; this.m_hasPrevVertex = true; } getPrevVertex(): Vec2 { return this.m_prevVertex; } /** * Establish connectivity to a vertex that follows the last vertex. Don't call * this for loops. */ setNextVertex(nextVertex: Vec2): void { // todo: copy or reference this.m_nextVertex = nextVertex; this.m_hasNextVertex = true; } getNextVertex(): Vec2 { return this.m_nextVertex; } /** * @internal @deprecated Shapes should be treated as immutable. * * clone the concrete shape. */ _clone(): ChainShape { const clone = new ChainShape(); clone._createChain(this.m_vertices); clone.m_type = this.m_type; clone.m_radius = this.m_radius; clone.m_prevVertex = this.m_prevVertex; clone.m_nextVertex = this.m_nextVertex; clone.m_hasPrevVertex = this.m_hasPrevVertex; clone.m_hasNextVertex = this.m_hasNextVertex; return clone; } /** * Get the number of child primitives. */ getChildCount(): number { // edge count = vertex count - 1 return this.m_count - 1; } // Get a child edge. getChildEdge(edge: EdgeShape, childIndex: number): void { if (_ASSERT) console.assert(0 <= childIndex && childIndex < this.m_count - 1); edge.m_type = EdgeShape.TYPE; edge.m_radius = this.m_radius; edge.m_vertex1 = this.m_vertices[childIndex]; edge.m_vertex2 = this.m_vertices[childIndex + 1]; if (childIndex > 0) { edge.m_vertex0 = this.m_vertices[childIndex - 1]; edge.m_hasVertex0 = true; } else { edge.m_vertex0 = this.m_prevVertex; edge.m_hasVertex0 = this.m_hasPrevVertex; } if (childIndex < this.m_count - 2) { edge.m_vertex3 = this.m_vertices[childIndex + 2]; edge.m_hasVertex3 = true; } else { edge.m_vertex3 = this.m_nextVertex; edge.m_hasVertex3 = this.m_hasNextVertex; } } getVertex(index: number): Vec2 { if (_ASSERT) console.assert(0 <= index && index <= this.m_count); if (index < this.m_count) { return this.m_vertices[index]; } else { return this.m_vertices[0]; } } isLoop(): boolean { return this.m_isLoop; } /** * Test a point for containment in this shape. This only works for convex * shapes. * * This always return false. * * @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 { if (_ASSERT) console.assert(0 <= childIndex && childIndex < this.m_count); const edgeShape = new EdgeShape(this.getVertex(childIndex), this.getVertex(childIndex + 1)); return edgeShape.rayCast(output, input, xf, 0); } /** * 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 { if (_ASSERT) console.assert(0 <= childIndex && childIndex < this.m_count); matrix.transformVec2(v1, xf, this.getVertex(childIndex)); matrix.transformVec2(v2, xf, this.getVertex(childIndex + 1)); AABB.combinePoints(aabb, v1, v2); } /** * Compute the mass properties of this shape using its dimensions and density. * The inertia tensor is computed about the local origin. * * Chains have zero mass. * * @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.zeroVec2(massData.center); massData.I = 0.0; } computeDistanceProxy(proxy: DistanceProxy, childIndex: number): void { if (_ASSERT) console.assert(0 <= childIndex && childIndex < this.m_count); proxy.m_vertices[0] = this.getVertex(childIndex); proxy.m_vertices[1] = this.getVertex(childIndex + 1); proxy.m_count = 2; proxy.m_radius = this.m_radius; } } export const Chain = ChainShape;