Start using the sdf-2d library

This commit is contained in:
schmelczerandras 2020-09-21 09:42:00 +02:00
commit 1b7dee4be0
5 changed files with 314 additions and 2119 deletions

View file

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

View file

@ -0,0 +1,127 @@
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';
export class BlobShape extends Drawable implements IShape {
public static descriptor: DrawableDescriptor = {
sdf: {
shader: `
uniform struct {
vec2 headCenter;
vec2 leftFootCenter;
vec2 rightFootCenter;
float headRadius;
float footRadius;
float k;
}[BLOB_COUNT] blobs;
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) {
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));
}
}
`,
distanceFunctionName: 'blobMinDistance',
},
uniformName: 'blobs',
uniformCountMacroName: 'BLOB_COUNT',
shaderCombinationSteps: [1],
empty: new BlobShape(vec2.fromValues(0, 0)),
};
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) {
super();
this.position = center;
}
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 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);
}
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
return {
headCenter: vec2.transformMat2d(vec2.create(), this.head.center, transform2d),
leftFootCenter: vec2.transformMat2d(
vec2.create(),
this.leftFoot.center,
transform2d
),
rightFootCenter: vec2.transformMat2d(
vec2.create(),
this.rightFoot.center,
transform2d
),
headRadius: this.headRadius * transform1d,
footRadius: this.footRadius * transform1d,
};
}
}

View file

@ -1,60 +0,0 @@
import { vec2 } from 'gl-matrix';
import { GameObject } from '../../objects/game-object';
import { BoundingBox } from '../bounding-box';
import { IShape } from '../i-shape';
import { Circle } from './circle';
export class Blob implements IShape {
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 Circle(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) {
this.position = center;
}
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 distance(target: vec2): number {
return this.boundingCircle.distance(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.distance(target) < 0;
}
public clone(): Blob {
return new Blob(this.boundingCircle.center, this.gameObject);
}
}

View file

@ -0,0 +1,49 @@
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;
public constructor(
center = vec2.create(),
radius = 0,
public readonly gameObject: GameObject = null
) {
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,24 +1,20 @@
import { vec2 } from 'gl-matrix';
import { InvertedTunnel } from 'sdf-2d';
import { clamp01 } from '../../helper/clamp';
import { mix } from '../../helper/mix';
import { rotate90Deg } from '../../helper/rotate-90-deg';
import { GameObject } from '../../objects/game-object';
import { BoundingBox } from '../bounding-box';
import { IShape } from '../i-shape';
export default class TunnelShape implements IShape {
public readonly isInverted = true;
public readonly toFromDelta: vec2;
export class TunnelShape extends InvertedTunnel implements IShape {
constructor(
public readonly from: vec2,
public readonly to: vec2,
public readonly fromRadius: number,
public readonly toRadius: number,
readonly from: vec2,
readonly to: vec2,
readonly fromRadius: number,
readonly toRadius: number,
public readonly gameObject: GameObject = null
) {
this.toFromDelta = vec2.subtract(vec2.create(), to, from);
super(from, to, fromRadius, toRadius);
}
public get boundingBox(): BoundingBox {
@ -33,9 +29,10 @@ export default class TunnelShape implements IShape {
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, this.toFromDelta) /
vec2.dot(this.toFromDelta, this.toFromDelta)
vec2.dot(targetFromDelta, toFromDelta) / vec2.dot(toFromDelta, toFromDelta)
);
let diff = vec2.create();
@ -46,10 +43,10 @@ export default class TunnelShape implements IShape {
vec2.subtract(diff, target, this.from);
} else {
const side = Math.sign(
this.toFromDelta.x * targetFromDelta.y - this.toFromDelta.y * targetFromDelta.x
toFromDelta.x * targetFromDelta.y - toFromDelta.y * targetFromDelta.x
);
const normal = rotate90Deg(this.toFromDelta);
const normal = rotate90Deg(toFromDelta);
vec2.normalize(normal, normal);
const translatedFrom = vec2.add(
@ -72,20 +69,6 @@ export default class TunnelShape implements IShape {
return vec2.normalize(diff, diff);
}
public distance(target: vec2): number {
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01(
vec2.dot(targetFromDelta, this.toFromDelta) /
vec2.dot(this.toFromDelta, this.toFromDelta)
);
return (
vec2.distance(targetFromDelta, vec2.scale(vec2.create(), this.toFromDelta, h)) -
mix(this.fromRadius, this.toRadius, h)
);
}
public clone(): TunnelShape {
return new TunnelShape(
vec2.clone(this.from),