Update physics

This commit is contained in:
schmelczerandras 2020-10-02 15:12:30 +02:00
commit d9d8d03c36
7 changed files with 445 additions and 550 deletions

View file

@ -1,61 +0,0 @@
import { vec2 } from 'gl-matrix';
import { IShape } from './i-shape';
export abstract class BoundingBoxBase {
constructor(
public readonly shape: IShape,
protected _xMin: number = 0,
protected _xMax: number = 0,
protected _yMin: number = 0,
protected _yMax: number = 0
) {}
public get 0(): number {
return this._xMin;
}
public get 1(): number {
return this._xMax;
}
public get 2(): number {
return this._yMin;
}
public get 3(): number {
return this._yMax;
}
public get xMin(): number {
return this._xMin;
}
public get xMax(): number {
return this._xMax;
}
public get yMin(): number {
return this._yMin;
}
public get yMax(): number {
return this._yMax;
}
public get topLeft(): vec2 {
return vec2.fromValues(this._xMin, this._yMax);
}
public get size(): vec2 {
return vec2.fromValues(this._xMax - this._xMin, this._yMax - this._yMin);
}
public intersects(other: BoundingBoxBase): boolean {
return (
this._xMin < other._xMax &&
this._xMax > other._xMin &&
this._yMin < other._yMax &&
this._yMax > other._yMin
);
}
}

View file

@ -1,33 +0,0 @@
import { vec2 } from 'gl-matrix';
import { BoundingBoxBase } from './bounding-box-base';
import { ImmutableBoundingBox } from './immutable-bounding-box';
export class BoundingBox extends BoundingBoxBase {
public get topLeft(): vec2 {
return vec2.fromValues(this._xMin, this._yMax);
}
public set topLeft(value: vec2) {
this._xMin = value.x;
this._yMax = value.y;
}
public get size(): vec2 {
return vec2.fromValues(this._xMax - this._xMin, this._yMax - this._yMin);
}
public set size(value: vec2) {
this._xMax = this.xMin + value.x;
this._yMin = this.yMax - value.y;
}
public cloneAsImmutable(): ImmutableBoundingBox {
return new ImmutableBoundingBox(
this.shape,
this.xMin,
this.xMax,
this.yMin,
this.yMax
);
}
}

View file

@ -1,12 +0,0 @@
import { vec2 } from 'gl-matrix';
import { GameObject } from '../objects/game-object';
import { BoundingBox } from './bounding-box';
export interface IShape {
readonly boundingBox: BoundingBox;
readonly gameObject?: GameObject;
minDistance(target: vec2): number;
normal(from: vec2): vec2;
clone(): IShape;
}

View file

@ -1,3 +0,0 @@
import { BoundingBoxBase } from './bounding-box-base';
export class ImmutableBoundingBox extends BoundingBoxBase {}

View file

