UNPKG

blaze-2d

Version:

A fast and simple WebGL 2 2D game engine written in TypeScript

393 lines 13.2 kB
import { vec2 } from "gl-matrix"; import solveImpulse from "./solvers/collision/impulse"; import solveVelocity from "./solvers/dynamics/velocity"; import CollisionsSpace from "./spaces/collisions"; import DynamicsSpace from "./spaces/dynamics"; import resetForce from "./solvers/dynamics/resetForce"; import solveForces from "./solvers/dynamics/forces"; import Texture from "../texture/texture"; import Color from "../utils/color"; import Circle from "../shapes/circle"; import preStepImpulse from "./solvers/collision/preStepImpulse"; import solvePositionImpulse from "./solvers/collision/positionImpulse"; import applyPositionImpulse from "./solvers/collision/applyPositionImpulse"; import CircleCollider from "./collider/circle"; import GJK from "./gjk"; import ConstraintSpace from "./spaces/constraints"; import preSolveConstraint from "./solvers/constraint/pre"; import solveConstraint from "./solvers/constraint/solve"; import postSolveConstraint from "./solvers/constraint/post"; const debugRGBA = { r: 0, g: 220, b: 255, a: 0.2, }; const debugTexture = new Texture(new Color(debugRGBA)); const contactRGBA = { r: 0, g: 255, b: 0, a: 0.5, }; const contactTexture = new Texture(new Color(contactRGBA)); const refRGBA = { r: 0, g: 0, b: 255, a: 0.5, }; const refTexture = new Texture(new Color(refRGBA)); const incRGBA = { r: 255, g: 0, b: 0, a: 0.5, }; const incTexture = new Texture(new Color(incRGBA)); const defaultConfig = { POINT_SIZE: 0.000001, CACHED_CONTACTS_TOLERANCE: 0.0008, RESTITUTION_THRESHOLD: 1, VELOCITY_ITERATIONS: 8, ACUMMULATE_IMPULSE: true, WARM_IMPULSE: true, POSITION_ITERATIONS: 4, POSITION_SLOP: 0.015, POSITION_DAMPING: 0.7, POSITION_WARMING: 0.8, POSITION_SCALE: 0.1, EPA_TOLERANCE: 0.005, EPA_MAX_ITERATIONS: 16, CONSTRAINT_ITERATIONS: 2, }; /** * Handles physics for bodies added to the engine. * * Raycasting can only check against objects in the phsyics engine's collisions space. * * At the start of each `update` call the static `G_CONF` variable is set to the instances `CONFIG` variable. * This allows for easy access to the physics config from other parts of the physics system. */ export default class Physics { /** * Create an {@link Physics} instance. * * @param gravity The gravitional force applied to objects in the system */ constructor(gravity) { this.CONFIG = Object.assign({}, defaultConfig); this.gravity = vec2.fromValues(0, -9.8); // spaces this.dynamicsSpace = new DynamicsSpace(this.gravity); this.collisionsSpace = new CollisionsSpace(this.gravity); this.constraintSpace = new ConstraintSpace(); this.fluids = []; /** * The amount of time in ms that the last physics step took. */ this.physicsTime = 0; /** * The time in ms that the last broadphase collision detection step took. */ this.broadphaseTime = 0; /** * The time in ms that the last narrowphase collision detection step took. */ this.narrowphaseTime = 0; /** * The time in ms that the last collision solving steps took. */ this.collisionSolveTime = 0; /** * The time in ms that the last constraint solving steps took. */ this.constraintTime = 0; /** * The time in ms that the last dynamics solving steps took. */ this.dynamicsTime = 0; /** * The time in ms that the last fluid solving steps took. */ this.fluidTime = 0; /** * Enable/disable debug tools. */ this.debug = false; if (gravity) this.setGravity(gravity); // add default solvers this.dynamicsSpace.addSolver("forces", solveForces, 1); this.dynamicsSpace.addSolver("velocity", solveVelocity, 1); this.dynamicsSpace.addSolver("reset", resetForce, 1); this.collisionsSpace.addSolver("preStepImpulse", preStepImpulse, 1); this.collisionsSpace.addSolver("positionImpulse", solvePositionImpulse, this.CONFIG.POSITION_ITERATIONS); this.collisionsSpace.addSolver("impulse", solveImpulse, this.CONFIG.VELOCITY_ITERATIONS); this.collisionsSpace.addSolver("position", applyPositionImpulse, 1); this.constraintSpace.addSolver("pre", preSolveConstraint, 1); this.constraintSpace.addSolver("solve", solveConstraint, this.CONFIG.CONSTRAINT_ITERATIONS); this.constraintSpace.addSolver("post", postSolveConstraint, 1); } /** * Steps the physics world forward by the given delta time. * * Order of updates is very important. * * @param ts The {@link TimeStep} for this update. */ update(ts) { this.physicsTime = performance.now(); // set physics config Physics.G_CONF = this.CONFIG; // step fluids this.fluidTime = performance.now(); for (const f of this.fluids) { f.update(ts.dt); } this.fluidTime = performance.now() - this.fluidTime; // step bodies // broadphase this.broadphaseTime = performance.now(); this.collisionsSpace.broadphase(); this.broadphaseTime = performance.now() - this.broadphaseTime; // narrow pahse this.narrowphaseTime = performance.now(); this.collisionsSpace.obtainManifolds(ts.dt); this.narrowphaseTime = performance.now() - this.narrowphaseTime; // integrate forces const forceTimer = performance.now(); this.dynamicsSpace.solve("forces", ts.dt); this.dynamicsTime = performance.now() - forceTimer; // solve collisions this.collisionSolveTime = performance.now(); // pre steps this.collisionsSpace.solve("preStepImpulse", ts.dt); // solve position impulse this.collisionsSpace.setSolverIterations("positionImpulse", this.CONFIG.POSITION_ITERATIONS); this.collisionsSpace.solve("positionImpulse", ts.dt); // apply position impulse this.collisionsSpace.solve("position", ts.dt); // solve collision impulse this.collisionsSpace.setSolverIterations("impulse", this.CONFIG.VELOCITY_ITERATIONS); this.collisionsSpace.solve("impulse", ts.dt); this.collisionSolveTime = performance.now() - this.collisionSolveTime; // solve constraints this.constraintTime = performance.now(); this.constraintSpace.solve("pre", ts.dt); this.constraintSpace.setSolverIterations("solve", this.CONFIG.CONSTRAINT_ITERATIONS); this.constraintSpace.solve("solve", ts.dt); this.constraintSpace.solve("post", ts.dt); this.constraintTime = performance.now() - this.constraintTime; // integrate velocities const velocityTimer = performance.now(); this.dynamicsSpace.solve("velocity", ts.dt); this.dynamicsTime += performance.now() - velocityTimer; // clear forces this.dynamicsSpace.solve("reset", ts.dt); // fire collision and trigger events this.collisionsSpace.fireEvents(); // debug if (this.debug) this.drawDebug(); // calculate physics time this.physicsTime = performance.now() - this.physicsTime; } drawDebug() { for (const obj of this.collisionsSpace.objects) { // draw entity bounding boxes (colliders) obj.collider.texture = debugTexture; obj.collider.render(); } for (const m of this.collisionsSpace.collisionManifolds) { // draw contact points for (const p of m.contactPoints) { const circle = new Circle(0.1, p.point); circle.texture = contactTexture; circle.render(); } // for (let i = 0; i < m.edges.length; i++) { // // incident edge is red, reference edge is blue // const e = m.edges[i]; // let texture = i === 0 ? incTexture : refTexture; // const circle = new Circle(0.1, e.p0); // circle.texture = texture; // circle.render(); // const circle2 = new Circle(0.1, e.p1); // circle2.texture = texture; // circle2.render(); // } } } /** * Point pick objects. * * **NOTE: Picking only checks objects in the {@link CollisionsSpace}.** * * @param point The point to pick objects at */ pick(point) { const picked = []; const aabbs = this.collisionsSpace.aabbTree.pick(point); const pointCollider = new CircleCollider(this.CONFIG.POINT_SIZE, point); for (const aabb of aabbs) { const obj = aabb.obj; if (!obj) continue; const res = GJK(obj.collider, pointCollider); if (res.collision) picked.push(obj); } return picked; } /** * raycast objects. * * **NOTE: Raycast only checks objects in the {@link CollisionsSpace}.** * * @param ray The ray to cast */ raycast(ray) { const result = []; const aabbs = this.collisionsSpace.aabbTree.raycast(ray); for (const aabb of aabbs) { const obj = aabb.obj; if (!obj) continue; const res = GJK(obj.collider, ray); if (res.collision) result.push(obj); } return result; } /** * Adds a {@link CollisionObject} to the world's collisions space. * * @param c The collision object to add */ addCollisionObj(c) { this.collisionsSpace.addObject(c); } /** * Adds a {@link RigidBody} to the world's dynamics space. * * @param obj The object to add */ addDynamicsObj(obj) { this.dynamicsSpace.addObject(obj); } /** * Adds a {@link Rigidbody} to the world's dynamics and collisions spaces. * * @param body The body to add */ addBody(body) { this.addCollisionObj(body); this.addDynamicsObj(body); } /** * Adds the given bodies to the world's dynamics and collisions spaces. * * @param bodies The bodies to add */ addBodies(...bodies) { for (const body of bodies) { this.addBody(body); } } /** * Removes a {@link CollisionObject} from the world's collisions space. * * @param c The collision object to remove */ removeCollisionObj(c) { this.collisionsSpace.removeObject(c); } /** * Removes a {@link RigidBody} from the world's dynamics space. * * @param obj The object to remove */ removeDynamicsObj(obj) { this.dynamicsSpace.removeObject(obj); } /** * Removes a {@link Rigidbody} from the world's dynamics and collisions spaces. * * @param body The body to remove */ removeBody(body) { this.removeCollisionObj(body); this.removeDynamicsObj(body); } /** * Removes the given bodies from the world's dynamics and collision spaces. * * @param bodies The bodies to remove */ removeBodies(...bodies) { for (const body of bodies) { this.removeBody(body); } } /** * Adds a {@link Constraint} to the world's constraint space. * * @param constraint The constraint to add */ addConstraint(constraint) { this.constraintSpace.addObject(constraint); } /** * Removes a {@link Constraint} from the world's constraint space. * * @param constraint The constraint to remove */ removeConstraint(constraint) { this.constraintSpace.removeObject(constraint); } /** * Adds the given fluid to the physics world. * * @param fluid The fluid to add */ addFluid(fluid) { this.fluids.push(fluid); fluid.physics = this; for (const p of fluid.particles) { this.addCollisionObj(p); } } /** * Removes the given fluid from the physics world. * * @param fluid The fluid to remove */ removeFluid(fluid) { const i = this.fluids.findIndex((f) => f === fluid); if (i === -1) return; this.fluids.splice(i, 1); this.removeBodies(...fluid.particles); } /** * Sets the gravity to use in the physics world. * * @param gravity The new gravity to use */ setGravity(gravity) { this.gravity = vec2.clone(gravity); this.dynamicsSpace.gravity = vec2.clone(gravity); this.collisionsSpace.gravity = vec2.clone(gravity); } /** * Gets the gravity vector the physics world is using. * * @returns The gravity in use */ getGravity() { return this.gravity; } } Physics.G_CONF = Object.assign({}, defaultConfig); //# sourceMappingURL=physics.js.map