Update physics
This commit is contained in:
parent
1b7dee4be0
commit
d9d8d03c36
30 changed files with 447 additions and 552 deletions
|
|
@ -23,6 +23,7 @@
|
||||||
"unused-imports/no-unused-imports-ts": "error",
|
"unused-imports/no-unused-imports-ts": "error",
|
||||||
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "off"
|
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@
|
||||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
|
|
||||||
<title>Test</title>
|
<title>Decla.red</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript><h1>Javascript is required for this website.</h1></noscript>
|
<noscript>Javascript is required for this website.</noscript>
|
||||||
<div id="overlay"></div>
|
<div id="overlay"></div>
|
||||||
<canvas id="main"></canvas>
|
<canvas id="main"></canvas>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -3,123 +3,9 @@ import { Game } from './scripts/game';
|
||||||
import { Random } from './scripts/helper/random';
|
import { Random } from './scripts/helper/random';
|
||||||
import './styles/main.scss';
|
import './styles/main.scss';
|
||||||
|
|
||||||
glMatrix.setMatrixArrayType(
|
glMatrix.setMatrixArrayType(Array);
|
||||||
Array
|
|
||||||
); /*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zeroes.forEach((z) => {
|
const main = async () => {
|
||||||
const blueIndex = z.y * (width * 4) + z.x * 4 + 2;
|
|
||||||
imageData.data[blueIndex] = 255;
|
|
||||||
imageData.data[blueIndex - 1] = 255;
|
|
||||||
imageData.data[blueIndex - 2] = 255;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(errors);
|
|
||||||
|
|
||||||
ctx.putImageData(imageData, 0, 0);
|
|
||||||
};*/ //testSDF(); // extract as new image (data-uri)
|
|
||||||
|
|
||||||
/*
|
|
||||||
const testSDF = () => {
|
|
||||||
Random.seed = 44;
|
|
||||||
|
|
||||||
const objects: Array<TunnelShape> = [
|
|
||||||
new TunnelShape(vec2.fromValues(20, 20), vec2.fromValues(40, 80), 10, 9),
|
|
||||||
new TunnelShape(vec2.fromValues(40, 80), vec2.fromValues(60, 20), 9, 15),
|
|
||||||
];
|
|
||||||
|
|
||||||
const getPixelValue = (position: vec2) => {
|
|
||||||
/*const k = 0.15;
|
|
||||||
let res = Math.pow(2, -k * (vec2.distance(position, vec2.fromValues(20, 20)) - 20));
|
|
||||||
res += Math.pow(2, -k * (vec2.distance(position, vec2.fromValues(40, 80)) - 30));
|
|
||||||
res += Math.pow(2, -k * (vec2.distance(position, vec2.fromValues(60, 20)) - 20));
|
|
||||||
res = -Math.log2(res) / k;
|
|
||||||
return -res;
|
|
||||||
|
|
||||||
*/ /*let min = 1000;
|
|
||||||
for (const t of objects) {
|
|
||||||
min = Math.min(min, t.distance(position));
|
|
||||||
}
|
|
||||||
if (min < 0) {
|
|
||||||
// min = Math.min(min, vec2.distance(position, vec2.fromValues(40, 80 - 31.62)) - 31.62);
|
|
||||||
}
|
|
||||||
return -min;*/ /*
|
|
||||||
const tree = new BoundingBoxTree([
|
|
||||||
new BoundingBox(300, 550, 150, 550, 'A'),
|
|
||||||
new BoundingBox(400, 800, 50, 200, 'B'),
|
|
||||||
new BoundingBox(450, 500, 175, 185, 'C'),
|
|
||||||
new BoundingBox(100, 200, 100, 500, 'D'),
|
|
||||||
new BoundingBox(750, 950, 450, 600, 'E'),
|
|
||||||
new BoundingBox(940, 1000, -2, 180, 'F'),
|
|
||||||
new BoundingBox(960, 1050, 50, 190, 'G'),
|
|
||||||
new BoundingBox(150, 900, 0, 575, 'H'),
|
|
||||||
new BoundingBox(-10000, 10000, -10000, 10000, 'I'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
tree.print();
|
|
||||||
console.log(tree.findIntersecting(new BoundingBox(960, 1050, 50, 190, 'G')));
|
|
||||||
*/ /*
|
|
||||||
return -vec2.distance(position, vec2.fromValues(40, 40)) + 30;
|
|
||||||
};
|
|
||||||
/*
|
|
||||||
const width = 80;
|
|
||||||
const height = 100;
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
document.body.appendChild(canvas);
|
|
||||||
// set desired size of transparent image
|
|
||||||
canvas.width = width;
|
|
||||||
canvas.height = height;
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
const imageData = ctx.createImageData(width, height);
|
|
||||||
|
|
||||||
const zeroes = [];
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
console.log(x);
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
const position = vec2.fromValues(x, y);
|
|
||||||
const redIndex = y * (width * 4) + x * 4;
|
|
||||||
const dist = getPixelValue(position);
|
|
||||||
if (Math.abs(dist) < 1.5) {
|
|
||||||
zeroes.push(position);
|
|
||||||
}
|
|
||||||
const blueIndex = y * (width * 4) + x * 4 + 2;
|
|
||||||
//imageData.data[blueIndex] = dist * 10;
|
|
||||||
|
|
||||||
imageData.data[redIndex + 3] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.putImageData(imageData, 0, 0);
|
|
||||||
|
|
||||||
console.log(zeroes);
|
|
||||||
const errors = [];
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
console.log('verify ', x);
|
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
const position = vec2.fromValues(x, y);
|
|
||||||
const dist = getPixelValue(position);
|
|
||||||
|
|
||||||
const realDistance = zeroes.map((z) => vec2.distance(z, position)).sort()[0];
|
|
||||||
|
|
||||||
if (Math.abs(realDistance - dist) < 3) {
|
|
||||||
const greenIndex = y * (width * 4) + x * 4 + 1;
|
|
||||||
imageData.data[greenIndex] = 255;
|
|
||||||
} else if (dist >= 0) {
|
|
||||||
const redIndex = y * (width * 4) + x * 4;
|
|
||||||
errors.push(realDistance - dist);
|
|
||||||
imageData.data[redIndex] = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if (zeroes.find((z) => vec2.distance(z, position) < dist) !== undefined) {
|
|
||||||
const blueIndex = y * (width * 4) + x * 4 + 2;
|
|
||||||
imageData.data[blueIndex] = 255;
|
|
||||||
}*/ const main = async () => {
|
|
||||||
try {
|
try {
|
||||||
Random.seed = 42;
|
Random.seed = 42;
|
||||||
await new Game().start();
|
await new Game().start();
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,30 @@
|
||||||
import { vec3 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { CircleLight, compile, Flashlight, Renderer } from 'sdf-2d';
|
import { CircleLight, compile, Flashlight, InvertedTunnel, Renderer } from 'sdf-2d';
|
||||||
import { CommandBroadcaster } from './commands/command-broadcaster';
|
import { CommandBroadcaster } from './commands/command-broadcaster';
|
||||||
import { RenderCommand } from './graphics/commands/render';
|
import { RenderCommand } from './graphics/commands/render';
|
||||||
|
import { DeltaTimeCalculator } from './helper/delta-time-calculator';
|
||||||
import { prettyPrint } from './helper/pretty-print';
|
import { prettyPrint } from './helper/pretty-print';
|
||||||
|
import { rgb } from './helper/rgb';
|
||||||
import { IGame } from './i-game';
|
import { IGame } from './i-game';
|
||||||
import { KeyboardListener } from './input/keyboard-listener';
|
import { KeyboardListener } from './input/keyboard-listener';
|
||||||
import { MouseListener } from './input/mouse-listener';
|
import { MouseListener } from './input/mouse-listener';
|
||||||
import { TouchListener } from './input/touch-listener';
|
import { TouchListener } from './input/touch-listener';
|
||||||
import { GameObject } from './objects/game-object';
|
|
||||||
import { Objects } from './objects/objects';
|
import { Objects } from './objects/objects';
|
||||||
import { Camera } from './objects/types/camera';
|
import { Camera } from './objects/types/camera';
|
||||||
import { Character } from './objects/types/character';
|
import { Character } from './objects/types/character';
|
||||||
import { createDungeon } from './objects/world/create-dungeon';
|
import { createDungeon } from './objects/world/create-dungeon';
|
||||||
|
import { BoundingBoxBase } from './physics/bounds/bounding-box-base';
|
||||||
import { MoveToCommand } from './physics/commands/move-to';
|
import { MoveToCommand } from './physics/commands/move-to';
|
||||||
import { StepCommand } from './physics/commands/step';
|
import { StepCommand } from './physics/commands/step';
|
||||||
import { TeleportToCommand } from './physics/commands/teleport-to';
|
import { TeleportToCommand } from './physics/commands/teleport-to';
|
||||||
import { Physics } from './physics/physics';
|
import { Physics } from './physics/physics';
|
||||||
import { BoundingBoxBase } from './shapes/bounding-box-base';
|
import { settings } from './settings';
|
||||||
import { CircleShape } from './shapes/types/circle-shape';
|
import { BlobShape } from './shapes/types/blob-shape';
|
||||||
import { TunnelShape } from './shapes/types/tunnel-shape';
|
|
||||||
|
|
||||||
export class Game implements IGame {
|
export class Game implements IGame {
|
||||||
public readonly objects = new Objects();
|
public readonly objects = new Objects();
|
||||||
public readonly physics = new Physics();
|
public readonly physics = new Physics();
|
||||||
public readonly camera = new Camera();
|
public readonly camera = new Camera();
|
||||||
private previousTime?: DOMHighResTimeStamp = null;
|
|
||||||
private previousFpsValues: Array<number> = [];
|
|
||||||
private character: Character;
|
private character: Character;
|
||||||
private renderer: Renderer;
|
private renderer: Renderer;
|
||||||
private rendererPromise: Promise<Renderer>;
|
private rendererPromise: Promise<Renderer>;
|
||||||
|
|
@ -34,8 +33,6 @@ export class Game implements IGame {
|
||||||
constructor() {
|
constructor() {
|
||||||
const canvas: HTMLCanvasElement = document.querySelector('canvas#main');
|
const canvas: HTMLCanvasElement = document.querySelector('canvas#main');
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
|
|
||||||
|
|
||||||
new CommandBroadcaster(
|
new CommandBroadcaster(
|
||||||
[
|
[
|
||||||
new KeyboardListener(document.body),
|
new KeyboardListener(document.body),
|
||||||
|
|
@ -48,16 +45,16 @@ export class Game implements IGame {
|
||||||
this.rendererPromise = compile(
|
this.rendererPromise = compile(
|
||||||
canvas,
|
canvas,
|
||||||
[
|
[
|
||||||
CircleShape.descriptor,
|
InvertedTunnel.descriptor,
|
||||||
TunnelShape.descriptor,
|
|
||||||
Flashlight.descriptor,
|
Flashlight.descriptor,
|
||||||
|
BlobShape.descriptor,
|
||||||
CircleLight.descriptor,
|
CircleLight.descriptor,
|
||||||
],
|
],
|
||||||
[
|
{
|
||||||
vec3.fromValues(0, 0, 0),
|
shadowTraceCount: 12,
|
||||||
vec3.fromValues(224 / 255, 96 / 255, 126 / 255),
|
paletteSize: 10,
|
||||||
vec3.fromValues(119 / 255, 173 / 255, 120 / 255),
|
enableStopwatch: true,
|
||||||
]
|
}
|
||||||
);
|
);
|
||||||
this.initializeScene();
|
this.initializeScene();
|
||||||
this.physics.start();
|
this.physics.start();
|
||||||
|
|
@ -67,16 +64,22 @@ export class Game implements IGame {
|
||||||
this.renderer = await this.rendererPromise;
|
this.renderer = await this.rendererPromise;
|
||||||
this.renderer.setRuntimeSettings({
|
this.renderer.setRuntimeSettings({
|
||||||
isWorldInverted: true,
|
isWorldInverted: true,
|
||||||
ambientLight: vec3.fromValues(0.35, 0.1, 0.45),
|
ambientLight: rgb(0.35, 0.1, 0.45),
|
||||||
shadowLength: 300,
|
colorPalette: [
|
||||||
|
rgb(0.4, 1, 0.6),
|
||||||
|
rgb(1, 1, 0),
|
||||||
|
rgb(0.3, 1, 1),
|
||||||
|
rgb(0.3, 1, 1),
|
||||||
|
rgb(0.3, 1, 1),
|
||||||
|
rgb(0.3, 1, 1),
|
||||||
|
rgb(0.3, 1, 1),
|
||||||
|
],
|
||||||
|
enableHighDpiRendering: false,
|
||||||
|
lightCutoffDistance: settings.lightCutoffDistance,
|
||||||
});
|
});
|
||||||
requestAnimationFrame(this.gameLoop.bind(this));
|
requestAnimationFrame(this.gameLoop.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public addObject(o: GameObject) {
|
|
||||||
this.objects.addObject(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get viewArea(): BoundingBoxBase {
|
public get viewArea(): BoundingBoxBase {
|
||||||
return this.camera.viewArea;
|
return this.camera.viewArea;
|
||||||
}
|
}
|
||||||
|
|
@ -86,51 +89,38 @@ export class Game implements IGame {
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeScene() {
|
private initializeScene() {
|
||||||
const start = createDungeon(this.objects, this.physics);
|
|
||||||
createDungeon(this.objects, this.physics);
|
createDungeon(this.objects, this.physics);
|
||||||
|
//createDungeon(this.objects, this.physics);
|
||||||
|
|
||||||
this.character = new Character(this);
|
this.character = new Character(this.physics, this);
|
||||||
// this.physics.addDynamicBoundingBox(this.character.boundingBox);
|
this.objects.addObject(this.character);
|
||||||
this.addObject(this.character);
|
this.objects.addObject(this.camera);
|
||||||
this.addObject(this.camera);
|
let pos: any = localStorage.getItem('characterPosition');
|
||||||
let pos: any = localStorage.getItem('character-position');
|
pos = pos ? JSON.parse(pos) : vec2.fromValues(0, 0);
|
||||||
pos = pos ? JSON.parse(pos) : start.from;
|
|
||||||
this.character.sendCommand(new TeleportToCommand(pos));
|
this.character.sendCommand(new TeleportToCommand(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleVisibilityChange() {
|
private deltaTimeCalculator = new DeltaTimeCalculator();
|
||||||
if (!document.hidden) {
|
|
||||||
this.previousTime = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private gameLoop(time: DOMHighResTimeStamp) {
|
private gameLoop(time: DOMHighResTimeStamp) {
|
||||||
if (this.previousTime === null) {
|
const deltaTime = this.deltaTimeCalculator.getNextDeltaTime(time);
|
||||||
this.previousTime = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deltaTime = time - this.previousTime;
|
|
||||||
this.previousTime = time;
|
|
||||||
|
|
||||||
this.objects.sendCommand(new StepCommand(deltaTime));
|
this.objects.sendCommand(new StepCommand(deltaTime));
|
||||||
this.camera.sendCommand(new MoveToCommand(this.character.position));
|
this.camera.sendCommand(new MoveToCommand(this.character.position));
|
||||||
|
|
||||||
this.camera.sendCommand(new RenderCommand(this.renderer));
|
this.camera.sendCommand(new RenderCommand(this.renderer));
|
||||||
|
|
||||||
const shouldBeDrawn = this.physics
|
const shouldBeDrawn = this.physics
|
||||||
.findIntersecting(this.camera.viewArea)
|
.findIntersecting(this.camera.viewArea)
|
||||||
.map((b) => b.shape?.gameObject);
|
.map((b) => b.owner);
|
||||||
|
|
||||||
for (const object of shouldBeDrawn) {
|
for (const object of shouldBeDrawn) {
|
||||||
object?.sendCommand(new RenderCommand(this.renderer));
|
object?.sendCommand(new RenderCommand(this.renderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.character.sendCommand(new RenderCommand(this.renderer));
|
|
||||||
this.renderer.renderDrawables();
|
this.renderer.renderDrawables();
|
||||||
|
|
||||||
this.overlay.innerText = prettyPrint(this.renderer.insights);
|
this.overlay.innerText = prettyPrint(this.renderer.insights);
|
||||||
|
localStorage.setItem('characterPosition', JSON.stringify(this.character.position));
|
||||||
localStorage.setItem('character-position', JSON.stringify(this.character.position));
|
|
||||||
requestAnimationFrame(this.gameLoop.bind(this));
|
requestAnimationFrame(this.gameLoop.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
export const exponentialDecay = (
|
|
||||||
accumulator: number,
|
|
||||||
nextValue: number,
|
|
||||||
biasOfNextValue: number
|
|
||||||
) => accumulator * (1 - biasOfNextValue) + nextValue * biasOfNextValue;
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
export const getCombinations = (values: Array<Array<number>>): Array<Array<number>> => {
|
|
||||||
if (!values.every((a) => a.length > 0)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const result: Array<Array<number>> = [];
|
|
||||||
const counters = values.map((_) => 0);
|
|
||||||
|
|
||||||
const increaseCounter = (i: number) => {
|
|
||||||
if (i >= counters.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
counters[i]++;
|
|
||||||
|
|
||||||
if (counters[i] >= values[i].length) {
|
|
||||||
counters[i] = 0;
|
|
||||||
return increaseCounter(i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
do {
|
|
||||||
result.push(values.map((v, i) => v[counters[i]]));
|
|
||||||
} while (increaseCounter(0));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
3
frontend/src/scripts/helper/rgb.ts
Normal file
3
frontend/src/scripts/helper/rgb.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { vec3 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export const rgb = (r: number, g: number, b: number): vec3 => vec3.fromValues(r, g, b);
|
||||||
4
frontend/src/scripts/helper/rgb255.ts
Normal file
4
frontend/src/scripts/helper/rgb255.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { vec3 } from 'gl-matrix';
|
||||||
|
|
||||||
|
export const rgb255 = (r: number, g: number, b: number): vec3 =>
|
||||||
|
vec3.fromValues(r / 255, g / 255, b / 255);
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
export const waitWhileFalse = (body: () => boolean): Promise<void> => {
|
|
||||||
let resolveOnDone: () => void;
|
|
||||||
let rejectOnDone: (e: Error) => void;
|
|
||||||
|
|
||||||
const onDone = new Promise<void>((resolve, reject) => {
|
|
||||||
resolveOnDone = resolve;
|
|
||||||
rejectOnDone = reject;
|
|
||||||
});
|
|
||||||
|
|
||||||
const waiter = () => {
|
|
||||||
let success: boolean;
|
|
||||||
|
|
||||||
try {
|
|
||||||
success = body();
|
|
||||||
} catch (e) {
|
|
||||||
rejectOnDone(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
resolveOnDone();
|
|
||||||
} else {
|
|
||||||
requestAnimationFrame(waiter);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
waiter();
|
|
||||||
return onDone;
|
|
||||||
};
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { GameObject } from './objects/game-object';
|
import { BoundingBoxBase } from './physics/bounds/bounding-box-base';
|
||||||
import { BoundingBoxBase } from './shapes/bounding-box-base';
|
|
||||||
|
|
||||||
export interface IGame {
|
export interface IGame {
|
||||||
addObject(o: GameObject): void;
|
|
||||||
readonly viewArea: BoundingBoxBase;
|
readonly viewArea: BoundingBoxBase;
|
||||||
findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase>;
|
findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { vec2 } from 'gl-matrix';
|
||||||
import { RenderCommand } from '../../graphics/commands/render';
|
import { RenderCommand } from '../../graphics/commands/render';
|
||||||
import { CursorMoveCommand } from '../../input/commands/cursor-move-command';
|
import { CursorMoveCommand } from '../../input/commands/cursor-move-command';
|
||||||
import { ZoomCommand } from '../../input/commands/zoom';
|
import { ZoomCommand } from '../../input/commands/zoom';
|
||||||
|
import { BoundingBox } from '../../physics/bounds/bounding-box';
|
||||||
import { MoveToCommand } from '../../physics/commands/move-to';
|
import { MoveToCommand } from '../../physics/commands/move-to';
|
||||||
import { BoundingBox } from '../../shapes/bounding-box';
|
|
||||||
import { GameObject } from '../game-object';
|
import { GameObject } from '../game-object';
|
||||||
|
|
||||||
export class Camera extends GameObject {
|
export class Camera extends GameObject {
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,61 @@
|
||||||
import { vec2, vec3 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { Flashlight } from 'sdf-2d';
|
import { Flashlight } from 'sdf-2d';
|
||||||
import { RenderCommand } from '../../graphics/commands/render';
|
import { RenderCommand } from '../../graphics/commands/render';
|
||||||
|
import { rgb } from '../../helper/rgb';
|
||||||
import { IGame } from '../../i-game';
|
import { IGame } from '../../i-game';
|
||||||
import { KeyDownCommand } from '../../input/commands/key-down';
|
import { KeyDownCommand } from '../../input/commands/key-down';
|
||||||
import { KeyUpCommand } from '../../input/commands/key-up';
|
import { KeyUpCommand } from '../../input/commands/key-up';
|
||||||
import { SwipeCommand } from '../../input/commands/swipe';
|
import { SwipeCommand } from '../../input/commands/swipe';
|
||||||
|
import { BoundingBoxBase } from '../../physics/bounds/bounding-box-base';
|
||||||
|
import { BoundingCircle } from '../../physics/bounds/bounding-circle';
|
||||||
import { StepCommand } from '../../physics/commands/step';
|
import { StepCommand } from '../../physics/commands/step';
|
||||||
import { TeleportToCommand } from '../../physics/commands/teleport-to';
|
import { TeleportToCommand } from '../../physics/commands/teleport-to';
|
||||||
import { IShape } from '../../shapes/i-shape';
|
import { DynamicPhysicalObject } from '../../physics/dynamic-physical-object';
|
||||||
|
import { Physics } from '../../physics/physics';
|
||||||
import { BlobShape } from '../../shapes/types/blob-shape';
|
import { BlobShape } from '../../shapes/types/blob-shape';
|
||||||
import { GameObject } from '../game-object';
|
|
||||||
|
|
||||||
export class Character extends GameObject {
|
export class Character extends DynamicPhysicalObject {
|
||||||
|
protected head: BoundingCircle;
|
||||||
|
protected leftFoot: BoundingCircle;
|
||||||
|
protected rightFoot: BoundingCircle;
|
||||||
|
|
||||||
private keysDown: Set<string> = new Set();
|
private keysDown: Set<string> = new Set();
|
||||||
|
|
||||||
private light = new Flashlight(
|
private light = new Flashlight(
|
||||||
vec2.create(),
|
vec2.fromValues(0, 0),
|
||||||
vec3.fromValues(1, 0.6, 0.45),
|
rgb(1, 0.6, 0.45),
|
||||||
1.5,
|
0.5,
|
||||||
vec2.fromValues(-1, 0)
|
vec2.fromValues(-1, 0)
|
||||||
);
|
);
|
||||||
private shape = new BlobShape(vec2.create());
|
|
||||||
|
private shape = new BlobShape();
|
||||||
|
private boundingCircle = new BoundingCircle(this, vec2.create(), 50);
|
||||||
|
private center = vec2.create();
|
||||||
private static speed = 1.5;
|
private static speed = 1.5;
|
||||||
|
|
||||||
constructor(private game: IGame) {
|
constructor(physics: Physics, private game: IGame) {
|
||||||
super();
|
super(physics, true);
|
||||||
|
|
||||||
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
|
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
|
||||||
this.addCommandExecutor(TeleportToCommand, (c) => this.setPosition(c.position));
|
this.addCommandExecutor(TeleportToCommand, (c) => this.setPosition(c.position));
|
||||||
this.addCommandExecutor(KeyDownCommand, (c) => this.keysDown.add(c.key));
|
this.addCommandExecutor(KeyDownCommand, (c) => this.keysDown.add(c.key));
|
||||||
this.addCommandExecutor(KeyUpCommand, (c) => this.keysDown.delete(c.key));
|
this.addCommandExecutor(KeyUpCommand, (c) => this.keysDown.delete(c.key));
|
||||||
|
this.addCommandExecutor(StepCommand, this.stepHandler.bind(this));
|
||||||
this.addCommandExecutor(SwipeCommand, (c) => {
|
this.addCommandExecutor(SwipeCommand, (c) => {
|
||||||
this.tryMoving(vec2.multiply(vec2.create(), c.delta, this.game.viewArea.size));
|
this.tryMoving(vec2.multiply(vec2.create(), c.delta, this.game.viewArea.size));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.head = new BoundingCircle(this, this.center, 50);
|
||||||
|
this.leftFoot = new BoundingCircle(this, this.center, 50);
|
||||||
|
this.rightFoot = new BoundingCircle(this, this.center, 50);
|
||||||
|
|
||||||
|
this.shape.setCircles([this.head, this.leftFoot, this.rightFoot]);
|
||||||
|
|
||||||
|
this.addToPhysics();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get position(): vec2 {
|
public distance(target: vec2): number {
|
||||||
return this.shape.center;
|
return this.shape.minDistance(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
private draw(c: RenderCommand) {
|
private draw(c: RenderCommand) {
|
||||||
|
|
@ -43,73 +63,24 @@ export class Character extends GameObject {
|
||||||
c.renderer.addDrawable(this.light);
|
c.renderer.addDrawable(this.light);
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryMoving(delta: vec2, isFirstIteration = true) {
|
public get position(): vec2 {
|
||||||
const maxStep = 2;
|
return this.center;
|
||||||
if (vec2.length(delta) > maxStep) {
|
|
||||||
let steppedDelta = vec2.normalize(vec2.create(), delta);
|
|
||||||
vec2.scale(steppedDelta, steppedDelta, maxStep - 0.001);
|
|
||||||
|
|
||||||
for (let i = 0; i <= vec2.length(delta) / maxStep - 1; i++) {
|
|
||||||
this.tryMoving(vec2.clone(steppedDelta), isFirstIteration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
steppedDelta = vec2.normalize(vec2.create(), delta);
|
public getBoundingCircles(): Array<BoundingCircle> {
|
||||||
|
return [this.boundingCircle];
|
||||||
vec2.scale(steppedDelta, steppedDelta, vec2.length(delta) % maxStep);
|
|
||||||
this.tryMoving(vec2.clone(steppedDelta), isFirstIteration);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextShape = this.shape.clone();
|
public getBoundingBox(): BoundingBoxBase {
|
||||||
vec2.add(nextShape.center, nextShape.center, delta);
|
return this.head.boundingBox;
|
||||||
|
|
||||||
const nextNearShapes = this.getNearShapesTo(nextShape);
|
|
||||||
|
|
||||||
if (nextNearShapes.length && nextNearShapes[0].distance < 0) {
|
|
||||||
this.setPosition(nextShape.center);
|
|
||||||
} else {
|
|
||||||
if (!isFirstIteration) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentNearShapes = this.getNearShapesTo(this.shape);
|
private tryMoving(delta: vec2) {
|
||||||
const intersecting = nextNearShapes
|
this.physics.tryMovingDynamicCircle(this.head, delta);
|
||||||
.filter(
|
|
||||||
(n) =>
|
|
||||||
currentNearShapes.find((c) => c.shape === n.shape && c.distance <= 0) !==
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
.sort((e) => Math.abs(e.distance));
|
|
||||||
|
|
||||||
if (intersecting.length < 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const normal = intersecting[0].shape.normal(this.shape.center);
|
|
||||||
|
|
||||||
const maxDistance = intersecting.reduce((p, c) => (p.distance > c.distance ? p : c))
|
|
||||||
.distance;
|
|
||||||
|
|
||||||
vec2.add(delta, delta, vec2.scale(vec2.create(), normal, -maxDistance - 2));
|
|
||||||
|
|
||||||
this.tryMoving(delta, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getNearShapesTo(shape: BlobShape): Array<{ shape: IShape; distance: number }> {
|
|
||||||
return this.game
|
|
||||||
.findIntersecting(shape.boundingBox)
|
|
||||||
.filter((b) => b.shape)
|
|
||||||
.map((b) => ({
|
|
||||||
shape: b.shape,
|
|
||||||
// TODO: fix this
|
|
||||||
distance: b.shape.minDistance(shape.center) + shape.radius - 20,
|
|
||||||
}))
|
|
||||||
.sort((e) => e.distance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setPosition(value: vec2) {
|
private setPosition(value: vec2) {
|
||||||
this.shape.position = value;
|
//this.head.center = value;
|
||||||
this.light.center = vec2.add(vec2.create(), value, vec2.fromValues(50, -40));
|
this.light.center = vec2.add(vec2.create(), value, vec2.fromValues(50, -40));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,40 @@
|
||||||
import { vec2, vec3 } from 'gl-matrix';
|
import { vec2, vec3 } from 'gl-matrix';
|
||||||
import { CircleLight } from 'sdf-2d';
|
import { CircleLight } from 'sdf-2d';
|
||||||
import { RenderCommand } from '../../graphics/commands/render';
|
import { RenderCommand } from '../../graphics/commands/render';
|
||||||
|
import { BoundingBoxBase } from '../../physics/bounds/bounding-box-base';
|
||||||
|
import { ImmutableBoundingBox } from '../../physics/bounds/immutable-bounding-box';
|
||||||
import { MoveToCommand } from '../../physics/commands/move-to';
|
import { MoveToCommand } from '../../physics/commands/move-to';
|
||||||
import { GameObject } from '../game-object';
|
import { PhysicalObject } from '../../physics/physical-object';
|
||||||
|
import { Physics } from '../../physics/physics';
|
||||||
|
import { settings } from '../../settings';
|
||||||
|
|
||||||
export class Lamp extends GameObject {
|
export class Lamp extends PhysicalObject {
|
||||||
private light: CircleLight;
|
private light: CircleLight;
|
||||||
|
|
||||||
constructor(center: vec2, color: vec3, lightness: number) {
|
constructor(center: vec2, color: vec3, lightness: number, physics: Physics) {
|
||||||
super();
|
super(physics, false);
|
||||||
|
|
||||||
this.light = new CircleLight(center, color, lightness);
|
this.light = new CircleLight(center, color, lightness);
|
||||||
|
this.addToPhysics();
|
||||||
|
|
||||||
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
|
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
|
||||||
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
|
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public distance(target: vec2): number {
|
||||||
|
return this.light.minDistance(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBoundingBox(): BoundingBoxBase {
|
||||||
|
return new ImmutableBoundingBox(
|
||||||
|
this,
|
||||||
|
this.light.center.x - settings.lightCutoffDistance,
|
||||||
|
this.light.center.x + settings.lightCutoffDistance,
|
||||||
|
this.light.center.y - settings.lightCutoffDistance,
|
||||||
|
this.light.center.y + settings.lightCutoffDistance
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private draw(c: RenderCommand) {
|
private draw(c: RenderCommand) {
|
||||||
c.renderer.addDrawable(this.light);
|
c.renderer.addDrawable(this.light);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,52 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { RenderCommand } from '../../graphics/commands/render';
|
import { RenderCommand } from '../../graphics/commands/render';
|
||||||
|
import { BoundingBoxBase } from '../../physics/bounds/bounding-box-base';
|
||||||
|
import { ImmutableBoundingBox } from '../../physics/bounds/immutable-bounding-box';
|
||||||
import { Physics } from '../../physics/physics';
|
import { Physics } from '../../physics/physics';
|
||||||
|
import { StaticPhysicalObject } from '../../physics/static-physical-object';
|
||||||
import { TunnelShape } from '../../shapes/types/tunnel-shape';
|
import { TunnelShape } from '../../shapes/types/tunnel-shape';
|
||||||
import { GameObject } from '../game-object';
|
|
||||||
|
|
||||||
export class Tunnel extends GameObject {
|
export class Tunnel extends StaticPhysicalObject {
|
||||||
private shape: TunnelShape;
|
private shape: TunnelShape;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
physics: Physics,
|
physics: Physics,
|
||||||
public readonly from: vec2,
|
from: vec2,
|
||||||
to: vec2,
|
to: vec2,
|
||||||
fromRadius: number,
|
fromRadius: number,
|
||||||
toRadius: number
|
toRadius: number
|
||||||
) {
|
) {
|
||||||
super();
|
super(physics, true);
|
||||||
|
this.shape = new TunnelShape(from, to, fromRadius, toRadius);
|
||||||
|
this.addToPhysics();
|
||||||
|
|
||||||
this.shape = new TunnelShape(from, to, fromRadius, toRadius, this);
|
|
||||||
physics.addStaticBoundingBox(this.shape.boundingBox);
|
|
||||||
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
|
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public distance(target: vec2): number {
|
||||||
|
return this.shape.minDistance(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBoundingBox(): BoundingBoxBase {
|
||||||
|
const xMin = Math.min(
|
||||||
|
this.shape.from.x - this.shape.fromRadius,
|
||||||
|
this.shape.to.x - this.shape.toRadius
|
||||||
|
);
|
||||||
|
const yMin = Math.min(
|
||||||
|
this.shape.from.y - this.shape.fromRadius,
|
||||||
|
this.shape.to.y - this.shape.toRadius
|
||||||
|
);
|
||||||
|
const xMax = Math.max(
|
||||||
|
this.shape.from.x + this.shape.fromRadius,
|
||||||
|
this.shape.to.x + this.shape.toRadius
|
||||||
|
);
|
||||||
|
const yMax = Math.max(
|
||||||
|
this.shape.from.y + this.shape.fromRadius,
|
||||||
|
this.shape.to.y + this.shape.toRadius
|
||||||
|
);
|
||||||
|
return new ImmutableBoundingBox(this, xMin, xMax, yMin, yMax, true);
|
||||||
|
}
|
||||||
|
|
||||||
private draw(c: RenderCommand) {
|
private draw(c: RenderCommand) {
|
||||||
c.renderer.addDrawable(this.shape);
|
c.renderer.addDrawable(this.shape);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2, vec3 } from 'gl-matrix';
|
||||||
import { Random } from '../../helper/random';
|
import { Random } from '../../helper/random';
|
||||||
import { Physics } from '../../physics/physics';
|
import { Physics } from '../../physics/physics';
|
||||||
import { Objects } from '../objects';
|
import { Objects } from '../objects';
|
||||||
|
import { Lamp } from '../types/lamp';
|
||||||
import { Tunnel } from '../types/tunnel';
|
import { Tunnel } from '../types/tunnel';
|
||||||
|
|
||||||
export const createDungeon = (objects: Objects, physics: Physics): Tunnel => {
|
export const createDungeon = (objects: Objects, physics: Physics) => {
|
||||||
let previousRadius = 350;
|
let previousRadius = 350;
|
||||||
let previousEnd = vec2.create();
|
let previousEnd = vec2.create();
|
||||||
|
|
||||||
let first: Tunnel;
|
let tunnelsCountSinceLastLight = 0;
|
||||||
|
|
||||||
for (let i = 0; i < 500000; i += 500) {
|
for (let i = 0; i < 500000; i += 500) {
|
||||||
const deltaHeight = (Random.getRandom() - 0.5) * 2000;
|
const deltaHeight = (Random.getRandom() - 0.5) * 2000;
|
||||||
|
|
@ -24,30 +25,24 @@ export const createDungeon = (objects: Objects, physics: Physics): Tunnel => {
|
||||||
currentToRadius
|
currentToRadius
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!first) {
|
|
||||||
first = tunnel;
|
|
||||||
}
|
|
||||||
|
|
||||||
objects.addObject(tunnel);
|
objects.addObject(tunnel);
|
||||||
|
|
||||||
/* if (deltaHeight > 0 && Random.getRandom() > 0.8) {
|
if (++tunnelsCountSinceLastLight > 3 && Random.getRandom() > 0.6) {
|
||||||
objects.addObject(
|
objects.addObject(
|
||||||
new Lamp(
|
new Lamp(
|
||||||
currentEnd,
|
currentEnd,
|
||||||
Random.getRandom() * 20 + 30,
|
vec3.normalize(
|
||||||
vec3.scale(
|
|
||||||
vec3.create(),
|
vec3.create(),
|
||||||
vec3.normalize(vec3.create(), vec3.fromValues(0.5, 0.1, 0.8)),
|
vec3.fromValues(Random.getRandom(), 0, Random.getRandom())
|
||||||
Random.getRandom() * 0.5 + 0.5
|
|
||||||
),
|
),
|
||||||
1
|
0.5,
|
||||||
|
physics
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} */
|
tunnelsCountSinceLastLight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
previousEnd = currentEnd;
|
previousEnd = currentEnd;
|
||||||
previousRadius = currentToRadius;
|
previousRadius = currentToRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
return first;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { IShape } from './i-shape';
|
import { PhysicalObject } from '../physical-object';
|
||||||
|
|
||||||
export abstract class BoundingBoxBase {
|
export abstract class BoundingBoxBase {
|
||||||
constructor(
|
constructor(
|
||||||
public readonly shape: IShape,
|
public readonly owner: PhysicalObject,
|
||||||
protected _xMin: number = 0,
|
protected _xMin: number = 0,
|
||||||
protected _xMax: number = 0,
|
protected _xMax: number = 0,
|
||||||
protected _yMin: number = 0,
|
protected _yMin: number = 0,
|
||||||
protected _yMax: number = 0
|
protected _yMax: number = 0,
|
||||||
|
public readonly isInverted = false
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public get 0(): number {
|
public get 0(): number {
|
||||||
|
|
@ -3,6 +3,38 @@ import { BoundingBoxBase } from './bounding-box-base';
|
||||||
import { ImmutableBoundingBox } from './immutable-bounding-box';
|
import { ImmutableBoundingBox } from './immutable-bounding-box';
|
||||||
|
|
||||||
export class BoundingBox extends BoundingBoxBase {
|
export class BoundingBox extends BoundingBoxBase {
|
||||||
|
public get xMin(): number {
|
||||||
|
return this._xMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set xMin(value: number) {
|
||||||
|
this._xMin = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set xMax(value: number) {
|
||||||
|
this._xMax = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get xMax(): number {
|
||||||
|
return this._xMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set yMin(value: number) {
|
||||||
|
this._yMin = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get yMin(): number {
|
||||||
|
return this._yMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set yMax(value: number) {
|
||||||
|
this._yMax = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get yMax(): number {
|
||||||
|
return this._yMax;
|
||||||
|
}
|
||||||
|
|
||||||
public get topLeft(): vec2 {
|
public get topLeft(): vec2 {
|
||||||
return vec2.fromValues(this._xMin, this._yMax);
|
return vec2.fromValues(this._xMin, this._yMax);
|
||||||
}
|
}
|
||||||
|
|
@ -12,18 +44,18 @@ export class BoundingBox extends BoundingBoxBase {
|
||||||
this._yMax = value.y;
|
this._yMax = value.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get size(): vec2 {
|
|
||||||
return vec2.fromValues(this._xMax - this._xMin, this._yMax - this._yMin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public set size(value: vec2) {
|
public set size(value: vec2) {
|
||||||
this._xMax = this.xMin + value.x;
|
this._xMax = this.xMin + value.x;
|
||||||
this._yMin = this.yMax - value.y;
|
this._yMin = this.yMax - value.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get size(): vec2 {
|
||||||
|
return vec2.fromValues(this._xMax - this._xMin, this._yMax - this._yMin);
|
||||||
|
}
|
||||||
|
|
||||||
public cloneAsImmutable(): ImmutableBoundingBox {
|
public cloneAsImmutable(): ImmutableBoundingBox {
|
||||||
return new ImmutableBoundingBox(
|
return new ImmutableBoundingBox(
|
||||||
this.shape,
|
this.owner,
|
||||||
this.xMin,
|
this.xMin,
|
||||||
this.xMax,
|
this.xMax,
|
||||||
this.yMin,
|
this.yMin,
|
||||||
67
frontend/src/scripts/physics/bounds/bounding-circle.ts
Normal file
67
frontend/src/scripts/physics/bounds/bounding-circle.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { PhysicalObject } from '../physical-object';
|
||||||
|
import { BoundingBox } from './bounding-box';
|
||||||
|
import { BoundingBoxBase } from './bounding-box-base';
|
||||||
|
|
||||||
|
export class BoundingCircle {
|
||||||
|
private _boundingBox: BoundingBox;
|
||||||
|
|
||||||
|
constructor(public readonly owner, private _center: vec2, private _radius: number) {
|
||||||
|
this._boundingBox = new BoundingBox(owner);
|
||||||
|
this.recalculateBoundingBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 areIntersecting(other: PhysicalObject): boolean {
|
||||||
|
return other.distance(this.center) < this.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isInside(other: PhysicalObject): 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get boundingBox(): BoundingBoxBase {
|
||||||
|
return this._boundingBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { BoundingBoxBase } from '../../shapes/bounding-box-base';
|
import { BoundingBoxBase } from '../bounds/bounding-box-base';
|
||||||
|
|
||||||
export class BoundingBoxList {
|
export class BoundingBoxList {
|
||||||
constructor(private boundingBoxes: Array<BoundingBoxBase> = []) {}
|
constructor(private boundingBoxes: Array<BoundingBoxBase> = []) {}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
// source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js
|
// source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js
|
||||||
|
|
||||||
import { ImmutableBoundingBox } from '../../shapes/immutable-bounding-box';
|
import { ImmutableBoundingBox } from '../bounds/immutable-bounding-box';
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
public left?: Node = null;
|
public left?: Node = null;
|
||||||
|
|
||||||
public right?: Node = null;
|
public right?: Node = null;
|
||||||
|
|
||||||
constructor(public rectangle: ImmutableBoundingBox, public parent: Node) {}
|
constructor(public rectangle: ImmutableBoundingBox, public parent: Node) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
10
frontend/src/scripts/physics/dynamic-physical-object.ts
Normal file
10
frontend/src/scripts/physics/dynamic-physical-object.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { BoundingCircle } from './bounds/bounding-circle';
|
||||||
|
import { PhysicalObject } from './physical-object';
|
||||||
|
|
||||||
|
export abstract class DynamicPhysicalObject extends PhysicalObject {
|
||||||
|
public abstract getBoundingCircles(): Array<BoundingCircle>;
|
||||||
|
|
||||||
|
protected addToPhysics() {
|
||||||
|
super.addToPhysics(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/src/scripts/physics/physical-object.ts
Normal file
21
frontend/src/scripts/physics/physical-object.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { GameObject } from '../objects/game-object';
|
||||||
|
import { BoundingBoxBase } from './bounds/bounding-box-base';
|
||||||
|
import { Physics } from './physics';
|
||||||
|
|
||||||
|
export abstract class PhysicalObject extends GameObject {
|
||||||
|
public abstract getBoundingBox(): BoundingBoxBase;
|
||||||
|
public abstract distance(target: vec2): number;
|
||||||
|
|
||||||
|
constructor(protected physics: Physics, public readonly canCollide: boolean) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected addToPhysics(isDynamic = false) {
|
||||||
|
if (isDynamic) {
|
||||||
|
this.physics.addDynamicBoundingBox(this.getBoundingBox());
|
||||||
|
} else {
|
||||||
|
this.physics.addStaticBoundingBox(this.getBoundingBox());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { BoundingBoxTree } from './containers/bounding-box-tree';
|
import { vec2 } from 'gl-matrix';
|
||||||
|
import { BoundingBoxBase } from './bounds/bounding-box-base';
|
||||||
|
import { BoundingCircle } from './bounds/bounding-circle';
|
||||||
|
import { ImmutableBoundingBox } from './bounds/immutable-bounding-box';
|
||||||
import { BoundingBoxList } from './containers/bounding-box-list';
|
import { BoundingBoxList } from './containers/bounding-box-list';
|
||||||
import { ImmutableBoundingBox } from '../shapes/immutable-bounding-box';
|
import { BoundingBoxTree } from './containers/bounding-box-tree';
|
||||||
import { BoundingBoxBase } from '../shapes/bounding-box-base';
|
|
||||||
|
|
||||||
export class Physics {
|
export class Physics {
|
||||||
private isTreeInitialized = false;
|
private isTreeInitialized = false;
|
||||||
|
|
||||||
private staticBoundingBoxesWaitList = [];
|
private staticBoundingBoxesWaitList = [];
|
||||||
|
|
||||||
private staticBoundingBoxes = new BoundingBoxTree();
|
private staticBoundingBoxes = new BoundingBoxTree();
|
||||||
|
|
||||||
private dynamicBoundingBoxes = new BoundingBoxList();
|
private dynamicBoundingBoxes = new BoundingBoxList();
|
||||||
|
|
||||||
public addStaticBoundingBox(boundingBox: ImmutableBoundingBox) {
|
public addStaticBoundingBox(boundingBox: ImmutableBoundingBox) {
|
||||||
|
|
@ -24,6 +23,62 @@ export class Physics {
|
||||||
this.dynamicBoundingBoxes.insert(boundingBox);
|
this.dynamicBoundingBoxes.insert(boundingBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public tryMovingDynamicCircle(
|
||||||
|
circle: BoundingCircle,
|
||||||
|
delta: vec2,
|
||||||
|
invocationCount = 0
|
||||||
|
) {
|
||||||
|
circle.center = vec2.add(circle.center, circle.center, delta);
|
||||||
|
|
||||||
|
const intersecting = this.findIntersecting(circle.boundingBox).filter(
|
||||||
|
(b) =>
|
||||||
|
b.owner !== circle.owner && circle.areIntersecting(b.owner) && b.owner.canCollide
|
||||||
|
);
|
||||||
|
|
||||||
|
const pointCount = 16;
|
||||||
|
const points = circle.getPerimeterPoints(pointCount);
|
||||||
|
|
||||||
|
const distancesOfPoints = points.map((point) => ({
|
||||||
|
point,
|
||||||
|
distances: intersecting.map((i) => i.owner.distance(point)).sort((a, b) => a - b),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const distancesOfIntersectingPoints = distancesOfPoints.filter(
|
||||||
|
(d) => d.distances[0] > 0
|
||||||
|
);
|
||||||
|
|
||||||
|
if (distancesOfIntersectingPoints.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distanceToLeastIntersectingForEachPoint = distancesOfIntersectingPoints.map(
|
||||||
|
(pointDistances) => ({
|
||||||
|
point: pointDistances.point,
|
||||||
|
distance: pointDistances.distances[0],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const deltas = distanceToLeastIntersectingForEachPoint.map((pointDistance) => {
|
||||||
|
vec2.subtract(pointDistance.point, circle.center, pointDistance.point);
|
||||||
|
vec2.normalize(pointDistance.point, pointDistance.point);
|
||||||
|
|
||||||
|
vec2.scale(pointDistance.point, pointDistance.point, pointDistance.distance);
|
||||||
|
return pointDistance.point;
|
||||||
|
});
|
||||||
|
|
||||||
|
const sumDelta = vec2.fromValues(0, 0);
|
||||||
|
|
||||||
|
deltas.forEach((d) => vec2.add(sumDelta, sumDelta, d));
|
||||||
|
|
||||||
|
vec2.scale(sumDelta, sumDelta, 1 / deltas.length);
|
||||||
|
|
||||||
|
if (invocationCount > 10) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tryMovingDynamicCircle(circle, sumDelta, ++invocationCount);
|
||||||
|
}
|
||||||
|
|
||||||
public findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase> {
|
public findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase> {
|
||||||
return [
|
return [
|
||||||
...this.staticBoundingBoxes.findIntersecting(box),
|
...this.staticBoundingBoxes.findIntersecting(box),
|
||||||
|
|
|
||||||
7
frontend/src/scripts/physics/static-physical-object.ts
Normal file
7
frontend/src/scripts/physics/static-physical-object.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { PhysicalObject } from './physical-object';
|
||||||
|
|
||||||
|
export abstract class StaticPhysicalObject extends PhysicalObject {
|
||||||
|
protected addToPhysics() {
|
||||||
|
super.addToPhysics(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
frontend/src/scripts/settings.ts
Normal file
3
frontend/src/scripts/settings.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const settings = {
|
||||||
|
lightCutoffDistance: 600,
|
||||||
|
};
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,17 @@
|
||||||
import { mat2d, vec2 } from 'gl-matrix';
|
import { mat2d, vec2 } from 'gl-matrix';
|
||||||
import { Circle, Drawable, DrawableDescriptor } from 'sdf-2d';
|
import { Drawable, DrawableDescriptor } from 'sdf-2d';
|
||||||
import { GameObject } from '../../objects/game-object';
|
import { BoundingCircle } from '../../physics/bounds/bounding-circle';
|
||||||
import { BoundingBox } from '../bounding-box';
|
|
||||||
import { IShape } from '../i-shape';
|
|
||||||
import { CircleShape } from './circle-shape';
|
|
||||||
|
|
||||||
export class BlobShape extends Drawable implements IShape {
|
export class BlobShape extends Drawable {
|
||||||
public static descriptor: DrawableDescriptor = {
|
public static descriptor: DrawableDescriptor = {
|
||||||
sdf: {
|
sdf: {
|
||||||
shader: `
|
shader: `
|
||||||
uniform struct {
|
uniform vec2 headCenters[BLOB_COUNT];
|
||||||
vec2 headCenter;
|
uniform vec2 leftFootCenters[BLOB_COUNT];
|
||||||
vec2 leftFootCenter;
|
uniform vec2 rightFootCenters[BLOB_COUNT];
|
||||||
vec2 rightFootCenter;
|
uniform float headRadii[BLOB_COUNT];
|
||||||
float headRadius;
|
uniform float footRadii[BLOB_COUNT];
|
||||||
float footRadius;
|
//uniform float ks[BLOB_COUNT];
|
||||||
float k;
|
|
||||||
}[BLOB_COUNT] blobs;
|
|
||||||
|
|
||||||
float smoothMin(float a, float b)
|
float smoothMin(float a, float b)
|
||||||
{
|
{
|
||||||
|
|
@ -25,15 +20,18 @@ export class BlobShape extends Drawable implements IShape {
|
||||||
return -log2( res )/k;
|
return -log2( res )/k;
|
||||||
}
|
}
|
||||||
|
|
||||||
float circleDistance(vec2 circleCenter, float radius) {
|
float circleDistance(vec2 circleCenter, float radius, vec2 target) {
|
||||||
return distance(position, circleCenter) - radius;
|
return distance(target, circleCenter) - radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
void blobMinDistance(inout float minDistance, inout float color) {
|
float blobMinDistance(vec2 target, out float colorIndex) {
|
||||||
|
float minDistance = 1000.0;
|
||||||
|
colorIndex = 3.0;
|
||||||
|
|
||||||
for (int i = 0; i < BLOB_COUNT; i++) {
|
for (int i = 0; i < BLOB_COUNT; i++) {
|
||||||
float headDistance = circleDistance(blobs[i].headCenter, blobs[i].headRadius);
|
float headDistance = circleDistance(headCenters[i], headRadii[i], target);
|
||||||
float leftFootDistance = circleDistance(blobs[i].leftFootCenter, blobs[i].footRadius);
|
float leftFootDistance = circleDistance(leftFootCenters[i], footRadii[i], target);
|
||||||
float rightFootDistance = circleDistance(blobs[i].rightFootCenter, blobs[i].footRadius);
|
float rightFootDistance = circleDistance(rightFootCenters[i], footRadii[i], target);
|
||||||
|
|
||||||
float res = min(
|
float res = min(
|
||||||
smoothMin(headDistance, leftFootDistance),
|
smoothMin(headDistance, leftFootDistance),
|
||||||
|
|
@ -41,70 +39,51 @@ export class BlobShape extends Drawable implements IShape {
|
||||||
);
|
);
|
||||||
|
|
||||||
minDistance = min(minDistance, res);
|
minDistance = min(minDistance, res);
|
||||||
color = mix(1.0, color, step(distanceNdcPixelSize + SURFACE_OFFSET, res));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return minDistance;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
distanceFunctionName: 'blobMinDistance',
|
distanceFunctionName: 'blobMinDistance',
|
||||||
},
|
},
|
||||||
uniformName: 'blobs',
|
propertyUniformMapping: {
|
||||||
|
footRadius: 'footRadii',
|
||||||
|
headRadius: 'headRadii',
|
||||||
|
rightFootCenter: 'rightFootCenters',
|
||||||
|
leftFootCenter: 'leftFootCenters',
|
||||||
|
headCenter: 'headCenters',
|
||||||
|
},
|
||||||
uniformCountMacroName: 'BLOB_COUNT',
|
uniformCountMacroName: 'BLOB_COUNT',
|
||||||
shaderCombinationSteps: [1],
|
shaderCombinationSteps: [0, 1, 10],
|
||||||
empty: new BlobShape(vec2.fromValues(0, 0)),
|
empty: new BlobShape(),
|
||||||
};
|
};
|
||||||
|
protected head: BoundingCircle;
|
||||||
|
protected leftFoot: BoundingCircle;
|
||||||
|
protected rightFoot: BoundingCircle;
|
||||||
|
|
||||||
public readonly boundingCircleRadius = 100;
|
public constructor() {
|
||||||
|
|
||||||
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();
|
super();
|
||||||
this.position = center;
|
|
||||||
|
const circle = new BoundingCircle(null, vec2.create(), 200);
|
||||||
|
this.setCircles([circle, circle, circle]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public set position(value: vec2) {
|
public setCircles([head, leftFoot, rightFoot]: [
|
||||||
vec2.copy(this.boundingCircle.center, value);
|
BoundingCircle,
|
||||||
vec2.add(this.head.center, value, this.headOffset);
|
BoundingCircle,
|
||||||
vec2.add(this.leftFoot.center, value, this.leftFootOffset);
|
BoundingCircle
|
||||||
vec2.add(this.rightFoot.center, value, this.rightFootOffset);
|
]) {
|
||||||
}
|
this.head = head;
|
||||||
|
this.leftFoot = leftFoot;
|
||||||
public get center(): vec2 {
|
this.rightFoot = rightFoot;
|
||||||
return this.boundingCircle.center;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get radius(): number {
|
|
||||||
return this.boundingCircle.radius;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public minDistance(target: vec2): number {
|
public minDistance(target: vec2): number {
|
||||||
return this.boundingCircle.minDistance(target);
|
return Math.min(
|
||||||
}
|
this.head.distance(target),
|
||||||
public normal(from: vec2): vec2 {
|
this.leftFoot.distance(target),
|
||||||
return this.boundingCircle.normal(from);
|
this.rightFoot.distance(target)
|
||||||
}
|
);
|
||||||
|
|
||||||
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 {
|
protected getObjectToSerialize(transform2d: mat2d, transform1d: number): any {
|
||||||
|
|
@ -120,8 +99,8 @@ export class BlobShape extends Drawable implements IShape {
|
||||||
this.rightFoot.center,
|
this.rightFoot.center,
|
||||||
transform2d
|
transform2d
|
||||||
),
|
),
|
||||||
headRadius: this.headRadius * transform1d,
|
headRadius: this.head.radius * transform1d,
|
||||||
footRadius: this.footRadius * transform1d,
|
footRadius: this.leftFoot.radius * transform1d,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { Circle } from 'sdf-2d';
|
import { Circle } from 'sdf-2d';
|
||||||
import { GameObject } from '../../objects/game-object';
|
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(
|
public constructor(
|
||||||
center = vec2.create(),
|
center = vec2.create(),
|
||||||
radius = 0,
|
radius = 0,
|
||||||
|
|
@ -15,34 +11,6 @@ export class CircleShape extends Circle implements IShape {
|
||||||
super(center, radius);
|
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 {
|
public clone(): CircleShape {
|
||||||
return new CircleShape(vec2.clone(this.center), this.radius, this.gameObject);
|
return new CircleShape(vec2.clone(this.center), this.radius, this.gameObject);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +1,17 @@
|
||||||
import { vec2 } from 'gl-matrix';
|
import { vec2 } from 'gl-matrix';
|
||||||
import { InvertedTunnel } from 'sdf-2d';
|
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 {
|
export class TunnelShape extends InvertedTunnel {
|
||||||
constructor(
|
constructor(from: vec2, to: vec2, fromRadius: number, toRadius: number) {
|
||||||
readonly from: vec2,
|
|
||||||
readonly to: vec2,
|
|
||||||
readonly fromRadius: number,
|
|
||||||
readonly toRadius: number,
|
|
||||||
public readonly gameObject: GameObject = null
|
|
||||||
) {
|
|
||||||
super(from, to, fromRadius, toRadius);
|
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 {
|
public clone(): TunnelShape {
|
||||||
return new TunnelShape(
|
return new TunnelShape(
|
||||||
vec2.clone(this.from),
|
vec2.clone(this.from),
|
||||||
vec2.clone(this.to),
|
vec2.clone(this.to),
|
||||||
this.fromRadius,
|
this.fromRadius,
|
||||||
this.toRadius,
|
this.toRadius
|
||||||
this.gameObject
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue