This commit is contained in:
schmelczerandras 2020-08-04 22:07:39 +02:00
parent 24cc572e47
commit 345e183e34
30 changed files with 628 additions and 187 deletions

View file

@ -6,8 +6,26 @@ import './styles/main.scss';
glMatrix.setMatrixArrayType(Array);
applyArrayPlugins();
/*
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')));
*/
try {
new Game();
} catch (e) {
console.error(e);
alert(e);
}

View file

@ -1,7 +1,11 @@
import { vec2 } from 'gl-matrix';
import { ImmutableBoundingBox } from '../../physics/containers/immutable-bounding-box';
import { GameObject } from '../../objects/game-object';
export interface IDrawable {
serializeToUniforms(uniforms: any): void;
distance(target: vec2): number;
minimumDistance(target: vec2): number;
readonly owner: GameObject;
readonly boundingBox: ImmutableBoundingBox;
}

View file

@ -2,6 +2,8 @@ import { ILight } from './i-light';
import { vec2, vec3 } from 'gl-matrix';
import { settings } from '../../settings';
import { IDrawableDescriptor } from '../i-drawable-descriptor';
import { ImmutableBoundingBox } from '../../../physics/containers/immutable-bounding-box';
import { GameObject } from '../../../objects/game-object';
export class CircleLight implements ILight {
public static descriptor: IDrawableDescriptor = {
@ -11,12 +13,15 @@ export class CircleLight implements ILight {
};
constructor(
public readonly owner: GameObject,
public center: vec2,
public radius: number,
public color: vec3,
public lightness: number
) {}
boundingBox: ImmutableBoundingBox;
public distance(target: vec2): number {
return 0;
}

View file

@ -2,6 +2,8 @@ import { ILight } from './i-light';
import { vec2, vec3 } from 'gl-matrix';
import { IDrawableDescriptor } from '../i-drawable-descriptor';
import { settings } from '../../settings';
import { ImmutableBoundingBox } from '../../../physics/containers/immutable-bounding-box';
import { GameObject } from '../../../objects/game-object';
export class PointLight implements ILight {
public static descriptor: IDrawableDescriptor = {
@ -10,13 +12,16 @@ export class PointLight implements ILight {
shaderCombinationSteps: settings.shaderCombinations.pointLightSteps,
};
constructor(
public constructor(
public readonly owner: GameObject,
public center: vec2,
public radius: number,
public color: vec3,
public lightness: number
) {}
boundingBox: ImmutableBoundingBox;
public distance(target: vec2): number {
return vec2.distance(this.center, target) - this.radius;
}

View file

@ -1,8 +1,14 @@
import { vec2 } from 'gl-matrix';
import { IPrimitive } from './i-primitive';
import { ImmutableBoundingBox } from '../../../physics/containers/immutable-bounding-box';
import { GameObject } from '../../../objects/game-object';
export class Circle implements IPrimitive {
public constructor(public center = vec2.create(), public radius = 0) {}
public constructor(
public readonly owner: GameObject,
public center = vec2.create(),
public radius = 0
) {}
public serializeToUniforms(uniforms: any): void {
throw new Error('Method not implemented.');
@ -16,6 +22,16 @@ export class Circle implements IPrimitive {
return vec2.distance(this.center, target) - this.radius;
}
public get boundingBox(): ImmutableBoundingBox {
return new ImmutableBoundingBox(
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;
}

View file

@ -1,18 +0,0 @@
import { vec2 } from 'gl-matrix';
export class Rectangle {
public constructor(
public topLeft: vec2 = vec2.create(),
public size: vec2 = vec2.create()
) {}
public isInside(position: vec2): boolean {
const translated = vec2.subtract(vec2.create(), position, this.topLeft);
return (
0 <= translated.x &&
translated.x < this.size.x &&
0 <= translated.y &&
translated.y < this.size.y
);
}
}

View file

@ -1,10 +1,12 @@
import { vec2 } from 'gl-matrix';
import { clamp01 } from '../../../helper/clamp';
import { mix } from '../../../helper/mix';
import { Circle } from './circle';
import { IPrimitive } from './i-primitive';
import { IDrawableDescriptor } from '../i-drawable-descriptor';
import { IPrimitive } from './i-primitive';
import { settings } from '../../settings';
import { Circle } from './circle';
import { mix } from '../../../helper/mix';
import { clamp01 } from '../../../helper/clamp';
import { ImmutableBoundingBox } from '../../../physics/containers/immutable-bounding-box';
import { GameObject } from '../../../objects/game-object';
export class TunnelShape implements IPrimitive {
public static descriptor: IDrawableDescriptor = {
@ -14,11 +16,11 @@ export class TunnelShape implements IPrimitive {
};
public readonly toFromDelta: vec2;
private toFromDeltaLength: number;
private boundingCircle: Circle;
constructor(
public readonly owner: GameObject,
public readonly from: vec2,
public readonly to: vec2,
public readonly fromRadius: number,
@ -27,12 +29,13 @@ export class TunnelShape implements IPrimitive {
this.toFromDelta = vec2.subtract(vec2.create(), to, from);
this.boundingCircle = new Circle(
this.owner,
vec2.fromValues(from.x / 2 + to.x / 2, from.y / 2 + to.y / 2),
Math.max(fromRadius, toRadius) + vec2.distance(from, to)
);
}
serializeToUniforms(uniforms: any): void {
public serializeToUniforms(uniforms: any): void {
if (!uniforms.hasOwnProperty(TunnelShape.descriptor.uniformName)) {
uniforms[TunnelShape.descriptor.uniformName] = [];
}
@ -45,6 +48,27 @@ export class TunnelShape implements IPrimitive {
});
}
public get boundingBox(): ImmutableBoundingBox {
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 ImmutableBoundingBox(this, xMin, xMax, yMin, yMax);
}
public distance(target: vec2): number {
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01(

View file

@ -1,7 +1,7 @@
import { vec2 } from 'gl-matrix';
export interface IProgram {
setDrawingRectangle(topLeft: vec2, size: vec2): void;
setDrawingRectangle(bottomLeft: vec2, size: vec2): void;
bindAndSetUniforms(values: { [name: string]: any }): void;
draw(): void;
delete(): void;

View file

@ -32,10 +32,10 @@ export abstract class Program implements IProgram {
this.setUniforms({ modelTransform: this.modelTransform, ...values });
}
public setDrawingRectangle(topLeft: vec2, size: vec2) {
public setDrawingRectangle(bottomLeft: vec2, size: vec2) {
mat2d.invert(this.modelTransform, this.ndcToUv);
mat2d.translate(this.modelTransform, this.modelTransform, topLeft);
mat2d.translate(this.modelTransform, this.modelTransform, bottomLeft);
mat2d.scale(this.modelTransform, this.modelTransform, size);
mat2d.multiply(this.modelTransform, this.modelTransform, this.ndcToUv);
}

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix';
import { IPrimitive } from './drawables/primitives/i-primitive';
import { ILight } from './drawables/lights/i-light';
import { IPrimitive } from './drawables/primitives/i-primitive';
export interface IRenderer {
startFrame(deltaTime: DOMHighResTimeStamp): void;

View file

@ -1,11 +1,10 @@
import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer';
import { IDrawable } from '../drawables/i-drawable';
import { settings } from '../settings';
import { vec2 } from 'gl-matrix';
import { Circle } from '../drawables/primitives/circle';
import { InfoText } from '../../objects/types/info-text';
import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program';
import { IDrawable } from '../drawables/i-drawable';
import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor';
import { FrameBuffer } from '../graphics-library/frame-buffer/frame-buffer';
import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program';
import { settings } from '../settings';
export class RenderingPass {
private drawables: Array<IDrawable> = [];
@ -30,7 +29,8 @@ export class RenderingPass {
public render(
commonUniforms: any,
viewCircle: Circle,
viewBoxCenter: vec2,
viewBoxRadius: number,
inputTexture?: WebGLTexture
) {
this.frame.bindAndClear(inputTexture);
@ -38,7 +38,7 @@ export class RenderingPass {
const tileUvSize = vec2.fromValues(q, q);
const possiblyOnScreenDrawables = this.drawables.filter(
(p) => p.minimumDistance(viewCircle.center) < viewCircle.radius
(p) => p.minimumDistance(viewBoxCenter) < viewBoxRadius
);
const origin = vec2.transformMat2d(

View file

@ -1,34 +1,29 @@
import { mat2d, vec2, vec3 } from 'gl-matrix';
import { CircleLight } from '../drawables/lights/circle-light';
import { ILight } from '../drawables/lights/i-light';
import { PointLight } from '../drawables/lights/point-light';
import { IPrimitive } from '../drawables/primitives/i-primitive';
import { TunnelShape } from '../drawables/primitives/tunnel-shape';
// import lightsShader from '../shaders/rainbow-shading-fs.glsl';
import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer';
import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer';
import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context';
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
import { IRenderer } from '../i-renderer';
import caveFragmentShader from '../shaders/cave-distance-fs.glsl';
import lightsFragmentShader from '../shaders/lights-shading-fs.glsl';
import caveVertexShader from '../shaders/passthrough-distance-vs.glsl';
import lightsVertexShader from '../shaders/passthrough-shading-vs.glsl';
// import lightsShader from '../shaders/rainbow-shading-fs.glsl';
import { DefaultFrameBuffer } from '../graphics-library/frame-buffer/default-frame-buffer';
import { IntermediateFrameBuffer } from '../graphics-library/frame-buffer/intermediate-frame-buffer';
import { WebGlStopwatch } from '../graphics-library/helper/stopwatch';
import { IProgram } from '../graphics-library/program/i-program';
import { UniformArrayAutoScalingProgram } from '../graphics-library/program/uniform-array-autoscaling-program';
import { IRenderer } from '../i-renderer';
import { Circle } from '../drawables/primitives/circle';
import { IPrimitive } from '../drawables/primitives/i-primitive';
import { Rectangle } from '../drawables/primitives/rectangle';
import { ILight } from '../drawables/lights/i-light';
import { settings } from '../settings';
import { FpsAutoscaler } from './fps-autoscaler';
import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context';
import { RenderingPass } from './rendering-pass';
import { TunnelShape } from '../drawables/primitives/tunnel-shape';
import { CircleLight } from '../drawables/lights/circle-light';
import { PointLight } from '../drawables/lights/point-light';
export class WebGl2Renderer implements IRenderer {
private gl: WebGL2RenderingContext;
private stopwatch?: WebGlStopwatch;
private viewBox: Rectangle = new Rectangle();
private viewCircle: Circle = new Circle();
private viewBoxBottomLeft = vec2.create();
private cameraPosition = vec2.create();
private viewBoxSize = vec2.create();
private cursorPosition = vec2.create();
private distanceFieldFrameBuffer: IntermediateFrameBuffer;
@ -38,6 +33,15 @@ export class WebGl2Renderer implements IRenderer {
private autoscaler: FpsAutoscaler;
private matrices: {
distanceScreenToWorld?: mat2d;
worldToDistanceUV?: mat2d;
cursorPosition?: mat2d;
ndcToUv?: mat2d;
uvToWorld?: mat2d;
viewBoxSize?: mat2d;
} = { ndcToUv: mat2d.fromValues(0.5, 0, 0, 0.5, 0.5, 0.5) };
constructor(private canvas: HTMLCanvasElement, private overlay: HTMLElement) {
this.gl = getWebGl2Context(canvas);
@ -85,64 +89,69 @@ export class WebGl2Renderer implements IRenderer {
}
public finishFrame() {
const uniforms = this.calculateOwnUniforms();
this.lightingPass.addDrawable(
new PointLight(uniforms.cursorPosition, 200, vec3.fromValues(1, 1, 0), 1)
);
this.distancePass.render(uniforms, this.viewCircle);
this.lightingPass.render(
uniforms,
this.viewCircle,
this.distanceFieldFrameBuffer.colorTexture
);
this.stopwatch?.stop();
}
private calculateOwnUniforms(): any {
const distanceScreenToWorld = this.getScreenToWorldTransform(
this.distanceFieldFrameBuffer.getSize()
);
const uvToWorld = mat2d.fromTranslation(
mat2d.create(),
this.viewBox.topLeft
);
mat2d.scale(uvToWorld, uvToWorld, this.viewBox.size);
const worldToDistanceUV = mat2d.scale(
mat2d.create(),
distanceScreenToWorld,
this.distanceFieldFrameBuffer.getSize()
);
mat2d.invert(worldToDistanceUV, worldToDistanceUV);
const ndcToUv = mat2d.fromScaling(
mat2d.create(),
vec2.fromValues(0.5, 0.5)
);
mat2d.translate(ndcToUv, ndcToUv, vec2.fromValues(1, 1));
this.calculateMatrices();
const cursorPosition = this.screenUvToWorldCoordinate(this.cursorPosition);
return {
distanceScreenToWorld,
worldToDistanceUV,
cursorPosition,
ndcToUv,
uvToWorld,
viewBoxSize: this.viewBox.size,
};
this.lightingPass.addDrawable(
new PointLight(null, cursorPosition, 200, vec3.fromValues(1, 1, 0), 1)
);
const viewBoxRadius = vec2.length(
vec2.scale(vec2.create(), this.viewBoxSize, 0.5)
);
this.distancePass.render(
{ ...this.matrices, cursorPosition },
this.cameraPosition,
viewBoxRadius
);
this.lightingPass.render(
{ ...this.matrices, cursorPosition },
this.cameraPosition,
viewBoxRadius,
this.distanceFieldFrameBuffer.colorTexture
);
this.stopwatch?.stop();
}
private calculateMatrices() {
this.matrices.uvToWorld = mat2d.fromTranslation(
mat2d.create(),
this.viewBoxBottomLeft
);
mat2d.scale(
this.matrices.uvToWorld,
this.matrices.uvToWorld,
this.viewBoxSize
);
this.matrices.distanceScreenToWorld = this.getScreenToWorldTransform(
this.distanceFieldFrameBuffer.getSize()
);
this.matrices.worldToDistanceUV = mat2d.scale(
mat2d.create(),
this.matrices.distanceScreenToWorld,
this.distanceFieldFrameBuffer.getSize()
);
mat2d.invert(
this.matrices.worldToDistanceUV,
this.matrices.worldToDistanceUV
);
}
private getScreenToWorldTransform(screenSize: vec2) {
const transform = mat2d.fromTranslation(
mat2d.create(),
this.viewBox.topLeft
this.viewBoxBottomLeft
);
mat2d.scale(
transform,
transform,
vec2.divide(vec2.create(), this.viewBox.size, screenSize)
vec2.divide(vec2.create(), this.viewBoxSize, screenSize)
);
mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5));
@ -160,12 +169,10 @@ export class WebGl2Renderer implements IRenderer {
}
public setCameraPosition(position: vec2) {
this.viewBox.topLeft = position;
const halfDiagonal = vec2.scale(vec2.create(), this.viewBox.size, 0.5);
this.viewCircle.center = vec2.add(
vec2.create(),
this.viewBox.topLeft,
halfDiagonal
this.cameraPosition = position;
this.viewBoxBottomLeft = vec2.fromValues(
this.cameraPosition.x - this.viewBoxSize.x / 2,
this.cameraPosition.y - this.viewBoxSize.y / 2
);
}
@ -177,21 +184,10 @@ export class WebGl2Renderer implements IRenderer {
const canvasAspectRatio =
this.canvas.clientWidth / this.canvas.clientHeight;
this.viewBox.size = vec2.fromValues(
return (this.viewBoxSize = vec2.fromValues(
Math.sqrt(size * canvasAspectRatio),
Math.sqrt(size / canvasAspectRatio)
);
const halfDiagonal = vec2.scale(vec2.create(), this.viewBox.size, 0.5);
this.viewCircle.center = vec2.add(
vec2.create(),
this.viewBox.topLeft,
halfDiagonal
);
this.viewCircle.radius = vec2.length(halfDiagonal);
return this.viewBox.size;
));
}
public drawInfoText(text: string) {

View file

@ -6,6 +6,7 @@ precision mediump float;
#define POINT_LIGHT_COUNT {pointLightCount}
uniform mat3 modelTransform;
uniform mat3 cameraTransform;
uniform mat3 ndcToUv;
uniform mat3 uvToWorld;

View file

@ -6,16 +6,22 @@ import { timeIt } from './helper/timing';
import { KeyboardListener } from './input/keyboard-listener';
import { MouseListener } from './input/mouse-listener';
import { TouchListener } from './input/touch-listener';
import { ObjectContainer } from './objects/object-container';
import { Objects } from './objects/objects';
import { InfoText } from './objects/types/info-text';
import { createCharacter } from './objects/world/create-character';
import { createDungeon } from './objects/world/create-dungeon';
import { RenderCommand } from './drawing/commands/render';
import { Physics } from './physics/physics';
import { MoveToCommand } from './physics/commands/move-to';
import { TeleportToCommand } from './physics/commands/teleport-to';
import { IRenderer } from './drawing/i-renderer';
export class Game {
private previousTime?: DOMHighResTimeStamp = null;
private objects: ObjectContainer = new ObjectContainer();
private renderer: WebGl2Renderer;
private objects = new Objects();
private physics = new Physics();
private renderer: IRenderer;
private previousFpsValues: Array<number> = [];
constructor() {
@ -39,14 +45,18 @@ export class Game {
this.renderer = new WebGl2Renderer(canvas, overlay);
this.initializeScene();
this.physics.start();
requestAnimationFrame(this.gameLoop.bind(this));
}
private initializeScene() {
this.objects.addObject(new InfoText());
createCharacter(this.objects);
createDungeon(this.objects);
createDungeon(this.objects);
const start = createDungeon(this.objects, this.physics);
createDungeon(this.objects, this.physics);
const character = createCharacter(this.objects, this.physics);
console.log('start', start.from);
character.sendCommand(new TeleportToCommand(start.from));
}
private handleVisibilityChange() {

View file

@ -1,33 +1,16 @@
import { vec2 } from 'gl-matrix';
import { Command } from '../commands/command';
import { CommandReceiver } from '../commands/command-receiver';
import { IdentityManager } from '../identity/identity-manager';
import { Objects } from './objects';
import { Physics } from '../physics/physics';
export abstract class GameObject implements CommandReceiver {
public readonly id = IdentityManager.generateId();
protected _position = vec2.create();
public get position(): vec2 {
return this._position;
}
protected _boundingBoxSize = vec2.create();
public get boundingBoxSize(): vec2 {
return this._boundingBoxSize;
}
private commandExecutors: {
[commandType: string]: (e: Command) => void;
} = {};
// can only be called inside the constructor
protected addCommandExecutor<T extends Command>(
commandType: new () => T,
handler: (command: T) => void
) {
this.commandExecutors[new commandType().type] = handler;
}
public reactsToCommand(commandType: string): boolean {
return this.commandExecutors.hasOwnProperty(commandType);
}
@ -39,4 +22,12 @@ export abstract class GameObject implements CommandReceiver {
this.commandExecutors[commandType](command);
}
}
// can only be called inside the constructor
protected addCommandExecutor<T extends Command>(
commandType: new () => T,
handler: (command: T) => void
) {
this.commandExecutors[new commandType().type] = handler;
}
}

View file

@ -3,7 +3,7 @@ import { CommandReceiver } from '../commands/command-receiver';
import { Id } from '../identity/identity';
import { GameObject } from './game-object';
export class ObjectContainer implements CommandReceiver {
export class Objects implements CommandReceiver {
private objects: Map<Id, GameObject> = new Map();
private objectsGroupedByAbilities: Map<string, Array<GameObject>> = new Map();

View file

@ -5,14 +5,20 @@ import { GameObject } from '../game-object';
import { Lamp } from './lamp';
import { ZoomCommand } from '../../input/commands/zoom';
import { MoveToCommand } from '../../physics/commands/move-to';
import { BoundingBox } from '../../physics/containers/bounding-box';
import { Physics } from '../../physics/physics';
export class Camera extends GameObject {
private inViewArea = 1920 * 1080 * 5;
private cursorPosition = vec2.create();
private boundingBox: BoundingBox;
constructor(private light: Lamp) {
constructor(physics: Physics, private light: Lamp) {
super();
this.boundingBox = new BoundingBox(null);
physics.addDynamicBoundingBox(this.boundingBox);
this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
this.addCommandExecutor(
@ -22,20 +28,27 @@ export class Camera extends GameObject {
this.addCommandExecutor(ZoomCommand, this.zoom.bind(this));
}
public get viewAreaSize(): vec2 {
return this.boundingBox.size;
}
private draw(c: BeforeRenderCommand) {
c.renderer.setCameraPosition(this.position);
console.log('camera', this.boundingBox.topLeft);
c.renderer.setCameraPosition(this.boundingBox.topLeft);
c.renderer.setCursorPosition(this.cursorPosition);
this._boundingBoxSize = c.renderer.setInViewArea(this.inViewArea);
this.boundingBox.size = c.renderer.setInViewArea(this.inViewArea);
}
private moveTo(c: MoveToCommand) {
this._position = c.position;
console.log('camera', c.position);
this.boundingBox.topLeft = c.position;
this.light.sendCommand(
new MoveToCommand(
vec2.add(
vec2.create(),
c.position,
vec2.scale(vec2.create(), this.boundingBoxSize, 0.5)
vec2.scale(vec2.create(), this.boundingBox.size, 0.5)
)
)
);

View file

@ -1,37 +1,80 @@
import { vec2 } from 'gl-matrix';
import { GameObject } from '../game-object';
import { Camera } from './camera';
import { StepCommand } from '../../physics/commands/step';
import { Circle } from '../../drawing/drawables/primitives/circle';
import { KeyDownCommand } from '../../input/commands/key-down';
import { KeyUpCommand } from '../../input/commands/key-up';
import { SwipeCommand } from '../../input/commands/swipe';
import { MoveToCommand } from '../../physics/commands/move-to';
import { StepCommand } from '../../physics/commands/step';
import { TeleportToCommand } from '../../physics/commands/teleport-to';
import { BoundingBox } from '../../physics/containers/bounding-box';
import { Physics } from '../../physics/physics';
import { GameObject } from '../game-object';
import { Camera } from './camera';
export class Character extends GameObject {
private keysDown: Set<string> = new Set();
private static speed = 0.5;
private primitive: Circle;
private static speed = 1.5;
constructor(private camera: Camera) {
constructor(private physics: Physics, private camera: Camera) {
super();
this.primitive = new Circle(this);
this.primitive.radius = 20;
this.addCommandExecutor(StepCommand, this.stepHandler.bind(this));
this.addCommandExecutor(TeleportToCommand, (c) =>
this.setPosition(c.position)
);
this.addCommandExecutor(KeyDownCommand, (c) => this.keysDown.add(c.key));
this.addCommandExecutor(KeyUpCommand, (c) => this.keysDown.delete(c.key));
this.addCommandExecutor(SwipeCommand, (c) =>
this.setPosition(
this.checkAndSetPosition(
vec2.add(
vec2.create(),
this.position,
vec2.multiply(vec2.create(), c.delta, this.camera.boundingBoxSize)
this.primitive.center,
vec2.multiply(vec2.create(), c.delta, this.camera.viewAreaSize)
)
)
);
}
private checkAndSetPosition(value: vec2) {
const size = this.camera.viewAreaSize;
const realCenter = vec2.fromValues(
value.x + size.x / 2,
value.y + size.y / 2
);
const targetBoundingBox = new BoundingBox(
null,
value.x + size.x / 2,
value.x + size.x / 2 + 10,
value.y + size.y / 2,
value.y + size.y / 2 + 10
);
console.log(
this.physics
.findIntersecting(targetBoundingBox)
.map((b) => b.value.distance(realCenter))
);
if (
this.physics
.findIntersecting(targetBoundingBox)
.map((b) => b.value.distance(realCenter))
.find((d) => d < 0) !== undefined
) {
this.setPosition(value);
}
}
private setPosition(value: vec2) {
this._position = value;
this.camera.sendCommand(new MoveToCommand(this.position));
console.log('character', value);
this.primitive.center = value;
this.camera.sendCommand(new MoveToCommand(this.primitive.center));
}
public stepHandler(c: StepCommand) {
@ -44,16 +87,11 @@ export class Character extends GameObject {
const movementVector = vec2.fromValues(right - left, up - down);
if (movementVector.length > 0) {
this.setPosition(
vec2.add(
vec2.create(),
this.position,
vec2.scale(
vec2.create(),
vec2.normalize(movementVector, movementVector),
Character.speed * deltaTime
)
)
vec2.normalize(movementVector, movementVector);
vec2.scale(movementVector, movementVector, Character.speed * deltaTime);
this.checkAndSetPosition(
vec2.add(vec2.create(), this.primitive.center, movementVector)
);
}
}

View file

@ -10,7 +10,7 @@ export class Lamp extends GameObject {
constructor(center: vec2, radius: number, color: vec3, lightness: number) {
super();
this.light = new CircleLight(center, radius, color, lightness);
this.light = new CircleLight(this, center, radius, color, lightness);
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));

View file

@ -1,16 +1,25 @@
import { vec2 } from 'gl-matrix';
import { TunnelShape } from '../../drawing/drawables/primitives/tunnel-shape';
import { GameObject } from '../game-object';
import { RenderCommand } from '../../drawing/commands/render';
import { Physics } from '../../physics/physics';
import { TunnelShape } from '../../drawing/drawables/primitives/tunnel-shape';
export interface Line {}
export class Tunnel extends GameObject {
private primitive: TunnelShape;
constructor(from: vec2, to: vec2, fromRadius: number, toRadius: number) {
constructor(
physics: Physics,
public readonly from: vec2,
to: vec2,
fromRadius: number,
toRadius: number
) {
super();
this.primitive = new TunnelShape(from, to, fromRadius, toRadius);
this.primitive = new TunnelShape(this, from, to, fromRadius, toRadius);
physics.addStaticBoundingBox(this.primitive.boundingBox);
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
}

View file

@ -1,10 +1,15 @@
import { vec2, vec3 } from 'gl-matrix';
import { ObjectContainer } from '../object-container';
import { Objects } from '../objects';
import { Camera } from '../types/camera';
import { Character } from '../types/character';
import { Lamp } from '../types/lamp';
import { Physics } from '../../physics/physics';
import { GameObject } from '../game-object';
export const createCharacter = (objects: ObjectContainer) => {
export const createCharacter = (
objects: Objects,
physics: Physics
): GameObject => {
const light = new Lamp(
vec2.create(),
40,
@ -12,8 +17,11 @@ export const createCharacter = (objects: ObjectContainer) => {
2
);
const camera = new Camera(light);
const camera = new Camera(physics, light);
const character = new Character(physics, camera);
objects.addObject(light);
objects.addObject(camera);
objects.addObject(new Character(camera));
objects.addObject(character);
return character;
};

View file

@ -1,22 +1,35 @@
import { vec2, vec3 } from 'gl-matrix';
import { ObjectContainer } from '../object-container';
import { Lamp } from '../types/lamp';
import { Objects } from '../objects';
import { Tunnel } from '../types/tunnel';
import { Physics } from '../../physics/physics';
import { GameObject } from '../game-object';
export const createDungeon = (objects: ObjectContainer) => {
export const createDungeon = (objects: Objects, physics: Physics): Tunnel => {
let previousRadius = 350;
let previousEnd = vec2.create();
for (let i = 0; i < 500000; i += 500) {
let first: Tunnel;
for (let i = 0; i < 1; i += 500) {
const deltaHeight = (Math.random() - 0.5) * 2000;
const height = previousEnd.y + deltaHeight;
const currentEnd = vec2.fromValues(i, height);
const currentToRadius = Math.random() * 300 + 150;
objects.addObject(
new Tunnel(previousEnd, currentEnd, previousRadius, currentToRadius)
const tunnel = new Tunnel(
physics,
previousEnd,
currentEnd,
previousRadius,
currentToRadius
);
if (!first) {
first = tunnel;
}
objects.addObject(tunnel);
/*if (deltaHeight > 0 && Math.random() > 0.8) {
objects.addObject(
new Lamp(
@ -35,4 +48,6 @@ export const createDungeon = (objects: ObjectContainer) => {
previousEnd = currentEnd;
previousRadius = currentToRadius;
}
return first;
};

View file

@ -0,0 +1,12 @@
import { vec2 } from 'gl-matrix';
import { Command } from '../../commands/command';
export class TeleportToCommand extends Command {
public constructor(public readonly position?: vec2) {
super();
}
public get type(): string {
return 'TeleportToCommand';
}
}

View file

@ -0,0 +1,61 @@
import { vec2 } from 'gl-matrix';
import { IPrimitive } from '../../drawing/drawables/primitives/i-primitive';
export abstract class BoundingBoxBase {
constructor(
public readonly value: IPrimitive,
protected _xMin: number,
protected _xMax: number,
protected _yMin: number,
protected _yMax: number
) {}
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,13 @@
import { BoundingBoxBase } from './bounding-box-base';
export class BoundingBoxList {
constructor(private boundingBoxes: Array<BoundingBoxBase> = []) {}
public insert(box: BoundingBoxBase) {
this.boundingBoxes.push(box);
}
public findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase> {
return this.boundingBoxes.filter((b) => b.intersects(box));
}
}

View file

@ -0,0 +1,138 @@
import { ImmutableBoundingBox } from './immutable-bounding-box';
// source: https://github.com/ubilabs/kd-tree-javascript/blob/master/kdTree.js
class Node {
public left?: Node = null;
public right?: Node = null;
constructor(public rectangle: ImmutableBoundingBox, public parent: Node) {}
}
export class BoundingBoxTree {
root?: Node;
constructor(boxes?: Array<ImmutableBoundingBox>) {
if (boxes) {
this.build(boxes);
}
}
public build(boxes: Array<ImmutableBoundingBox>) {
this.root = this.buildRecursive(boxes, 0, null);
}
private buildRecursive(
boxes: Array<ImmutableBoundingBox>,
depth: number,
parent: Node
): Node {
if (boxes.length === 0) {
return null;
}
if (boxes.length === 1) {
return new Node(boxes[0], parent);
}
const dimension = depth % 4;
boxes.sort((a, b) => a[dimension] - b[dimension]);
const median = Math.floor(boxes.length / 2);
const node = new Node(boxes[median], parent);
node.left = this.buildRecursive(boxes.slice(0, median), depth + 1, node);
node.right = this.buildRecursive(boxes.slice(median + 1), depth + 1, node);
return node;
}
public insert(box: ImmutableBoundingBox) {
const [insertPosition, depth] = this.findParent(box, this.root, 0, null);
if (insertPosition === null) {
this.root = new Node(box, null);
} else {
const node = new Node(box, insertPosition);
const dimension = depth % 4;
if (box[dimension] < insertPosition.rectangle[dimension]) {
insertPosition.left = node;
} else {
insertPosition.right = node;
}
}
}
public print() {
this.printRecursive(this.root, 0);
}
private printRecursive(node: Node, tabCount: number) {
if (node === null) {
return;
}
console.log(' '.repeat(tabCount) + '- ' + node.rectangle.value);
this.printRecursive(node.left, tabCount + 2);
this.printRecursive(node.right, tabCount + 2);
}
public findIntersecting(
box: ImmutableBoundingBox
): Array<ImmutableBoundingBox> {
const maybeResults = this.findMaybeIntersecting(box, this.root, 0);
const results = maybeResults.filter(box.intersects.bind(box));
return results;
}
private findMaybeIntersecting(
box: ImmutableBoundingBox,
node: Node,
depth: number
): Array<ImmutableBoundingBox> {
if (node === null) {
return [];
}
const comparisons: Array<(
a: ImmutableBoundingBox,
b: ImmutableBoundingBox
) => boolean> = [
(a, b) => a.xMin < b.xMax,
(a, b) => a.xMax > b.xMin,
(a, b) => a.yMin < b.yMax,
(a, b) => a.xMax > b.xMin,
];
if (comparisons[depth % 4](node.rectangle, box)) {
return [
node.rectangle,
...this.findMaybeIntersecting(box, node.left, depth + 1),
...this.findMaybeIntersecting(box, node.right, depth + 1),
];
}
return this.findMaybeIntersecting(box, node.left, depth + 1);
}
private findParent(
box: ImmutableBoundingBox,
node: Node,
depth: number,
parent: Node
): [Node, number] {
if (node === null) {
return [parent, depth];
}
const dimension = depth % 4;
if (box[dimension] < node.rectangle[dimension]) {
return this.findParent(box, node.left, depth + 1, node);
}
return this.findParent(box, node.right, depth + 1, node);
}
}

View file

@ -0,0 +1,32 @@
import { vec2 } from 'gl-matrix';
import { BoundingBoxBase } from './bounding-box-base';
import { IPrimitive } from '../../drawing/drawables/primitives/i-primitive';
export class BoundingBox extends BoundingBoxBase {
constructor(
value: IPrimitive,
xMin: number = 0,
xMax: number = 0,
yMin: number = 0,
yMax: number = 0
) {
super(value, xMin, xMax, yMin, 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 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;
}
}

View file

@ -0,0 +1,14 @@
import { BoundingBoxBase } from './bounding-box-base';
import { IPrimitive } from '../../drawing/drawables/primitives/i-primitive';
export class ImmutableBoundingBox extends BoundingBoxBase {
constructor(
value: IPrimitive,
xMin: number = 0,
xMax: number = 0,
yMin: number = 0,
yMax: number = 0
) {
super(value, xMin, xMax, yMin, yMax);
}
}

View file

@ -0,0 +1,36 @@
import { BoundingBoxTree } from './containers/bounding-box-tree';
import { BoundingBoxList } from './containers/bounding-box-list';
import { ImmutableBoundingBox } from './containers/immutable-bounding-box';
import { BoundingBoxBase } from './containers/bounding-box-base';
export class Physics {
private staticBoundingBoxesWaitList = [];
private isTreeInitialized = false;
private staticBoundingBoxes = new BoundingBoxTree();
private dynamicBoundingBoxes = new BoundingBoxList();
public addStaticBoundingBox(boundingBox: ImmutableBoundingBox) {
this.staticBoundingBoxesWaitList.push(boundingBox);
}
public addDynamicBoundingBox(boundingBox: BoundingBoxBase) {
if (this.isTreeInitialized) {
this.staticBoundingBoxes.insert(boundingBox);
} else {
this.dynamicBoundingBoxes.insert(boundingBox);
}
}
public findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase> {
return [
...this.staticBoundingBoxes.findIntersecting(box),
...this.dynamicBoundingBoxes.findIntersecting(box),
];
}
public start() {
this.staticBoundingBoxes.build(this.staticBoundingBoxesWaitList);
this.isTreeInitialized = true;
}
}

View file

@ -8,7 +8,7 @@ certbot certonly \
--dns-digitalocean-propagation-seconds 120 \
-m "schmelczerandras@gmail.com" \
-d "decla.red" -d "*.decla.red" \
--agree-tos
--agree-tos --non-interactive
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf > "/etc/letsencrypt/options-ssl-nginx.conf"
curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "/etc/letsencrypt/ssl-dhparams.pem"