Refactor physics

This commit is contained in:
schmelczerandras 2020-10-05 20:30:42 +02:00
parent 46a48e7c15
commit c5d97eeea6
40 changed files with 484 additions and 791 deletions

View 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];
}
}

View file

@ -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];
}
}

View 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 };
}
}

View file

@ -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 };
}
}

View file

@ -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 {

View file

@ -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 {