import { System, SystemNames } from "."; import { BoundingBox, ComponentNames, Forces, Gravity, Velocity, Mass, Jump, Moment, Control, } from "../components"; import { PhysicsConstants } from "../config"; import type { Force2D, Velocity2D } from "../interfaces"; import { Game } from "../Game"; export class Physics extends System { constructor() { super(SystemNames.Physics); } public update(dt: number, game: Game): void { game.forEachEntityWithComponent(ComponentNames.Forces, (entity) => { const mass = entity.getComponent(ComponentNames.Mass).mass; const forces = entity.getComponent(ComponentNames.Forces).forces; const velocity = entity.getComponent( ComponentNames.Velocity, ).velocity; const inertia = entity.getComponent( ComponentNames.Moment, ).inertia; // F_g = mg, applied only until terminal velocity is reached if (entity.hasComponent(ComponentNames.Gravity)) { const gravity = entity.getComponent(ComponentNames.Gravity); if (velocity.dCartesian.dy <= gravity.terminalVelocity) { forces.push({ fCartesian: { fy: mass * PhysicsConstants.GRAVITY, fx: 0, }, torque: 0, }); } } // ma = Σ(F), Iα = Σ(T) const sumOfForces = forces.reduce( (accum: Force2D, { fCartesian, torque }: Force2D) => ({ fCartesian: { fx: accum.fCartesian.fx + (fCartesian?.fx ?? 0), fy: accum.fCartesian.fy + (fCartesian?.fy ?? 0), }, torque: accum.torque + (torque ?? 0), }), { fCartesian: { fx: 0, fy: 0 }, torque: 0 }, ); // integrate accelerations const [ddy, ddx] = [ sumOfForces.fCartesian.fy, sumOfForces.fCartesian.fx, ].map((x) => x / mass); velocity.dCartesian.dx += ddx * dt; velocity.dCartesian.dy += ddy * dt; velocity.dTheta += (sumOfForces.torque * dt) / inertia; // clear the forces entity.getComponent(ComponentNames.Forces).forces = []; // maybe we fell off the floor if (ddy > 0 && entity.hasComponent(ComponentNames.Jump)) { entity.getComponent(ComponentNames.Jump).canJump = false; } }); game.forEachEntityWithComponent(ComponentNames.Velocity, (entity) => { const velocityComponent: Velocity = new Velocity(); const control = entity.getComponent(ComponentNames.Control); velocityComponent.add( entity.getComponent(ComponentNames.Velocity).velocity, ); if (control) { velocityComponent.add(control.controlVelocityComponent.velocity); } const boundingBox = entity.getComponent( ComponentNames.BoundingBox, ); // integrate velocity boundingBox.center.x += velocityComponent.velocity.dCartesian.dx * dt; boundingBox.center.y += velocityComponent.velocity.dCartesian.dy * dt; boundingBox.rotation += velocityComponent.velocity.dTheta * dt; boundingBox.rotation = (boundingBox.rotation < 0 ? 360 + boundingBox.rotation : boundingBox.rotation) % 360; // clear the control velocity if (control) { control.controlVelocityComponent = new Velocity(); } }); } }