Make physics more fun

This commit is contained in:
schmelczerandras 2020-10-16 11:48:52 +02:00
parent 155e360c8e
commit 89fafeafd3
41 changed files with 511 additions and 334 deletions

View file

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

View file

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

View file

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

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

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

View 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(),
);

View 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,
);

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

View file

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

View file

@ -0,0 +1,6 @@
import { PhysicalBase } from './physical-base';
export interface DynamicPhysical extends PhysicalBase {
readonly canMove: true;
step(deltaTimeInMilliseconds: number): void;
}

View file

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

View file

@ -0,0 +1,4 @@
import { DynamicPhysical } from './dynamic-physical';
import { StaticPhysical } from './static-physical';
export type Physical = StaticPhysical | DynamicPhysical;

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

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