@ -1,110 +1,89 @@
import { mat2d, vec2 } from 'gl-matrix';
import { Circle, Drawable, DrawableDescriptor } from 'sdf-2d';
import { GameObject } from '../../objects/game-object';
import { BoundingBox } from '../bounding-box';
import { IShape } from '../i-shape';
import { CircleShape } from './circle-shape';
import { Drawable, DrawableDescriptor } from 'sdf-2d';
import { BoundingCircle } from '../../physics/bounds/bounding-circle';
export class BlobShape extends Drawable implements IShape {
export class BlobShape extends Drawable {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform struct {
vec2 headCenter;
vec2 leftFootCenter;
vec2 rightFootCenter;
float headRadius;
float footRadius;
float k;
}[BLOB_COUNT] blobs;
uniform vec2 headCenters[BLOB_COUNT];
uniform vec2 leftFootCenters[BLOB_COUNT];
uniform vec2 rightFootCenters[BLOB_COUNT];
uniform float headRadii[BLOB_COUNT];
uniform float footRadii[BLOB_COUNT];
//uniform float ks[BLOB_COUNT];
float smoothMin(float a, float b)
{
const float k = 80.0;
float res = exp2( -k*a ) + exp2( -k*b );
return -log2( res )/k;
float smoothMin(float a, float b)
{
const float k = 80.0;
float res = exp2( -k*a ) + exp2( -k*b );
return -log2( res )/k;
}
float circleDistance(vec2 circleCenter, float radius, vec2 target) {
return distance(target, circleCenter) - radius;
}
float blobMinDistance(vec2 target, out float colorIndex) {
float minDistance = 1000.0;
colorIndex = 3.0;
for (int i = 0; i < BLOB_COUNT; i++) {
float headDistance = circleDistance(headCenters[i], headRadii[i], target);
float leftFootDistance = circleDistance(leftFootCenters[i], footRadii[i], target);
float rightFootDistance = circleDistance(rightFootCenters[i], footRadii[i], target);
float res = min(
smoothMin(headDistance, leftFootDistance),
smoothMin(headDistance, rightFootDistance)
);
minDistance = min(minDistance, res);
}
float circleDistance(vec2 circleCenter, float radius) {
return distance(position, circleCenter) - radius;
}
void blobMinDistance(inout float minDistance, inout float color) {
for (int i = 0; i < BLOB_COUNT; i++) {
float headDistance = circleDistance(blobs[i].headCenter, blobs[i].headRadius);
float leftFootDistance = circleDistance(blobs[i].leftFootCenter, blobs[i].footRadius);
float rightFootDistance = circleDistance(blobs[i].rightFootCenter, blobs[i].footRadius);
float res = min(
smoothMin(headDistance, leftFootDistance),
smoothMin(headDistance, rightFootDistance)
);
minDistance = min(minDistance, res);
color = mix(1.0, color, step(distanceNdcPixelSize + SURFACE_OFFSET, res));
}
}
`,
return minDistance;
}
`,
distanceFunctionName: 'blobMinDistance',
},
uniformName: 'blobs',
propertyUniformMapping: {
footRadius: 'footRadii',
headRadius: 'headRadii',
rightFootCenter: 'rightFootCenters',
leftFootCenter: 'leftFootCenters',
headCenter: 'headCenters',
},
uniformCountMacroName: 'BLOB_COUNT',
shaderCombinationSteps: [1],
empty: new BlobShape(vec2.fromValues(0, 0)),
shaderCombinationSteps: [0, 1, 10],
empty: new BlobShape(),
};
protected head: BoundingCircle;
protected leftFoot: BoundingCircle;
protected rightFoot: BoundingCircle;
public readonly boundingCircleRadius = 100;
protected readonly headRadius = 40;
protected readonly footRadius = 15;
private readonly headOffset = vec2.fromValues(0, -15);
private readonly leftFootOffset = vec2.fromValues(-12, -60);
private readonly rightFootOffset = vec2.fromValues(12, -60);
public readonly isInverted = false;
protected boundingCircle = new CircleShape(vec2.create(), this.boundingCircleRadius);
protected head = new Circle(vec2.create(), this.headRadius);
protected leftFoot = new Circle(vec2.create(), this.footRadius);
protected rightFoot = new Circle(vec2.create(), this.footRadius);
public constructor(center: vec2, public readonly gameObject: GameObject = null) {
public constructor() {
super();
this.position = center;
const circle = new BoundingCircle(null, vec2.create(), 200);
this.setCircles([circle, circle, circle]);
}
public set position(value: vec2) {
vec2.copy(this.boundingCircle.center, value);
vec2.add(this.head.center, value, this.headOffset);
vec2.add(this.leftFoot.center, value, this.leftFootOffset);
vec2.add(this.rightFoot.center, value, this.rightFootOffset);
}
public get center(): vec2 {
return this.boundingCircle.center;
}
public get radius(): number {
return this.boundingCircle.radius;
public setCircles([head, leftFoot, rightFoot]: [
BoundingCircle,
BoundingCircle,
BoundingCircle
]) {
this.head = head;
this.leftFoot = leftFoot;
this.rightFoot = rightFoot;
}
public minDistance(target: vec2): number {
return this.boundingCircle.minDistance(target);
}
public normal(from: vec2): vec2 {
return this.boundingCircle.normal(from);
}
public get boundingBox(): BoundingBox {
return this.boundingCircle.boundingBox;
}
public isInside(target: vec2): boolean {
return this.minDistance(target) < 0;
}
public clone(): BlobShape {
return new BlobShape(this.boundingCircle.center, this.gameObject);
return Math.min(
this.head.distance(target),
this.leftFoot.distance(target),
this.rightFoot.distance(target)
);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
@ -120,8 +99,8 @@ export class BlobShape extends Drawable implements IShape {
this.rightFoot.center,
transform2d
),
headRadius: this.headRadius * transform1d,
footRadius: this.footRadius * transform1d,
headRadius: this.head.radius * transform1d,
footRadius: this.leftFoot.radius * transform1d,
};
}
}

View file

@ -1,12 +1,8 @@
import { vec2 } from 'gl-matrix';
import { Circle } from 'sdf-2d';
import { GameObject } from '../../objects/game-object';
import { BoundingBox } from '../bounding-box';
import { IShape } from '../i-shape';
export class CircleShape extends Circle implements IShape {
public readonly isInverted = false;
export class CircleShape extends Circle {
public constructor(
center = vec2.create(),
radius = 0,
@ -15,34 +11,6 @@ export class CircleShape extends Circle implements IShape {
super(center, radius);
}
public distance(target: vec2): number {
return vec2.distance(this.center, target) - this.radius;
}
public normal(from: vec2): vec2 {
const diff = vec2.subtract(vec2.create(), from, this.center);
return vec2.normalize(diff, diff);
}
public get boundingBox(): BoundingBox {
return new BoundingBox(
this,
this.center.x - this.radius,
this.center.x + this.radius,
this.center.y - this.radius,
this.center.y + this.radius
);
}
public isInside(target: vec2): boolean {
return this.distance(target) < 0;
}
public areIntersecting(other: CircleShape): boolean {
const distance = vec2.distance(this.center, other.center);
return distance < this.radius + other.radius;
}
public clone(): CircleShape {
return new CircleShape(vec2.clone(this.center), this.radius, this.gameObject);
}

View file

@ -1,81 +1,17 @@
import { vec2 } from 'gl-matrix';
import { InvertedTunnel } from 'sdf-2d';
import { clamp01 } from '../../helper/clamp';
import { rotate90Deg } from '../../helper/rotate-90-deg';
import { GameObject } from '../../objects/game-object';
import { BoundingBox } from '../bounding-box';
import { IShape } from '../i-shape';
export class TunnelShape extends InvertedTunnel implements IShape {
constructor(
readonly from: vec2,
readonly to: vec2,
readonly fromRadius: number,
readonly toRadius: number,
public readonly gameObject: GameObject = null
) {
export class TunnelShape extends InvertedTunnel {
constructor(from: vec2, to: vec2, fromRadius: number, toRadius: number) {
super(from, to, fromRadius, toRadius);
}
public get boundingBox(): 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);
return new BoundingBox(this, xMin, xMax, yMin, yMax);
}
public normal(target: vec2): vec2 {
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const toFromDelta = vec2.subtract(vec2.create(), this.to, this.from);
const h = clamp01(
vec2.dot(targetFromDelta, toFromDelta) / vec2.dot(toFromDelta, toFromDelta)
);
let diff = vec2.create();
if (h == 1) {
vec2.subtract(diff, target, this.to);
} else if (h == 0) {
vec2.subtract(diff, target, this.from);
} else {
const side = Math.sign(
toFromDelta.x * targetFromDelta.y - toFromDelta.y * targetFromDelta.x
);
const normal = rotate90Deg(toFromDelta);
vec2.normalize(normal, normal);
const translatedFrom = vec2.add(
vec2.create(),
this.from,
vec2.scale(vec2.create(), normal, side * this.fromRadius)
);
const translatedTo = vec2.add(
vec2.create(),
this.to,
vec2.scale(vec2.create(), normal, side * this.toRadius)
);
diff = rotate90Deg(vec2.subtract(vec2.create(), translatedTo, translatedFrom));
vec2.scale(diff, diff, side);
}
return vec2.normalize(diff, diff);
}
public clone(): TunnelShape {
return new TunnelShape(
vec2.clone(this.from),
vec2.clone(this.to),
this.fromRadius,
this.toRadius,
this.gameObject
this.toRadius
);
}
}