Refactor physics
This commit is contained in:
parent
46a48e7c15
commit
c5d97eeea6
40 changed files with 484 additions and 791 deletions
94
backend/src/objects/character-physical.ts
Normal file
94
backend/src/objects/character-physical.ts
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { id, CharacterBase } from 'shared';
|
||||
|
||||
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
||||
|
||||
import { CirclePhysical } from './circle-physical';
|
||||
import { Physical } from '../physics/physical';
|
||||
|
||||
export class CharacterPhysical extends CharacterBase implements Physical {
|
||||
public readonly canCollide = true;
|
||||
public readonly isInverted = false;
|
||||
public readonly canMove = true;
|
||||
|
||||
private static readonly headOffset = vec2.fromValues(0, 40);
|
||||
private static readonly leftFootOffset = vec2.fromValues(-20, -10);
|
||||
private static readonly rightFootOffset = vec2.fromValues(20, -10);
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
id(),
|
||||
new CirclePhysical(vec2.clone(CharacterPhysical.headOffset), 50, null),
|
||||
new CirclePhysical(vec2.clone(CharacterPhysical.leftFootOffset), 20, null),
|
||||
new CirclePhysical(vec2.clone(CharacterPhysical.rightFootOffset), 20, null)
|
||||
);
|
||||
}
|
||||
|
||||
private _boundingBox?: ImmutableBoundingBox;
|
||||
|
||||
public get boundingBox(): ImmutableBoundingBox {
|
||||
if (!this._boundingBox) {
|
||||
this._boundingBox = (this.head as CirclePhysical).boundingBox;
|
||||
}
|
||||
|
||||
return this._boundingBox;
|
||||
}
|
||||
|
||||
public get gameObject(): CharacterPhysical {
|
||||
return this;
|
||||
}
|
||||
|
||||
// todo
|
||||
public distance(_: vec2): number {
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
public step() {
|
||||
const movementForce = vec2.fromValues(right - left, up - down);
|
||||
this.head.applyForce(movementForce, deltaTime);
|
||||
this.head.applyForce(settings.gravitationalForce, deltaTime);
|
||||
|
||||
const bodyCenter = vec2.sub(vec2.create(), this.head.center, this.headOffset);
|
||||
|
||||
const leftFootPositon = vec2.add(vec2.create(), bodyCenter, this.leftFootOffset);
|
||||
const rightFootPositon = vec2.add(vec2.create(), bodyCenter, this.rightFootOffset);
|
||||
|
||||
const leftFootDelta = vec2.sub(vec2.create(), this.leftFoot.center, leftFootPositon);
|
||||
const rightFootDelta = vec2.sub(
|
||||
vec2.create(),
|
||||
this.rightFoot.center,
|
||||
rightFootPositon
|
||||
);
|
||||
|
||||
vec2.scale(leftFootDelta, leftFootDelta, 0.0006);
|
||||
vec2.scale(rightFootDelta, rightFootDelta, 0.0006);
|
||||
|
||||
this.head.applyForce(leftFootDelta, deltaTime);
|
||||
this.head.applyForce(rightFootDelta, deltaTime);
|
||||
|
||||
vec2.scale(leftFootDelta, leftFootDelta, -0.5);
|
||||
vec2.scale(rightFootDelta, rightFootDelta, -0.5);
|
||||
|
||||
this.leftFoot.applyForce(movementForce, deltaTime);
|
||||
this.rightFoot.applyForce(movementForce, deltaTime);
|
||||
this.leftFoot.applyForce(leftFootDelta, deltaTime);
|
||||
this.rightFoot.applyForce(rightFootDelta, deltaTime);
|
||||
this.leftFoot.applyForce(settings.gravitationalForce, deltaTime);
|
||||
this.rightFoot.applyForce(settings.gravitationalForce, deltaTime);
|
||||
|
||||
this.head.step(deltaTime);
|
||||
this.leftFoot.step(deltaTime);
|
||||
this.rightFoot.step(deltaTime);
|
||||
|
||||
this.flashlight.center = vec2.add(
|
||||
vec2.create(),
|
||||
this.head.center,
|
||||
vec2.fromValues(0, 0)
|
||||
);
|
||||
}
|
||||
*/
|
||||
public toJSON(): any {
|
||||
const { type, id, head, leftFoot, rightFoot } = this;
|
||||
return [type, id, head, leftFoot, rightFoot];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { id, CharacterBase } from 'shared';
|
||||
|
||||
import { ImmutableBoundingBox } from '../physics/bounds/immutable-bounding-box';
|
||||
import { PhysicalGameObject } from '../physics/physical-game-object';
|
||||
import { CirclePhysics } from './circle-physics';
|
||||
|
||||
export class CharacterPhysics extends CharacterBase implements PhysicalGameObject {
|
||||
public readonly canCollide = true;
|
||||
public readonly isInverted = false;
|
||||
public readonly canMove = true;
|
||||
|
||||
constructor(head: CirclePhysics, leftFoot: CirclePhysics, rightFoot: CirclePhysics) {
|
||||
super(id(), head, leftFoot, rightFoot);
|
||||
}
|
||||
|
||||
private boundingBox?: ImmutableBoundingBox;
|
||||
|
||||
public getBoundingBox(): ImmutableBoundingBox {
|
||||
if (!this.boundingBox) {
|
||||
this.boundingBox = (this.head as CirclePhysics).boundingBox;
|
||||
(this.head as CirclePhysics).boundingBox.owner = this;
|
||||
}
|
||||
|
||||
return this.boundingBox;
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
const { type, id, head, leftFoot, rightFoot } = this;
|
||||
return [type, id, head, leftFoot, rightFoot];
|
||||
}
|
||||
}
|
||||
143
backend/src/objects/circle-physical.ts
Normal file
143
backend/src/objects/circle-physical.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Circle, clamp, GameObject, settings } from 'shared';
|
||||
import { Physical } from '../physics/physical';
|
||||
|
||||
import { BoundingBox } from '../physics/bounding-boxes/bounding-box';
|
||||
import { BoundingBoxBase } from '../physics/bounding-boxes/bounding-box-base';
|
||||
|
||||
export class CirclePhysical implements Circle, Physical {
|
||||
readonly isInverted = false;
|
||||
readonly canCollide = true;
|
||||
readonly canMove = true;
|
||||
|
||||
private _isAirborne = true;
|
||||
|
||||
private velocity = vec2.create();
|
||||
|
||||
public get isAirborne(): boolean {
|
||||
return this._isAirborne;
|
||||
}
|
||||
|
||||
private _boundingBox: BoundingBox;
|
||||
|
||||
constructor(private _center: vec2, private _radius: number, private owner: GameObject) {
|
||||
this._boundingBox = new BoundingBox(null);
|
||||
this.recalculateBoundingBox();
|
||||
}
|
||||
|
||||
public get boundingBox(): BoundingBoxBase {
|
||||
return this._boundingBox;
|
||||
}
|
||||
|
||||
public get center(): vec2 {
|
||||
return this._center;
|
||||
}
|
||||
|
||||
public set center(value: vec2) {
|
||||
this._center = value;
|
||||
this.recalculateBoundingBox();
|
||||
}
|
||||
|
||||
public get gameObject(): GameObject {
|
||||
return this.owner;
|
||||
}
|
||||
|
||||
public get radius(): number {
|
||||
return this._radius;
|
||||
}
|
||||
|
||||
public set radius(value: number) {
|
||||
this._radius = value;
|
||||
this.recalculateBoundingBox();
|
||||
}
|
||||
|
||||
public distance(target: vec2): number {
|
||||
return vec2.distance(target, this.center) - this.radius;
|
||||
}
|
||||
|
||||
public distanceBetween(target: CirclePhysical): number {
|
||||
return vec2.distance(target.center, this.center) - this.radius - target.radius;
|
||||
}
|
||||
|
||||
public areIntersecting(other: CirclePhysical): boolean {
|
||||
return other.distance(this.center) < this.radius;
|
||||
}
|
||||
|
||||
public isInside(other: CirclePhysical): boolean {
|
||||
return other.distance(this.center) < -this.radius;
|
||||
}
|
||||
|
||||
public getPerimeterPoints(count: number): Array<vec2> {
|
||||
const result: Array<vec2> = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
result.push(
|
||||
vec2.fromValues(
|
||||
Math.cos((2 * Math.PI * i) / count) * this.radius + this.center.x,
|
||||
Math.sin((2 * Math.PI * i) / count) * this.radius + this.center.y
|
||||
)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private recalculateBoundingBox() {
|
||||
this._boundingBox.xMin = this.center.x - this._radius;
|
||||
this._boundingBox.xMax = this.center.x + this._radius;
|
||||
this._boundingBox.yMin = this.center.y - this._radius;
|
||||
this._boundingBox.yMax = this.center.y + this._radius;
|
||||
}
|
||||
|
||||
public applyForce(force: vec2, timeInMilliseconds: number) {
|
||||
vec2.add(
|
||||
this.velocity,
|
||||
this.velocity,
|
||||
vec2.scale(vec2.create(), force, timeInMilliseconds)
|
||||
);
|
||||
|
||||
vec2.set(
|
||||
this.velocity,
|
||||
clamp(this.velocity.x, -settings.maxVelocityX, settings.maxVelocityX),
|
||||
clamp(this.velocity.y, -settings.maxVelocityY, settings.maxVelocityY)
|
||||
);
|
||||
}
|
||||
|
||||
public resetVelocity() {
|
||||
this.velocity = vec2.create();
|
||||
}
|
||||
|
||||
/*public step(timeInMilliseconds: number): boolean {
|
||||
vec2.scale(
|
||||
this.velocity,
|
||||
this.velocity,
|
||||
Math.pow(settings.velocityAttenuation, timeInMilliseconds)
|
||||
);
|
||||
const distance = vec2.scale(vec2.create(), this.velocity, timeInMilliseconds);
|
||||
const distanceLength = vec2.length(distance);
|
||||
const stepCount = Math.ceil(distanceLength / settings.physicsMaxStep);
|
||||
vec2.scale(distance, distance, 1 / stepCount);
|
||||
|
||||
let wasHit = false;
|
||||
|
||||
for (let i = 0; i < stepCount; i++) {
|
||||
const { normal, tangent, hitSurface } = this.physics.tryMovingDynamicCircle(
|
||||
this,
|
||||
distance
|
||||
);
|
||||
if (hitSurface) {
|
||||
vec2.scale(this.velocity, tangent, vec2.dot(tangent, this.velocity));
|
||||
wasHit = true;
|
||||
}
|
||||
|
||||
this._isAirborne = !(
|
||||
hitSurface && vec2.dot(normal, settings.gravitationalForce) < 0
|
||||
);
|
||||
}
|
||||
|
||||
return wasHit;
|
||||
}*/
|
||||
|
||||
public toJSON(): any {
|
||||
const { center, radius } = this;
|
||||
return { center, radius };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Circle } from 'shared';
|
||||
import { BoundingBox } from '../physics/bounds/bounding-box';
|
||||
import { BoundingBoxBase } from '../physics/bounds/bounding-box-base';
|
||||
|
||||
export class CirclePhysics implements Circle {
|
||||
private _boundingBox: BoundingBox;
|
||||
|
||||
constructor(private _center: vec2, private _radius: number) {
|
||||
this._boundingBox = new BoundingBox(null);
|
||||
this.recalculateBoundingBox();
|
||||
}
|
||||
|
||||
public get boundingBox(): BoundingBoxBase {
|
||||
return this._boundingBox;
|
||||
}
|
||||
|
||||
public get center(): vec2 {
|
||||
return this._center;
|
||||
}
|
||||
|
||||
public set center(value: vec2) {
|
||||
this._center = value;
|
||||
this.recalculateBoundingBox();
|
||||
}
|
||||
|
||||
public get radius(): number {
|
||||
return this._radius;
|
||||
}
|
||||
|
||||
public set radius(value: number) {
|
||||
this._radius = value;
|
||||
this.recalculateBoundingBox();
|
||||
}
|
||||
|
||||
public distance(target: vec2): number {
|
||||
return vec2.distance(target, this.center) - this.radius;
|
||||
}
|
||||
|
||||
public distanceBetween(target: CirclePhysics): number {
|
||||
return vec2.distance(target.center, this.center) - this.radius - target.radius;
|
||||
}
|
||||
|
||||
public areIntersecting(other: CirclePhysics): boolean {
|
||||
return other.distance(this.center) < this.radius;
|
||||
}
|
||||
|
||||
public isInside(other: CirclePhysics): boolean {
|
||||
return other.distance(this.center) < -this.radius;
|
||||
}
|
||||
|
||||
public getPerimeterPoints(count: number): Array<vec2> {
|
||||
const result: Array<vec2> = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
result.push(
|
||||
vec2.fromValues(
|
||||
Math.cos((2 * Math.PI * i) / count) * this.radius + this.center.x,
|
||||
Math.sin((2 * Math.PI * i) / count) * this.radius + this.center.y
|
||||
)
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private recalculateBoundingBox() {
|
||||
this._boundingBox.xMin = this.center.x - this._radius;
|
||||
this._boundingBox.xMax = this.center.x + this._radius;
|
||||
this._boundingBox.yMin = this.center.y - this._radius;
|
||||
this._boundingBox.yMax = this.center.y + this._radius;
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
const { center, radius } = this;
|
||||
return { center, radius };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { vec2, vec3 } from 'gl-matrix';
|
||||
import { LampBase, settings, id } from 'shared';
|
||||
|
||||
import { ImmutableBoundingBox } from '../physics/bounds/immutable-bounding-box';
|
||||
import { PhysicalGameObject } from '../physics/physical-game-object';
|
||||
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
||||
|
||||
export class LampPhysics extends LampBase implements PhysicalGameObject {
|
||||
import { Physical } from '../physics/physical';
|
||||
|
||||
export class LampPhysical extends LampBase implements Physical {
|
||||
public readonly canCollide = false;
|
||||
public readonly isInverted = false;
|
||||
public readonly canMove = false;
|
||||
|
|
@ -13,12 +14,11 @@ export class LampPhysics extends LampBase implements PhysicalGameObject {
|
|||
super(id(), center, color, lightness);
|
||||
}
|
||||
|
||||
private boundingBox?: ImmutableBoundingBox;
|
||||
private _boundingBox?: ImmutableBoundingBox;
|
||||
|
||||
public getBoundingBox(): ImmutableBoundingBox {
|
||||
if (!this.boundingBox) {
|
||||
this.boundingBox = new ImmutableBoundingBox(
|
||||
this,
|
||||
public get boundingBox(): ImmutableBoundingBox {
|
||||
if (!this._boundingBox) {
|
||||
this._boundingBox = new ImmutableBoundingBox(
|
||||
this.center.x - settings.lightCutoffDistance,
|
||||
this.center.x + settings.lightCutoffDistance,
|
||||
this.center.y - settings.lightCutoffDistance,
|
||||
|
|
@ -26,7 +26,16 @@ export class LampPhysics extends LampBase implements PhysicalGameObject {
|
|||
);
|
||||
}
|
||||
|
||||
return this.boundingBox;
|
||||
return this._boundingBox;
|
||||
}
|
||||
|
||||
public get gameObject(): LampPhysical {
|
||||
return this;
|
||||
}
|
||||
|
||||
// todo
|
||||
public distance(_: vec2): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { clamp01, mix, TunnelBase, id } from 'shared';
|
||||
import { PhysicalGameObject } from '../physics/physical-game-object';
|
||||
|
||||
import { ImmutableBoundingBox } from '../physics/bounds/immutable-bounding-box';
|
||||
import { ImmutableBoundingBox } from '../physics/bounding-boxes/immutable-bounding-box';
|
||||
import { StaticPhysical } from '../physics/containers/static-physical-object';
|
||||
|
||||
export class TunnelPhysics extends TunnelBase implements PhysicalGameObject {
|
||||
export class TunnelPhysical extends TunnelBase implements StaticPhysical {
|
||||
public readonly canCollide = true;
|
||||
public readonly isInverted = true;
|
||||
public readonly canMove = false;
|
||||
|
||||
private boundingBox?: ImmutableBoundingBox;
|
||||
private _boundingBox?: ImmutableBoundingBox;
|
||||
|
||||
constructor(from: vec2, to: vec2, fromRadius: number, toRadius: number) {
|
||||
super(id(), from, to, fromRadius, toRadius);
|
||||
|
|
@ -29,16 +29,20 @@ export class TunnelPhysics extends TunnelBase implements PhysicalGameObject {
|
|||
);
|
||||
}
|
||||
|
||||
public getBoundingBox(): ImmutableBoundingBox {
|
||||
if (!this.boundingBox) {
|
||||
public get boundingBox(): ImmutableBoundingBox {
|
||||
if (!this._boundingBox) {
|
||||
const xMin = Math.min(this.from.x - this.fromRadius, this.to.x - this.toRadius);
|
||||
const yMin = Math.min(this.from.y - this.fromRadius, this.to.y - this.toRadius);
|
||||
const xMax = Math.max(this.from.x + this.fromRadius, this.to.x + this.toRadius);
|
||||
const yMax = Math.max(this.from.y + this.fromRadius, this.to.y + this.toRadius);
|
||||
this.boundingBox = new ImmutableBoundingBox(this, xMin, xMax, yMin, yMax, true);
|
||||
this._boundingBox = new ImmutableBoundingBox(xMin, xMax, yMin, yMax);
|
||||
}
|
||||
|
||||
return this.boundingBox;
|
||||
return this._boundingBox;
|
||||
}
|
||||
|
||||
public get gameObject(): TunnelPhysical {
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): any {
|
||||
Loading…
Add table
Add a link
Reference in a new issue