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); glMatrix.setMatrixArrayType(Array);
applyArrayPlugins(); 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 { try {
new Game(); new Game();
} catch (e) { } catch (e) {
console.error(e);
alert(e); alert(e);
} }

View file

@ -1,7 +1,11 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { ImmutableBoundingBox } from '../../physics/containers/immutable-bounding-box';
import { GameObject } from '../../objects/game-object';
export interface IDrawable { export interface IDrawable {
serializeToUniforms(uniforms: any): void; serializeToUniforms(uniforms: any): void;
distance(target: vec2): number; distance(target: vec2): number;
minimumDistance(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 { vec2, vec3 } from 'gl-matrix';
import { settings } from '../../settings'; import { settings } from '../../settings';
import { IDrawableDescriptor } from '../i-drawable-descriptor'; 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 { export class CircleLight implements ILight {
public static descriptor: IDrawableDescriptor = { public static descriptor: IDrawableDescriptor = {
@ -11,12 +13,15 @@ export class CircleLight implements ILight {
}; };
constructor( constructor(
public readonly owner: GameObject,
public center: vec2, public center: vec2,
public radius: number, public radius: number,
public color: vec3, public color: vec3,
public lightness: number public lightness: number
) {} ) {}
boundingBox: ImmutableBoundingBox;
public distance(target: vec2): number { public distance(target: vec2): number {
return 0; return 0;
} }

View file

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

View file

@ -1,8 +1,14 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { IPrimitive } from './i-primitive'; import { IPrimitive } from './i-primitive';
import { ImmutableBoundingBox } from '../../../physics/containers/immutable-bounding-box';
import { GameObject } from '../../../objects/game-object';
export class Circle implements IPrimitive { 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 { public serializeToUniforms(uniforms: any): void {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
@ -16,6 +22,16 @@ export class Circle implements IPrimitive {
return vec2.distance(this.center, target) - this.radius; 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 { public isInside(target: vec2): boolean {
return this.distance(target) < 0; 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 { 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 { IDrawableDescriptor } from '../i-drawable-descriptor';
import { IPrimitive } from './i-primitive';
import { settings } from '../../settings'; 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 { export class TunnelShape implements IPrimitive {
public static descriptor: IDrawableDescriptor = { public static descriptor: IDrawableDescriptor = {
@ -14,11 +16,11 @@ export class TunnelShape implements IPrimitive {
}; };
public readonly toFromDelta: vec2; public readonly toFromDelta: vec2;
private toFromDeltaLength: number;
private boundingCircle: Circle; private boundingCircle: Circle;
constructor( constructor(
public readonly owner: GameObject,
public readonly from: vec2, public readonly from: vec2,
public readonly to: vec2, public readonly to: vec2,
public readonly fromRadius: number, public readonly fromRadius: number,
@ -27,12 +29,13 @@ export class TunnelShape implements IPrimitive {
this.toFromDelta = vec2.subtract(vec2.create(), to, from); this.toFromDelta = vec2.subtract(vec2.create(), to, from);
this.boundingCircle = new Circle( this.boundingCircle = new Circle(
this.owner,
vec2.fromValues(from.x / 2 + to.x / 2, from.y / 2 + to.y / 2), vec2.fromValues(from.x / 2 + to.x / 2, from.y / 2 + to.y / 2),
Math.max(fromRadius, toRadius) + vec2.distance(from, to) Math.max(fromRadius, toRadius) + vec2.distance(from, to)
); );
} }
serializeToUniforms(uniforms: any): void { public serializeToUniforms(uniforms: any): void {
if (!uniforms.hasOwnProperty(TunnelShape.descriptor.uniformName)) { if (!uniforms.hasOwnProperty(TunnelShape.descriptor.uniformName)) {
uniforms[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 { public distance(target: vec2): number {
const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); const targetFromDelta = vec2.subtract(vec2.create(), target, this.from);
const h = clamp01( const h = clamp01(

View file

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

View file

@ -32,10 +32,10 @@ export abstract class Program implements IProgram {
this.setUniforms({ modelTransform: this.modelTransform, ...values }); 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.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.scale(this.modelTransform, this.modelTransform, size);
mat2d.multiply(this.modelTransform, this.modelTransform, this.ndcToUv); mat2d.multiply(this.modelTransform, this.modelTransform, this.ndcToUv);
} }

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { IPrimitive } from './drawables/primitives/i-primitive';
import { ILight } from './drawables/lights/i-light'; import { ILight } from './drawables/lights/i-light';
import { IPrimitive } from './drawables/primitives/i-primitive';
export interface IRenderer { export interface IRenderer {
startFrame(deltaTime: DOMHighResTimeStamp): void; 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 { vec2 } from 'gl-matrix';
import { Circle } from '../drawables/primitives/circle';
import { InfoText } from '../../objects/types/info-text'; 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 { 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 { export class RenderingPass {
private drawables: Array<IDrawable> = []; private drawables: Array<IDrawable> = [];
@ -30,7 +29,8 @@ export class RenderingPass {
public render( public render(
commonUniforms: any, commonUniforms: any,
viewCircle: Circle, viewBoxCenter: vec2,
viewBoxRadius: number,
inputTexture?: WebGLTexture inputTexture?: WebGLTexture
) { ) {
this.frame.bindAndClear(inputTexture); this.frame.bindAndClear(inputTexture);
@ -38,7 +38,7 @@ export class RenderingPass {
const tileUvSize = vec2.fromValues(q, q); const tileUvSize = vec2.fromValues(q, q);
const possiblyOnScreenDrawables = this.drawables.filter( const possiblyOnScreenDrawables = this.drawables.filter(
(p) => p.minimumDistance(viewCircle.center) < viewCircle.radius (p) => p.minimumDistance(viewBoxCenter) < viewBoxRadius
); );
const origin = vec2.transformMat2d( const origin = vec2.transformMat2d(

View file

@ -1,34 +1,29 @@
import { mat2d, vec2, vec3 } from 'gl-matrix'; 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 caveFragmentShader from '../shaders/cave-distance-fs.glsl';
import lightsFragmentShader from '../shaders/lights-shading-fs.glsl'; import lightsFragmentShader from '../shaders/lights-shading-fs.glsl';
import caveVertexShader from '../shaders/passthrough-distance-vs.glsl'; import caveVertexShader from '../shaders/passthrough-distance-vs.glsl';
import lightsVertexShader from '../shaders/passthrough-shading-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 { FpsAutoscaler } from './fps-autoscaler';
import { getWebGl2Context } from '../graphics-library/helper/get-webgl2-context';
import { RenderingPass } from './rendering-pass'; 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 { export class WebGl2Renderer implements IRenderer {
private gl: WebGL2RenderingContext; private gl: WebGL2RenderingContext;
private stopwatch?: WebGlStopwatch; private stopwatch?: WebGlStopwatch;
private viewBox: Rectangle = new Rectangle(); private viewBoxBottomLeft = vec2.create();
private viewCircle: Circle = new Circle(); private cameraPosition = vec2.create();
private viewBoxSize = vec2.create();
private cursorPosition = vec2.create(); private cursorPosition = vec2.create();
private distanceFieldFrameBuffer: IntermediateFrameBuffer; private distanceFieldFrameBuffer: IntermediateFrameBuffer;
@ -38,6 +33,15 @@ export class WebGl2Renderer implements IRenderer {
private autoscaler: FpsAutoscaler; 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) { constructor(private canvas: HTMLCanvasElement, private overlay: HTMLElement) {
this.gl = getWebGl2Context(canvas); this.gl = getWebGl2Context(canvas);
@ -85,64 +89,69 @@ export class WebGl2Renderer implements IRenderer {
} }
public finishFrame() { public finishFrame() {
const uniforms = this.calculateOwnUniforms(); this.calculateMatrices();
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));
const cursorPosition = this.screenUvToWorldCoordinate(this.cursorPosition); const cursorPosition = this.screenUvToWorldCoordinate(this.cursorPosition);
return { this.lightingPass.addDrawable(
distanceScreenToWorld, new PointLight(null, cursorPosition, 200, vec3.fromValues(1, 1, 0), 1)
worldToDistanceUV, );
cursorPosition,
ndcToUv, const viewBoxRadius = vec2.length(
uvToWorld, vec2.scale(vec2.create(), this.viewBoxSize, 0.5)
viewBoxSize: this.viewBox.size, );
};
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) { private getScreenToWorldTransform(screenSize: vec2) {
const transform = mat2d.fromTranslation( const transform = mat2d.fromTranslation(
mat2d.create(), mat2d.create(),
this.viewBox.topLeft this.viewBoxBottomLeft
); );
mat2d.scale( mat2d.scale(
transform, transform,
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)); mat2d.translate(transform, transform, vec2.fromValues(0.5, 0.5));
@ -160,12 +169,10 @@ export class WebGl2Renderer implements IRenderer {
} }
public setCameraPosition(position: vec2) { public setCameraPosition(position: vec2) {
this.viewBox.topLeft = position; this.cameraPosition = position;
const halfDiagonal = vec2.scale(vec2.create(), this.viewBox.size, 0.5); this.viewBoxBottomLeft = vec2.fromValues(
this.viewCircle.center = vec2.add( this.cameraPosition.x - this.viewBoxSize.x / 2,
vec2.create(), this.cameraPosition.y - this.viewBoxSize.y / 2
this.viewBox.topLeft,
halfDiagonal
); );
} }
@ -177,21 +184,10 @@ export class WebGl2Renderer implements IRenderer {
const canvasAspectRatio = const canvasAspectRatio =
this.canvas.clientWidth / this.canvas.clientHeight; this.canvas.clientWidth / this.canvas.clientHeight;
this.viewBox.size = vec2.fromValues( return (this.viewBoxSize = vec2.fromValues(
Math.sqrt(size * canvasAspectRatio), Math.sqrt(size * canvasAspectRatio),
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) { public drawInfoText(text: string) {

View file

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

View file

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

View file

@ -1,33 +1,16 @@
import { vec2 } from 'gl-matrix';
import { Command } from '../commands/command'; import { Command } from '../commands/command';
import { CommandReceiver } from '../commands/command-receiver'; import { CommandReceiver } from '../commands/command-receiver';
import { IdentityManager } from '../identity/identity-manager'; import { IdentityManager } from '../identity/identity-manager';
import { Objects } from './objects';
import { Physics } from '../physics/physics';
export abstract class GameObject implements CommandReceiver { export abstract class GameObject implements CommandReceiver {
public readonly id = IdentityManager.generateId(); 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: { private commandExecutors: {
[commandType: string]: (e: Command) => void; [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 { public reactsToCommand(commandType: string): boolean {
return this.commandExecutors.hasOwnProperty(commandType); return this.commandExecutors.hasOwnProperty(commandType);
} }
@ -39,4 +22,12 @@ export abstract class GameObject implements CommandReceiver {
this.commandExecutors[commandType](command); 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 { Id } from '../identity/identity';
import { GameObject } from './game-object'; import { GameObject } from './game-object';
export class ObjectContainer implements CommandReceiver { export class Objects implements CommandReceiver {
private objects: Map<Id, GameObject> = new Map(); private objects: Map<Id, GameObject> = new Map();
private objectsGroupedByAbilities: Map<string, Array<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 { Lamp } from './lamp';
import { ZoomCommand } from '../../input/commands/zoom'; import { ZoomCommand } from '../../input/commands/zoom';
import { MoveToCommand } from '../../physics/commands/move-to'; import { MoveToCommand } from '../../physics/commands/move-to';
import { BoundingBox } from '../../physics/containers/bounding-box';
import { Physics } from '../../physics/physics';
export class Camera extends GameObject { export class Camera extends GameObject {
private inViewArea = 1920 * 1080 * 5; private inViewArea = 1920 * 1080 * 5;
private cursorPosition = vec2.create(); private cursorPosition = vec2.create();
private boundingBox: BoundingBox;
constructor(private light: Lamp) { constructor(physics: Physics, private light: Lamp) {
super(); super();
this.boundingBox = new BoundingBox(null);
physics.addDynamicBoundingBox(this.boundingBox);
this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this)); this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
this.addCommandExecutor( this.addCommandExecutor(
@ -22,20 +28,27 @@ export class Camera extends GameObject {
this.addCommandExecutor(ZoomCommand, this.zoom.bind(this)); this.addCommandExecutor(ZoomCommand, this.zoom.bind(this));
} }
public get viewAreaSize(): vec2 {
return this.boundingBox.size;
}
private draw(c: BeforeRenderCommand) { 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); c.renderer.setCursorPosition(this.cursorPosition);
this._boundingBoxSize = c.renderer.setInViewArea(this.inViewArea); this.boundingBox.size = c.renderer.setInViewArea(this.inViewArea);
} }
private moveTo(c: MoveToCommand) { private moveTo(c: MoveToCommand) {
this._position = c.position; console.log('camera', c.position);
this.boundingBox.topLeft = c.position;
this.light.sendCommand( this.light.sendCommand(
new MoveToCommand( new MoveToCommand(
vec2.add( vec2.add(
vec2.create(), vec2.create(),
c.position, 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 { vec2 } from 'gl-matrix';
import { GameObject } from '../game-object'; import { Circle } from '../../drawing/drawables/primitives/circle';
import { Camera } from './camera';
import { StepCommand } from '../../physics/commands/step';
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 { MoveToCommand } from '../../physics/commands/move-to'; 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 { export class Character extends GameObject {
private keysDown: Set<string> = new Set(); 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(); super();
this.primitive = new Circle(this);
this.primitive.radius = 20;
this.addCommandExecutor(StepCommand, this.stepHandler.bind(this)); 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(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(SwipeCommand, (c) => this.addCommandExecutor(SwipeCommand, (c) =>
this.setPosition( this.checkAndSetPosition(
vec2.add( vec2.add(
vec2.create(), vec2.create(),
this.position, this.primitive.center,
vec2.multiply(vec2.create(), c.delta, this.camera.boundingBoxSize) 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) { private setPosition(value: vec2) {
this._position = value; console.log('character', value);
this.camera.sendCommand(new MoveToCommand(this.position));
this.primitive.center = value;
this.camera.sendCommand(new MoveToCommand(this.primitive.center));
} }
public stepHandler(c: StepCommand) { public stepHandler(c: StepCommand) {
@ -44,16 +87,11 @@ export class Character extends GameObject {
const movementVector = vec2.fromValues(right - left, up - down); const movementVector = vec2.fromValues(right - left, up - down);
if (movementVector.length > 0) { if (movementVector.length > 0) {
this.setPosition( vec2.normalize(movementVector, movementVector);
vec2.add( vec2.scale(movementVector, movementVector, Character.speed * deltaTime);
vec2.create(),
this.position, this.checkAndSetPosition(
vec2.scale( vec2.add(vec2.create(), this.primitive.center, movementVector)
vec2.create(),
vec2.normalize(movementVector, movementVector),
Character.speed * deltaTime
)
)
); );
} }
} }

View file

@ -10,7 +10,7 @@ export class Lamp extends GameObject {
constructor(center: vec2, radius: number, color: vec3, lightness: number) { constructor(center: vec2, radius: number, color: vec3, lightness: number) {
super(); 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(RenderCommand, this.draw.bind(this));
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));

View file

@ -1,16 +1,25 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { TunnelShape } from '../../drawing/drawables/primitives/tunnel-shape';
import { GameObject } from '../game-object'; import { GameObject } from '../game-object';
import { RenderCommand } from '../../drawing/commands/render'; import { RenderCommand } from '../../drawing/commands/render';
import { Physics } from '../../physics/physics';
import { TunnelShape } from '../../drawing/drawables/primitives/tunnel-shape';
export interface Line {} export interface Line {}
export class Tunnel extends GameObject { export class Tunnel extends GameObject {
private primitive: TunnelShape; 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(); 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)); this.addCommandExecutor(RenderCommand, this.draw.bind(this));
} }

View file

@ -1,10 +1,15 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
import { ObjectContainer } from '../object-container'; import { Objects } from '../objects';
import { Camera } from '../types/camera'; import { Camera } from '../types/camera';
import { Character } from '../types/character'; import { Character } from '../types/character';
import { Lamp } from '../types/lamp'; 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( const light = new Lamp(
vec2.create(), vec2.create(),
40, 40,
@ -12,8 +17,11 @@ export const createCharacter = (objects: ObjectContainer) => {
2 2
); );
const camera = new Camera(light); const camera = new Camera(physics, light);
const character = new Character(physics, camera);
objects.addObject(light); objects.addObject(light);
objects.addObject(camera); 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 { vec2, vec3 } from 'gl-matrix';
import { ObjectContainer } from '../object-container'; import { Objects } from '../objects';
import { Lamp } from '../types/lamp';
import { Tunnel } from '../types/tunnel'; 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 previousRadius = 350;
let previousEnd = vec2.create(); 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 deltaHeight = (Math.random() - 0.5) * 2000;
const height = previousEnd.y + deltaHeight; const height = previousEnd.y + deltaHeight;
const currentEnd = vec2.fromValues(i, height); const currentEnd = vec2.fromValues(i, height);
const currentToRadius = Math.random() * 300 + 150; const currentToRadius = Math.random() * 300 + 150;
objects.addObject( const tunnel = new Tunnel(
new Tunnel(previousEnd, currentEnd, previousRadius, currentToRadius) physics,
previousEnd,
currentEnd,
previousRadius,
currentToRadius
); );
if (!first) {
first = tunnel;
}
objects.addObject(tunnel);
/*if (deltaHeight > 0 && Math.random() > 0.8) { /*if (deltaHeight > 0 && Math.random() > 0.8) {
objects.addObject( objects.addObject(
new Lamp( new Lamp(
@ -35,4 +48,6 @@ export const createDungeon = (objects: ObjectContainer) => {
previousEnd = currentEnd; previousEnd = currentEnd;
previousRadius = currentToRadius; 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 \ --dns-digitalocean-propagation-seconds 120 \
-m "schmelczerandras@gmail.com" \ -m "schmelczerandras@gmail.com" \
-d "decla.red" -d "*.decla.red" \ -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-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" curl -s https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem > "/etc/letsencrypt/ssl-dhparams.pem"