Update physics

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +0,0 @@
export const exponentialDecay = (
accumulator: number,
nextValue: number,
biasOfNextValue: number
) => accumulator * (1 - biasOfNextValue) + nextValue * biasOfNextValue;

View file

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

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

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

View file

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

View file

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

View file

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

View file

@ -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);
vec2.scale(steppedDelta, steppedDelta, vec2.length(delta) % maxStep);
this.tryMoving(vec2.clone(steppedDelta), isFirstIteration);
return;
}
const nextShape = this.shape.clone();
vec2.add(nextShape.center, nextShape.center, delta);
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);
const intersecting = nextNearShapes
.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 }> { public getBoundingCircles(): Array<BoundingCircle> {
return this.game return [this.boundingCircle];
.findIntersecting(shape.boundingBox) }
.filter((b) => b.shape)
.map((b) => ({ public getBoundingBox(): BoundingBoxBase {
shape: b.shape, return this.head.boundingBox;
// TODO: fix this }
distance: b.shape.minDistance(shape.center) + shape.radius - 20,
})) private tryMoving(delta: vec2) {
.sort((e) => e.distance); this.physics.tryMovingDynamicCircle(this.head, delta);
} }
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));
} }

View file

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

View file

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

View file

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

View file

@ -0,0 +1,62 @@
import { vec2 } from 'gl-matrix';
import { PhysicalObject } from '../physical-object';
export abstract class BoundingBoxBase {
constructor(
public readonly owner: PhysicalObject,
protected _xMin: number = 0,
protected _xMax: number = 0,
protected _yMin: number = 0,
protected _yMax: number = 0,
public readonly isInverted = false
) {}
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

@ -0,0 +1,65 @@
import { vec2 } from 'gl-matrix';
import { BoundingBoxBase } from './bounding-box-base';
import { ImmutableBoundingBox } from './immutable-bounding-box';
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 {
return vec2.fromValues(this._xMin, this._yMax);
}
public set topLeft(value: vec2) {
this._xMin = value.x;
this._yMax = value.y;
}
public set size(value: vec2) {
this._xMax = this.xMin + value.x;
this._yMin = this.yMax - value.y;
}
public get size(): vec2 {
return vec2.fromValues(this._xMax - this._xMin, this._yMax - this._yMin);
}
public cloneAsImmutable(): ImmutableBoundingBox {
return new ImmutableBoundingBox(
this.owner,
this.xMin,
this.xMax,
this.yMin,
this.yMax
);
}
}

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

View file

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

View file

@ -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> = []) {}

Some files were not shown because too many files have changed in this diff Show more