Make physics more fun
This commit is contained in:
parent
155e360c8e
commit
89fafeafd3
41 changed files with 511 additions and 334 deletions
|
|
@ -1,8 +0,0 @@
|
|||
import { GameObject } from 'shared';
|
||||
import { Physical } from '../physical';
|
||||
|
||||
export interface DynamicPhysical extends Physical {
|
||||
readonly canMove: true;
|
||||
step(deltaTimeInMilliseconds: number): void;
|
||||
onCollision(other: GameObject): void;
|
||||
}
|
||||
|
|
@ -3,9 +3,9 @@ import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
|||
import { BoundingBoxList } from './bounding-box-list';
|
||||
import { BoundingBoxTree } from './bounding-box-tree';
|
||||
|
||||
import { Physical } from '../physical';
|
||||
import { StaticPhysical } from './static-physical';
|
||||
import { DynamicPhysical } from './dynamic-physical';
|
||||
import { Physical } from '../physicals/physical';
|
||||
import { StaticPhysical } from '../physicals/static-physical';
|
||||
import { DynamicPhysical } from '../physicals/dynamic-physical';
|
||||
|
||||
export class PhysicalContainer {
|
||||
private isTreeInitialized = false;
|
||||
|
|
@ -34,8 +34,8 @@ export class PhysicalContainer {
|
|||
this.dynamicBoundingBoxes.remove(object);
|
||||
}
|
||||
|
||||
public stepObjects(deltaTimeInMilliseconds: number) {
|
||||
this.dynamicBoundingBoxes.forEach((o) => o.step(deltaTimeInMilliseconds));
|
||||
public stepObjects(deltaTime: number) {
|
||||
this.dynamicBoundingBoxes.forEach((o) => o.step(deltaTime));
|
||||
}
|
||||
|
||||
public findIntersecting(box: BoundingBoxBase): Array<Physical> {
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import { Physical } from '../physical';
|
||||
import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box';
|
||||
|
||||
export interface StaticPhysical extends Physical {
|
||||
readonly canMove: false;
|
||||
readonly boundingBox: ImmutableBoundingBox;
|
||||
}
|
||||
24
backend/src/physics/functions/apply-spring-force.ts
Normal file
24
backend/src/physics/functions/apply-spring-force.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Circle } from 'shared';
|
||||
import { CirclePhysical } from '../../objects/circle-physical';
|
||||
|
||||
export const applySpringForce = (
|
||||
a: CirclePhysical | Circle,
|
||||
b: CirclePhysical | Circle,
|
||||
distance: number,
|
||||
strength: number,
|
||||
deltaTimeInSeconds: number,
|
||||
) => {
|
||||
const length = vec2.dist(a.center, b.center) - distance;
|
||||
|
||||
const abDirection = vec2.subtract(vec2.create(), b.center, a.center);
|
||||
vec2.normalize(abDirection, abDirection);
|
||||
const force = vec2.scale(abDirection, abDirection, strength * length);
|
||||
if (a instanceof CirclePhysical) {
|
||||
a.applyForce(force, deltaTimeInSeconds);
|
||||
}
|
||||
if (b instanceof CirclePhysical) {
|
||||
vec2.scale(force, force, -1);
|
||||
b.applyForce(force, deltaTimeInSeconds);
|
||||
}
|
||||
};
|
||||
7
backend/src/physics/functions/evaluate-sdf.ts
Normal file
7
backend/src/physics/functions/evaluate-sdf.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { PhysicalBase } from '../physicals/physical-base';
|
||||
|
||||
export const evaluateSdf = (target: vec2, objects: Array<PhysicalBase>) =>
|
||||
objects
|
||||
.filter((i) => i.canCollide)
|
||||
.reduce((min, i) => (min = Math.min(min, i.distance(target))), 1000);
|
||||
9
backend/src/physics/functions/force-at-position.ts
Normal file
9
backend/src/physics/functions/force-at-position.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Physical } from '../physicals/physical';
|
||||
|
||||
export const forceAtPosition = (position: vec2, objects: Array<Physical>) =>
|
||||
objects.reduce(
|
||||
(sum: vec2, o: Physical) =>
|
||||
!o.canMove ? vec2.add(sum, sum, o.getForce(position)) : sum,
|
||||
vec2.create(),
|
||||
);
|
||||
10
backend/src/physics/functions/get-bounding-box-of-circle.ts
Normal file
10
backend/src/physics/functions/get-bounding-box-of-circle.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Circle } from 'shared';
|
||||
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
||||
|
||||
export const getBoundingBoxOfCircle = (circle: Circle): BoundingBoxBase =>
|
||||
new BoundingBoxBase(
|
||||
circle.center.x - circle.radius,
|
||||
circle.center.x + circle.radius,
|
||||
circle.center.y - circle.radius,
|
||||
circle.center.y + circle.radius,
|
||||
);
|
||||
8
backend/src/physics/functions/is-circle-intersecting.ts
Normal file
8
backend/src/physics/functions/is-circle-intersecting.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { Circle } from 'shared';
|
||||
import { evaluateSdf } from './evaluate-sdf';
|
||||
import { PhysicalBase } from '../physicals/physical-base';
|
||||
|
||||
export const isCircleIntersecting = (
|
||||
circle: Circle,
|
||||
intersectors: Array<PhysicalBase>,
|
||||
): boolean => evaluateSdf(circle.center, intersectors) < circle.radius;
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { Circle, rotate90Deg } from 'shared';
|
||||
import { CirclePhysical } from '../objects/circle-physical';
|
||||
import { DynamicPhysical } from './containers/dynamic-physical';
|
||||
import { Physical } from './physical';
|
||||
import { Circle, GameObject, rotate90Deg } from 'shared';
|
||||
import { CirclePhysical } from '../../objects/circle-physical';
|
||||
import { reactsToCollision } from '../physicals/reacts-to-collision';
|
||||
import { evaluateSdf } from './evaluate-sdf';
|
||||
import { Physical } from '../physicals/physical';
|
||||
|
||||
export const moveCircle = (
|
||||
circle: CirclePhysical,
|
||||
|
|
@ -13,6 +14,7 @@ export const moveCircle = (
|
|||
hitSurface: boolean;
|
||||
normal?: vec2;
|
||||
tangent?: vec2;
|
||||
hitObject?: GameObject;
|
||||
} => {
|
||||
const nextCircle = new Circle(vec2.clone(circle.center), circle.radius);
|
||||
vec2.add(nextCircle.center, nextCircle.center, delta);
|
||||
|
|
@ -21,13 +23,7 @@ export const moveCircle = (
|
|||
(b) => b.gameObject !== circle.gameObject && b.canCollide,
|
||||
);
|
||||
|
||||
const getSdfAtPoint = (point: vec2): number => {
|
||||
return possibleIntersectors
|
||||
.filter((i) => i.canCollide)
|
||||
.reduce((min, i) => (min = Math.min(min, i.distance(point))), 1000);
|
||||
};
|
||||
|
||||
const sdfAtCenter = getSdfAtPoint(nextCircle.center);
|
||||
const sdfAtCenter = evaluateSdf(nextCircle.center, possibleIntersectors);
|
||||
|
||||
if (sdfAtCenter > nextCircle.radius) {
|
||||
circle.center = vec2.add(circle.center, circle.center, delta);
|
||||
|
|
@ -38,21 +34,26 @@ export const moveCircle = (
|
|||
};
|
||||
}
|
||||
|
||||
const intersecting = possibleIntersectors
|
||||
.filter((i) => i.canCollide && i.canMove)
|
||||
.find((i) => i.distance(nextCircle.center) < circle.radius);
|
||||
const intersecting = possibleIntersectors.find(
|
||||
(i) => i.distance(nextCircle.center) <= circle.radius,
|
||||
)!;
|
||||
|
||||
(intersecting?.gameObject as DynamicPhysical)?.onCollision(circle.gameObject);
|
||||
((circle.gameObject as unknown) as DynamicPhysical).onCollision(
|
||||
intersecting?.gameObject,
|
||||
);
|
||||
if (reactsToCollision(intersecting)) {
|
||||
intersecting.onCollision(circle.gameObject);
|
||||
}
|
||||
|
||||
if (reactsToCollision(circle)) {
|
||||
circle.onCollision(intersecting.gameObject);
|
||||
}
|
||||
|
||||
const dx =
|
||||
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(1, 0))) -
|
||||
sdfAtCenter;
|
||||
evaluateSdf(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0.01, 0)), [
|
||||
intersecting,
|
||||
]) - sdfAtCenter;
|
||||
const dy =
|
||||
getSdfAtPoint(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0, 1))) -
|
||||
sdfAtCenter;
|
||||
evaluateSdf(vec2.add(vec2.create(), nextCircle.center, vec2.fromValues(0, 0.01)), [
|
||||
intersecting,
|
||||
]) - sdfAtCenter;
|
||||
const normal = vec2.fromValues(dx, dy);
|
||||
vec2.normalize(normal, normal);
|
||||
const rotatedNormal = rotate90Deg(normal);
|
||||
|
|
@ -60,6 +61,7 @@ export const moveCircle = (
|
|||
realDelta: delta,
|
||||
hitSurface: true,
|
||||
normal,
|
||||
hitObject: intersecting?.gameObject,
|
||||
tangent:
|
||||
vec2.dot(rotatedNormal, delta) < 0
|
||||
? vec2.scale(rotatedNormal, rotatedNormal, -1)
|
||||
6
backend/src/physics/physicals/dynamic-physical.ts
Normal file
6
backend/src/physics/physicals/dynamic-physical.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { PhysicalBase } from './physical-base';
|
||||
|
||||
export interface DynamicPhysical extends PhysicalBase {
|
||||
readonly canMove: true;
|
||||
step(deltaTimeInMilliseconds: number): void;
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { GameObject } from 'shared';
|
||||
import { BoundingBoxBase } from './bounding-boxes/bounding-box-base';
|
||||
import { BoundingBoxBase } from '../bounding-boxes/bounding-box-base';
|
||||
|
||||
export interface Physical {
|
||||
export interface PhysicalBase {
|
||||
readonly canCollide: boolean;
|
||||
readonly canMove: boolean;
|
||||
readonly boundingBox: BoundingBoxBase;
|
||||
4
backend/src/physics/physicals/physical.ts
Normal file
4
backend/src/physics/physicals/physical.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { DynamicPhysical } from './dynamic-physical';
|
||||
import { StaticPhysical } from './static-physical';
|
||||
|
||||
export type Physical = StaticPhysical | DynamicPhysical;
|
||||
8
backend/src/physics/physicals/reacts-to-collision.ts
Normal file
8
backend/src/physics/physicals/reacts-to-collision.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { GameObject } from 'shared';
|
||||
|
||||
export interface ReactsToCollision {
|
||||
onCollision(other: GameObject): void;
|
||||
}
|
||||
|
||||
export const reactsToCollision = (a: any): a is ReactsToCollision =>
|
||||
a && 'onCollision' in a;
|
||||
10
backend/src/physics/physicals/static-physical.ts
Normal file
10
backend/src/physics/physicals/static-physical.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { PhysicalBase } from './physical-base';
|
||||
import { ImmutableBoundingBox } from '../bounding-boxes/immutable-bounding-box';
|
||||
import { vec2 } from 'gl-matrix';
|
||||
|
||||
export interface StaticPhysical extends PhysicalBase {
|
||||
readonly canMove: false;
|
||||
readonly boundingBox: ImmutableBoundingBox;
|
||||
|
||||
getForce(target: vec2): vec2;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue