UNPKG

planck-js

Version:

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

217 lines (181 loc) 5.99 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 { EPSILON } from "../../common/Math"; import { Rot } from "../../common/Rot"; import { Vec2, Vec2Value } from "../../common/Vec2"; import { Shape } from "../Shape"; import { AABBValue, RayCastInput, RayCastOutput } from "../AABB"; import { Transform, TransformValue } from "../../common/Transform"; import { MassData } from "../../dynamics/Body"; import { DistanceProxy } from "../Distance"; /** @internal */ const _CONSTRUCTOR_FACTORY = typeof CONSTRUCTOR_FACTORY === "undefined" ? false : CONSTRUCTOR_FACTORY; /** @internal */ const math_sqrt = Math.sqrt; /** @internal */ const math_PI = Math.PI; /** @internal */ const temp = matrix.vec2(0, 0); declare module "./CircleShape" { /** @hidden @deprecated Use new keyword. */ // @ts-expect-error function CircleShape(position: Vec2Value, radius?: number): CircleShape; /** @hidden @deprecated Use new keyword. */ // @ts-expect-error function CircleShape(radius?: number): CircleShape; } /** Circle shape. */ // @ts-expect-error export class CircleShape extends Shape { static TYPE = "circle" as const; /** @hidden */ m_type: "circle"; /** @hidden */ m_p: Vec2; /** @hidden */ m_radius: number; constructor(position: Vec2Value, radius?: number); constructor(radius?: number); constructor(a: any, b?: any) { // @ts-ignore if (_CONSTRUCTOR_FACTORY && !(this instanceof CircleShape)) { return new CircleShape(a, b); } super(); this.m_type = CircleShape.TYPE; this.m_p = Vec2.zero(); this.m_radius = 1; if (typeof a === "object" && Vec2.isValid(a)) { this.m_p.setVec2(a); if (typeof b === "number") { this.m_radius = b; } } else if (typeof a === "number") { this.m_radius = a; } } /** @internal */ _serialize(): object { return { type: this.m_type, p: this.m_p, radius: this.m_radius, }; } /** @internal */ static _deserialize(data: any): CircleShape { return new CircleShape(data.p, data.radius); } /** @hidden */ _reset(): void { // noop } getType(): "circle" { return this.m_type; } getRadius(): number { return this.m_radius; } getCenter(): Vec2 { return this.m_p; } /** * @internal @deprecated Shapes should be treated as immutable. * * clone the concrete shape. */ _clone(): CircleShape { const clone = new CircleShape(); clone.m_type = this.m_type; clone.m_radius = this.m_radius; clone.m_p = this.m_p.clone(); 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): boolean { const center = matrix.transformVec2(temp, xf, this.m_p); return matrix.distSqrVec2(p, center) <= this.m_radius * this.m_radius; } /** * 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 { // Collision Detection in Interactive 3D Environments by Gino van den Bergen // From Section 3.1.2 // x = s + a * r // norm(x) = radius const position = Vec2.add(xf.p, Rot.mulVec2(xf.q, this.m_p)); const s = Vec2.sub(input.p1, position); const b = Vec2.dot(s, s) - this.m_radius * this.m_radius; // Solve quadratic equation. const r = Vec2.sub(input.p2, input.p1); const c = Vec2.dot(s, r); const rr = Vec2.dot(r, r); const sigma = c * c - rr * b; // Check for negative discriminant and short segment. if (sigma < 0.0 || rr < EPSILON) { return false; } // Find the point of intersection of the line with the circle. let a = -(c + math_sqrt(sigma)); // Is the intersection point on the segment? if (0.0 <= a && a <= input.maxFraction * rr) { a /= rr; output.fraction = a; output.normal = Vec2.add(s, Vec2.mulNumVec2(a, r)); output.normal.normalize(); return true; } return false; } /** * 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 { const p = matrix.transformVec2(temp, xf, this.m_p); matrix.setVec2(aabb.lowerBound, p.x - this.m_radius, p.y - this.m_radius); matrix.setVec2(aabb.upperBound, p.x + this.m_radius, p.y + 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 = density * math_PI * this.m_radius * this.m_radius; matrix.copyVec2(massData.center, this.m_p); // inertia about the local origin massData.I = massData.mass * (0.5 * this.m_radius * this.m_radius + matrix.lengthSqrVec2(this.m_p)); } computeDistanceProxy(proxy: DistanceProxy): void { proxy.m_vertices[0] = this.m_p; proxy.m_vertices.length = 1; proxy.m_count = 1; proxy.m_radius = this.m_radius; } } export const Circle = CircleShape;