This commit is contained in:
schmelczerandras 2020-08-04 22:07:39 +02:00
commit 345e183e34
30 changed files with 614 additions and 173 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

@ -0,0 +1,49 @@
import { Command } from '../commands/command';
import { CommandReceiver } from '../commands/command-receiver';
import { Id } from '../identity/identity';
import { GameObject } from './game-object';
export class Objects implements CommandReceiver {
private objects: Map<Id, GameObject> = new Map();
private objectsGroupedByAbilities: Map<string, Array<GameObject>> = new Map();
public addObject(o: GameObject) {
this.objects.set(o.id, o);
for (let command of this.objectsGroupedByAbilities.keys()) {
if (o.reactsToCommand(command)) {
this.objectsGroupedByAbilities.get(command).push(o);
}
}
}
public removeObject(o: GameObject) {
this.objects.delete(o.id);
}
public sendCommandToSingleObject(id: Id, e: Command) {
this.objects.get(id)?.sendCommand(e);
}
public sendCommand(e: Command) {
if (!this.objectsGroupedByAbilities.has(e.type)) {
this.createGroupForCommand(e.type);
}
this.objectsGroupedByAbilities
.get(e.type)
.forEach((o, _) => o.sendCommand(e));
}
private createGroupForCommand(commandType: string) {
const objectsReactingToCommand = [];
this.objects.forEach((o, _) => {
if (o.reactsToCommand(commandType)) {
objectsReactingToCommand.push(o);
}
});
this.objectsGroupedByAbilities.set(commandType, objectsReactingToCommand);
}
}

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

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