Refactor rendering

This commit is contained in:
schmelczerandras 2020-08-18 15:49:15 +02:00
parent edca93fcfe
commit 76282a4cf7
25 changed files with 239 additions and 213 deletions

View file

@ -1,5 +1,6 @@
{ {
"trailingComma": "es5", "trailingComma": "es5",
"printWidth": 120,
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true, "singleQuote": true,
"endOfLine": "lf" "endOfLine": "lf"

View file

@ -2,6 +2,7 @@ import { IDrawable } from './i-drawable';
import { IDrawableDescriptor } from './i-drawable-descriptor'; import { IDrawableDescriptor } from './i-drawable-descriptor';
import { settings } from '../settings'; import { settings } from '../settings';
import { Blob } from '../../shapes/types/blob'; import { Blob } from '../../shapes/types/blob';
import { vec2, mat2d } from 'gl-matrix';
export class DrawableBlob extends Blob implements IDrawable { export class DrawableBlob extends Blob implements IDrawable {
public static descriptor: IDrawableDescriptor = { public static descriptor: IDrawableDescriptor = {
@ -10,17 +11,37 @@ export class DrawableBlob extends Blob implements IDrawable {
shaderCombinationSteps: settings.shaderCombinations.blobSteps, shaderCombinationSteps: settings.shaderCombinations.blobSteps,
}; };
public serializeToUniforms(uniforms: any): void { public serializeToUniforms(
uniforms: any,
scale: number,
transform: mat2d
): void {
const uniformName = DrawableBlob.descriptor.uniformName; const uniformName = DrawableBlob.descriptor.uniformName;
if (!uniforms.hasOwnProperty(uniformName)) { if (!uniforms.hasOwnProperty(uniformName)) {
uniforms[uniformName] = []; uniforms[uniformName] = [];
} }
uniforms[uniformName].push({ uniforms[uniformName].push({
headCenter: this.head.center, headCenter: vec2.transformMat2d(
torsoCenter: this.torso.center, vec2.create(),
leftFootCenter: this.leftFoot.center, this.head.center,
rightFootCenter: this.rightFoot.center, transform
),
torsoCenter: vec2.transformMat2d(
vec2.create(),
this.torso.center,
transform
),
leftFootCenter: vec2.transformMat2d(
vec2.create(),
this.leftFoot.center,
transform
),
rightFootCenter: vec2.transformMat2d(
vec2.create(),
this.rightFoot.center,
transform
),
}); });
} }
} }

View file

@ -2,6 +2,7 @@ import { IDrawable } from './i-drawable';
import { TunnelShape } from '../../shapes/types/tunnel-shape'; import { TunnelShape } from '../../shapes/types/tunnel-shape';
import { IDrawableDescriptor } from './i-drawable-descriptor'; import { IDrawableDescriptor } from './i-drawable-descriptor';
import { settings } from '../settings'; import { settings } from '../settings';
import { mat2d, vec2 } from 'gl-matrix';
export class DrawableTunnel extends TunnelShape implements IDrawable { export class DrawableTunnel extends TunnelShape implements IDrawable {
public static descriptor: IDrawableDescriptor = { public static descriptor: IDrawableDescriptor = {
@ -10,17 +11,25 @@ export class DrawableTunnel extends TunnelShape implements IDrawable {
shaderCombinationSteps: settings.shaderCombinations.lineSteps, shaderCombinationSteps: settings.shaderCombinations.lineSteps,
}; };
public serializeToUniforms(uniforms: any): void { public serializeToUniforms(
uniforms: any,
scale: number,
transform: mat2d
): void {
const uniformName = DrawableTunnel.descriptor.uniformName; const uniformName = DrawableTunnel.descriptor.uniformName;
if (!uniforms.hasOwnProperty(uniformName)) { if (!uniforms.hasOwnProperty(uniformName)) {
uniforms[uniformName] = []; uniforms[uniformName] = [];
} }
uniforms[uniformName].push({ uniforms[uniformName].push({
from: this.from, from: vec2.transformMat2d(vec2.create(), this.from, transform),
toFromDelta: this.toFromDelta, toFromDelta: vec2.transformMat2d(
fromRadius: this.fromRadius, vec2.create(),
toRadius: this.toRadius, this.toFromDelta,
transform
),
fromRadius: this.fromRadius * scale,
toRadius: this.toRadius * scale,
}); });
} }
} }

View file

@ -1,6 +1,6 @@
import { vec2 } from 'gl-matrix'; import { vec2, mat2d } from 'gl-matrix';
export interface IDrawable { export interface IDrawable {
distance(target: vec2): number; distance(target: vec2): number;
serializeToUniforms(uniforms: any): void; serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void;
} }

View file

@ -1,4 +1,4 @@
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3, mat2d } from 'gl-matrix';
import { GameObject } from '../../../objects/game-object'; import { GameObject } from '../../../objects/game-object';
import { settings } from '../../settings'; import { settings } from '../../settings';
import { IDrawableDescriptor } from '../i-drawable-descriptor'; import { IDrawableDescriptor } from '../i-drawable-descriptor';
@ -12,7 +12,6 @@ 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,
@ -23,7 +22,11 @@ export class CircleLight implements ILight {
return 0; return 0;
} }
public serializeToUniforms(uniforms: any): void { public serializeToUniforms(
uniforms: any,
scale: number,
transform: mat2d
): void {
const uniformName = CircleLight.descriptor.uniformName; const uniformName = CircleLight.descriptor.uniformName;
if (!uniforms.hasOwnProperty(uniformName)) { if (!uniforms.hasOwnProperty(uniformName)) {
@ -31,8 +34,8 @@ export class CircleLight implements ILight {
} }
uniforms[uniformName].push({ uniforms[uniformName].push({
center: this.center, center: vec2.transformMat2d(vec2.create(), this.center, transform),
radius: this.radius, radius: this.radius * scale,
value: this.value, value: this.value,
}); });
} }

View file

@ -1,5 +1,5 @@
import { ILight } from './i-light'; import { ILight } from './i-light';
import { vec2, vec3 } from 'gl-matrix'; import { vec2, vec3, mat2d } from 'gl-matrix';
import { IDrawableDescriptor } from '../i-drawable-descriptor'; import { IDrawableDescriptor } from '../i-drawable-descriptor';
import { settings } from '../../settings'; import { settings } from '../../settings';
import { GameObject } from '../../../objects/game-object'; import { GameObject } from '../../../objects/game-object';
@ -12,7 +12,6 @@ export class PointLight implements ILight {
}; };
public 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,
@ -23,7 +22,11 @@ export class PointLight implements ILight {
return vec2.distance(this.center, target) - this.radius; return vec2.distance(this.center, target) - this.radius;
} }
public serializeToUniforms(uniforms: any): void { public serializeToUniforms(
uniforms: any,
scale: number,
transform: mat2d
): void {
const listName = PointLight.descriptor.uniformName; const listName = PointLight.descriptor.uniformName;
if (!uniforms.hasOwnProperty(listName)) { if (!uniforms.hasOwnProperty(listName)) {
@ -31,8 +34,8 @@ export class PointLight implements ILight {
} }
uniforms[listName].push({ uniforms[listName].push({
center: this.center, center: vec2.transformMat2d(vec2.create(), this.center, transform),
radius: this.radius, radius: this.radius * scale,
value: this.value, value: this.value,
}); });
} }

View file

@ -1,6 +1,7 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { ILight } from './drawables/lights/i-light'; import { ILight } from './drawables/lights/i-light';
import { IDrawable } from './drawables/i-drawable'; import { IDrawable } from './drawables/i-drawable';
import { BoundingBoxBase } from '../shapes/bounding-box-base';
export interface IRenderer { export interface IRenderer {
startFrame(deltaTime: DOMHighResTimeStamp): void; startFrame(deltaTime: DOMHighResTimeStamp): void;
@ -10,7 +11,7 @@ export interface IRenderer {
drawLight(light: ILight): void; drawLight(light: ILight): void;
drawInfoText(text: string): void; drawInfoText(text: string): void;
setCameraPosition(position: vec2): void; readonly canvasSize: vec2;
setViewArea(viewArea: BoundingBoxBase): void;
setCursorPosition(position: vec2): void; setCursorPosition(position: vec2): void;
setInViewArea(size: number): vec2;
} }

View file

@ -1,4 +1,4 @@
import { vec2 } from 'gl-matrix'; import { vec2, mat2d } from 'gl-matrix';
import { InfoText } from '../../objects/types/info-text'; import { InfoText } from '../../objects/types/info-text';
import { IDrawable } from '../drawables/i-drawable'; import { IDrawable } from '../drawables/i-drawable';
import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor'; import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor';
@ -27,20 +27,11 @@ export class RenderingPass {
this.drawables.push(drawable); this.drawables.push(drawable);
} }
public render( public render(commonUniforms: any, inputTexture?: WebGLTexture) {
commonUniforms: any,
viewBoxCenter: vec2,
viewBoxRadius: number,
inputTexture?: WebGLTexture
) {
this.frame.bindAndClear(inputTexture); this.frame.bindAndClear(inputTexture);
const q = 1 / settings.tileMultiplier; const q = 1 / settings.tileMultiplier;
const tileUvSize = vec2.fromValues(q, q); const tileUvSize = vec2.fromValues(q, q);
const possiblyOnScreenDrawables = this.drawables.filter(
(p) => p.distance(viewBoxCenter) < viewBoxRadius
);
const origin = vec2.transformMat2d( const origin = vec2.transformMat2d(
vec2.create(), vec2.create(),
vec2.fromValues(0, 0), vec2.fromValues(0, 0),
@ -59,6 +50,9 @@ export class RenderingPass {
let sumLineCount = 0; let sumLineCount = 0;
const scale = 1;
const transform = mat2d.create();
for (let x = 0; x < 1; x += q) { for (let x = 0; x < 1; x += q) {
for (let y = 0; y < 1; y += q) { for (let y = 0; y < 1; y += q) {
const uniforms = { ...commonUniforms }; const uniforms = { ...commonUniforms };
@ -73,24 +67,24 @@ export class RenderingPass {
uniforms.uvToWorld uniforms.uvToWorld
); );
const primitivesNearTile = possiblyOnScreenDrawables.filter( const primitivesNearTile = this.drawables.filter(
(p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR (p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR
); );
sumLineCount += primitivesNearTile.length; sumLineCount += primitivesNearTile.length;
primitivesNearTile.forEach((p) => p.serializeToUniforms(uniforms)); primitivesNearTile.forEach((p) =>
p.serializeToUniforms(uniforms, scale, transform)
);
this.program.bindAndSetUniforms(uniforms); this.program.bindAndSetUniforms(uniforms);
this.program.draw(); this.program.draw();
} }
} }
this.drawables = [];
InfoText.modifyRecord( InfoText.modifyRecord(
'nearby ' + this.drawableDescriptors[0].countMacroName, 'nearby ' + this.drawableDescriptors[0].countMacroName,
possiblyOnScreenDrawables.length.toFixed(2) this.drawables.length.toFixed(2)
); );
InfoText.modifyRecord( InfoText.modifyRecord(
@ -101,5 +95,7 @@ export class RenderingPass {
settings.tileMultiplier settings.tileMultiplier
).toFixed(2) ).toFixed(2)
); );
this.drawables = [];
} }
} }

View file

@ -18,13 +18,13 @@ import { IDrawable } from '../drawables/i-drawable';
import { DrawableTunnel } from '../drawables/drawable-tunnel'; import { DrawableTunnel } from '../drawables/drawable-tunnel';
import { enableExtension } from '../graphics-library/helper/enable-extension'; import { enableExtension } from '../graphics-library/helper/enable-extension';
import { DrawableBlob } from '../drawables/drawable-blob'; import { DrawableBlob } from '../drawables/drawable-blob';
import { BoundingBoxBase } from '../../shapes/bounding-box-base';
export class WebGl2Renderer implements IRenderer { export class WebGl2Renderer implements IRenderer {
private gl: WebGL2RenderingContext; private gl: WebGL2RenderingContext;
private stopwatch?: WebGlStopwatch; private stopwatch?: WebGlStopwatch;
private viewBoxBottomLeft = vec2.create(); private viewBoxBottomLeft = vec2.create();
private cameraPosition = vec2.create();
private viewBoxSize = vec2.create(); private viewBoxSize = vec2.create();
private cursorPosition = vec2.create(); private cursorPosition = vec2.create();
@ -92,16 +92,10 @@ export class WebGl2Renderer implements IRenderer {
public finishFrame() { public finishFrame() {
this.calculateMatrices(); this.calculateMatrices();
const viewBoxRadius = vec2.length( this.distancePass.render(this.uniforms);
vec2.scale(vec2.create(), this.viewBoxSize, 0.5)
);
this.distancePass.render(this.uniforms, this.cameraPosition, viewBoxRadius);
this.lightingPass.render( this.lightingPass.render(
this.uniforms, this.uniforms,
this.cameraPosition,
viewBoxRadius,
this.distanceFieldFrameBuffer.colorTexture this.distanceFieldFrameBuffer.colorTexture
); );
@ -164,11 +158,16 @@ export class WebGl2Renderer implements IRenderer {
); );
} }
public setCameraPosition(position: vec2) { public get canvasSize(): vec2 {
this.cameraPosition = position; return vec2.fromValues(this.canvas.clientWidth, this.canvas.clientHeight);
this.viewBoxBottomLeft = vec2.fromValues( }
this.cameraPosition.x - this.viewBoxSize.x / 2,
this.cameraPosition.y - this.viewBoxSize.y / 2 public setViewArea(viewArea: BoundingBoxBase) {
this.viewBoxSize = viewArea.size;
this.viewBoxBottomLeft = vec2.add(
vec2.create(),
viewArea.topLeft,
vec2.fromValues(0, -viewArea.size.y)
); );
} }
@ -176,16 +175,6 @@ export class WebGl2Renderer implements IRenderer {
this.cursorPosition = position; this.cursorPosition = position;
} }
public setInViewArea(size: number): vec2 {
const canvasAspectRatio =
this.canvas.clientWidth / this.canvas.clientHeight;
return (this.viewBoxSize = vec2.fromValues(
Math.sqrt(size * canvasAspectRatio),
Math.sqrt(size / canvasAspectRatio)
));
}
public drawInfoText(text: string) { public drawInfoText(text: string) {
if (this.overlay.innerText != text) { if (this.overlay.innerText != text) {
this.overlay.innerText = text; this.overlay.innerText = text;

View file

@ -7,7 +7,7 @@ export const settings = {
scaleTargets: [ scaleTargets: [
[0.2, 0.1], [0.2, 0.1],
[0.6, 0.1], [0.6, 0.1],
[1, 0.3], [1, 1],
/*[1.25, 0.75], /*[1.25, 0.75],
[1.5, 1], [1.5, 1],
[1.75, 1.25], [1.75, 1.25],
@ -20,12 +20,12 @@ export const settings = {
multiplicativeDecrease: 1.15, multiplicativeDecrease: 1.15,
}, },
}, },
tileMultiplier: 5, tileMultiplier: 8,
shaderMacros: { shaderMacros: {
edgeSmoothing: 10, edgeSmoothing: 10,
headRadius: 5, headRadius: 5,
torsoRadius: 10, torsoRadius: 8,
footRadius: 4, footRadius: 2,
}, },
shaderCombinations: { shaderCombinations: {
lineSteps: [0, 1, 2, 4, 8, 16, 128], lineSteps: [0, 1, 2, 4, 8, 16, 128],

View file

@ -69,8 +69,6 @@ uniform float maxMinDistance;
} }
#endif #endif
in vec2 worldCoordinates; in vec2 worldCoordinates;
out vec2 fragmentColor; out vec2 fragmentColor;

View file

@ -61,40 +61,41 @@ void main() {
#if CIRCLE_LIGHT_COUNT > 0 #if CIRCLE_LIGHT_COUNT > 0
for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) { for (int i = 0; i < CIRCLE_LIGHT_COUNT; i++) {
float lightCenterDistance = distance(circleLights[i].center, worldCoordinates); float lightCenterDistance = distance(
circleLights[i].center,
worldCoordinates
);
float lightDistance = lightCenterDistance - circleLights[i].radius;
/*if (lightDistance < 0.0) {
lighting = vec3(1.0, 1.0, 0.0);
}*/
vec3 lightColorAtPosition = circleLights[i].value / pow( vec3 lightColorAtPosition = circleLights[i].value / pow(
lightCenterDistance / LIGHT_DROP + 1.0, 2.0 lightDistance / LIGHT_DROP + 1.0, 2.0
); );
float q = INFINITY; float q = INFINITY;
float rayLength = startingDistance; float rayLength = startingDistance;
float exponentialDecayDistance = rayLength;
vec2 direction = normalize(circleLightDirections[i]) / viewBoxSize; vec2 direction = normalize(circleLightDirections[i]) / viewBoxSize;
for (int j = 0; j < 48; j++) { for (int j = 0; j < 48; j++) {
if (rayLength > lightCenterDistance - circleLights[i].radius) { if (rayLength >= lightDistance) {
rayLength = lightCenterDistance - circleLights[i].radius;
float minDistance = getDistance(uvCoordinates + direction * rayLength);
exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0;
q = min(q, minDistance / rayLength);
lighting += lightColorAtPosition * clamp( lighting += lightColorAtPosition * clamp(
q / circleLights[i].radius * (lightCenterDistance + 1.0), 0.0, 1.0 q / circleLights[i].radius * lightCenterDistance, 0.0, 1.0
); );
break; break;
} }
float minDistance = getDistance(uvCoordinates + direction * rayLength); float minDistance = getDistance(uvCoordinates + direction * rayLength);
exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0; q = min(q, minDistance / rayLength);
q = min(q, exponentialDecayDistance / rayLength);
rayLength += minDistance; rayLength += minDistance;
} }
} }
#endif #endif
#if POINT_LIGHT_COUNT > 0 #if POINT_LIGHT_COUNT > 0
for (int i = 0; i < POINT_LIGHT_COUNT; i++) { /*for (int i = 0; i < POINT_LIGHT_COUNT; i++) {
float lightDistance = distance(pointLights[i].center, worldCoordinates); float lightDistance = distance(pointLights[i].center, worldCoordinates);
vec3 lightColorAtPosition = mix( vec3 lightColorAtPosition = mix(
@ -120,8 +121,11 @@ void main() {
q = min(q, exponentialDecayDistance); q = min(q, exponentialDecayDistance);
rayLength += minDistance; rayLength += minDistance;
} }
} }*/
#endif #endif
fragmentColor = vec4(colorAtPosition * lighting * step(0.0, startingDistance), 1.0); fragmentColor = vec4(
colorAtPosition * lighting * step(0.0, startingDistance),
1.0
);
} }

View file

@ -1,49 +0,0 @@
#version 300 es
precision mediump float;
vec3 rainbow(float level) {
float r = float(level <= 2.0) + float(level > 4.0) * 0.5;
float g = max(1.0 - abs(level - 2.0) * 0.5, 0.0);
float b = (1.0 - (level - 4.0) * 0.5) * float(level >= 4.0);
return vec3(r, g, b);
}
vec4 smoothRainbow(float x) {
float level1 = floor(x*6.0);
float level2 = min(6.0, floor(x*6.0) + 1.0);
vec3 a = rainbow(level1);
vec3 b = rainbow(level2);
return vec4(mix(a, b, fract(x*6.0)), 1.0);
}
uniform sampler2D distanceTexture;
uniform mat3 worldToDistanceUV;
uniform mat3 lightingScreenToWorld;
out vec4 fragmentColor;
uniform vec2 cursorPosition;
float getDistance(in vec2 targetUV, out vec3 color) {
vec4 values = texture(distanceTexture, targetUV);
color = values.rgb;
return values.a;
}
in vec2 worldCoordinates;
void main() {
vec2 targetUV = (vec3(worldCoordinates.xy, 1.0) * worldToDistanceUV).xy;
vec4 previous = texture(distanceTexture, targetUV);
//fragmentColor = smoothRainbow(previous.a);
fragmentColor = previous.a > 0.5 ? vec4(1.0, 1.0, 1.0, 1.0) : vec4(0.0, 0.0, 0.0, 1.0);
if (distance(worldCoordinates, cursorPosition) < 10.0) {
fragmentColor = vec4(1.0, 1.0, 0.0, 1.0);
}
}

View file

@ -8,22 +8,33 @@ import { MouseListener } from './input/mouse-listener';
import { TouchListener } from './input/touch-listener'; import { TouchListener } from './input/touch-listener';
import { Objects } from './objects/objects'; 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 { 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 { Physics } from './physics/physics';
import { TeleportToCommand } from './physics/commands/teleport-to'; import { TeleportToCommand } from './physics/commands/teleport-to';
import { IRenderer } from './drawing/i-renderer'; import { IRenderer } from './drawing/i-renderer';
import { Random } from './helper/random'; import { Random } from './helper/random';
import { Camera } from './objects/types/camera';
import { Character } from './objects/types/character';
import { IGame } from './i-game';
import { GameObject } from './objects/game-object';
import { vec2 } from 'gl-matrix';
import { BoundingBoxBase } from './shapes/bounding-box-base';
import { MoveToCommand } from './physics/commands/move-to';
import { BoundingBox } from './shapes/bounding-box';
export class Game implements IGame {
public readonly objects = new Objects();
public readonly physics = new Physics();
public readonly camera = new Camera();
export class Game {
private previousTime?: DOMHighResTimeStamp = null; private previousTime?: DOMHighResTimeStamp = null;
private objects = new Objects();
private physics = new Physics();
private renderer: IRenderer;
private previousFpsValues: Array<number> = []; private previousFpsValues: Array<number> = [];
private infoText = new InfoText();
private character: Character;
private renderer: IRenderer;
constructor() { constructor() {
const canvas: HTMLCanvasElement = document.querySelector('canvas#main'); const canvas: HTMLCanvasElement = document.querySelector('canvas#main');
const overlay: HTMLElement = document.querySelector('#overlay'); const overlay: HTMLElement = document.querySelector('#overlay');
@ -52,13 +63,29 @@ export class Game {
requestAnimationFrame(this.gameLoop.bind(this)); requestAnimationFrame(this.gameLoop.bind(this));
} }
public addObject(o: GameObject) {
this.objects.addObject(o);
}
public get viewArea(): BoundingBoxBase {
return this.camera.viewArea;
}
public findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase> {
return this.physics.findIntersecting(box);
}
private initializeScene() { private initializeScene() {
this.objects.addObject(new InfoText()); this.objects.addObject(this.infoText);
const start = createDungeon(this.objects, this.physics); const start = createDungeon(this.objects, this.physics);
createDungeon(this.objects, this.physics); createDungeon(this.objects, this.physics);
const character = createCharacter(this.objects, this.physics);
console.log('start', start.from); this.character = new Character(this);
character.sendCommand(new TeleportToCommand(start.from)); //this.physics.addDynamicBoundingBox(this.character.boundingBox);
this.addObject(this.character);
this.addObject(this.camera);
this.character.sendCommand(new TeleportToCommand(start.from));
} }
private handleVisibilityChange() { private handleVisibilityChange() {
@ -78,10 +105,23 @@ export class Game {
this.calculateFps(deltaTime); this.calculateFps(deltaTime);
this.objects.sendCommand(new StepCommand(deltaTime)); this.objects.sendCommand(new StepCommand(deltaTime));
this.camera.sendCommand(new MoveToCommand(this.character.position));
this.renderer.startFrame(deltaTime); this.renderer.startFrame(deltaTime);
this.objects.sendCommand(new BeforeRenderCommand(this.renderer));
this.objects.sendCommand(new RenderCommand(this.renderer)); this.camera.sendCommand(new BeforeRenderCommand(this.renderer));
const shouldBeDrawn = this.physics
.findIntersecting(this.camera.viewArea)
.map((b) => b.shape?.gameObject);
for (let object of shouldBeDrawn) {
object?.sendCommand(new BeforeRenderCommand(this.renderer));
object?.sendCommand(new RenderCommand(this.renderer));
}
this.character.sendCommand(new RenderCommand(this.renderer));
this.infoText.sendCommand(new RenderCommand(this.renderer));
this.renderer.finishFrame(); this.renderer.finishFrame();
window.requestAnimationFrame(this.gameLoop.bind(this)); window.requestAnimationFrame(this.gameLoop.bind(this));

View file

@ -1,11 +1,8 @@
import { InfoText } from '../objects/types/info-text'; import { InfoText } from '../objects/types/info-text';
import { last } from './last';
export function timeIt(interval = 60) { export function timeIt(interval = 60) {
return function ( return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
let i = 0; let i = 0;
let previousTimes: Array<DOMHighResTimeStamp> = []; let previousTimes: Array<DOMHighResTimeStamp> = [];
@ -19,10 +16,9 @@ export function timeIt(interval = 60) {
previousTimes.push(end - start); previousTimes.push(end - start);
if (i++ % interval == 0) { if (i++ % interval == 0) {
previousTimes.sort(); previousTimes = previousTimes.sort();
const text = `Max: ${previousTimes[previousTimes.length - 1].toFixed(
2 const text = `Max: ${last(previousTimes).toFixed(2)} ms\n\tMedian: ${previousTimes[
)} ms\n\tMedian: ${previousTimes[
Math.floor(previousTimes.length / 2) Math.floor(previousTimes.length / 2)
].toFixed(2)} ms`; ].toFixed(2)} ms`;

View file

@ -0,0 +1,9 @@
import { GameObject } from './objects/game-object';
import { vec2 } from 'gl-matrix';
import { BoundingBoxBase } from './shapes/bounding-box-base';
export interface IGame {
addObject(o: GameObject);
readonly viewArea: BoundingBoxBase;
findIntersecting(box: BoundingBoxBase): Array<BoundingBoxBase>;
}

View file

@ -9,40 +9,46 @@ import { GameObject } from '../game-object';
import { Lamp } from './lamp'; import { Lamp } from './lamp';
export class Camera extends GameObject { export class Camera extends GameObject {
private inViewArea = 1920 * 1080 * 5; private inViewAreaSize = 1920 * 1080 * 5;
private cursorPosition = vec2.create(); private cursorPosition = vec2.create();
private boundingBox: BoundingBox; private _viewArea: BoundingBox;
constructor() { constructor() {
super(); super();
this.boundingBox = new BoundingBox(null); this._viewArea = new BoundingBox(null);
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(CursorMoveCommand, this.setCursorPosition.bind(this));
CursorMoveCommand,
this.setCursorPosition.bind(this)
);
this.addCommandExecutor(ZoomCommand, this.zoom.bind(this)); this.addCommandExecutor(ZoomCommand, this.zoom.bind(this));
} }
public get viewAreaSize(): vec2 { public get viewArea(): BoundingBox {
return this.boundingBox.size; return this._viewArea;
} }
private draw(c: BeforeRenderCommand) { private draw(c: BeforeRenderCommand) {
c.renderer.setCameraPosition(this.boundingBox.topLeft); const canvasAspectRatio = c.renderer.canvasSize.x / c.renderer.canvasSize.y;
this.viewArea.size = vec2.fromValues(
Math.sqrt(this.inViewAreaSize * canvasAspectRatio),
Math.sqrt(this.inViewAreaSize / canvasAspectRatio)
);
c.renderer.setViewArea(this._viewArea);
c.renderer.setCursorPosition(this.cursorPosition); c.renderer.setCursorPosition(this.cursorPosition);
this.boundingBox.size = c.renderer.setInViewArea(this.inViewArea);
} }
private moveTo(c: MoveToCommand) { private moveTo(c: MoveToCommand) {
this.boundingBox.topLeft = c.position; this._viewArea.topLeft = vec2.fromValues(
c.position.x - this._viewArea.size.x / 2,
c.position.y + this._viewArea.size.y / 2
);
} }
private zoom(c: ZoomCommand) { private zoom(c: ZoomCommand) {
this.inViewArea *= c.factor; this.inViewAreaSize *= c.factor;
} }
private setCursorPosition(c: CursorMoveCommand) { private setCursorPosition(c: CursorMoveCommand) {

View file

@ -1,4 +1,4 @@
import { vec2 } from 'gl-matrix'; import { vec2, vec3 } from 'gl-matrix';
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';
@ -13,21 +13,25 @@ import { Blob } from '../../shapes/types/blob';
import { RenderCommand } from '../../drawing/commands/render'; import { RenderCommand } from '../../drawing/commands/render';
import { DrawableBlob } from '../../drawing/drawables/drawable-blob'; import { DrawableBlob } from '../../drawing/drawables/drawable-blob';
import { Lamp } from './lamp'; import { Lamp } from './lamp';
import { Game } from '../../game';
import { IGame } from '../../i-game';
export class Character extends GameObject { export class Character extends GameObject {
private keysDown: Set<string> = new Set(); private keysDown: Set<string> = new Set();
private light = new Lamp(
vec2.create(),
40,
vec3.fromValues(0.67, 0.0, 0.33),
2
);
private shape: DrawableBlob; private shape = new DrawableBlob(vec2.create());
private static speed = 1.5; private static speed = 1.5;
constructor( constructor(private game: IGame) {
private physics: Physics,
private camera: Camera,
private light: Lamp
) {
super(); super();
this.shape = new DrawableBlob(vec2.create()); game.addObject(this.light);
this.addCommandExecutor(StepCommand, this.stepHandler.bind(this)); this.addCommandExecutor(StepCommand, this.stepHandler.bind(this));
this.addCommandExecutor(RenderCommand, this.draw.bind(this)); this.addCommandExecutor(RenderCommand, this.draw.bind(this));
@ -38,13 +42,18 @@ export class Character extends GameObject {
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.tryMoving( this.tryMoving(
vec2.multiply(vec2.create(), c.delta, this.camera.viewAreaSize) vec2.multiply(vec2.create(), c.delta, this.game.viewArea.size)
); );
}); });
} }
public get position(): vec2 {
return this.shape.center;
}
private draw(c: RenderCommand) { private draw(c: RenderCommand) {
c.renderer.drawShape(this.shape); c.renderer.drawShape(this.shape);
this.light.sendCommand(c);
} }
private tryMoving(delta: vec2, isFirstIteration = true) { private tryMoving(delta: vec2, isFirstIteration = true) {
@ -87,6 +96,9 @@ export class Character extends GameObject {
) )
.sort((e) => Math.abs(e.distance)); .sort((e) => Math.abs(e.distance));
if (intersecting.length < 1) {
return;
}
const normal = intersecting[0].shape.normal(this.shape.center); const normal = intersecting[0].shape.normal(this.shape.center);
const maxDistance = intersecting.reduce((p, c) => const maxDistance = intersecting.reduce((p, c) =>
@ -106,7 +118,7 @@ export class Character extends GameObject {
private getNearShapesTo( private getNearShapesTo(
shape: Blob shape: Blob
): Array<{ shape: IShape; distance: number }> { ): Array<{ shape: IShape; distance: number }> {
return this.physics return this.game
.findIntersecting(shape.boundingBox) .findIntersecting(shape.boundingBox)
.filter((b) => b.shape) .filter((b) => b.shape)
.map((b) => ({ .map((b) => ({
@ -118,7 +130,6 @@ export class Character extends GameObject {
private setPosition(value: vec2) { private setPosition(value: vec2) {
this.shape.position = value; this.shape.position = value;
this.camera.sendCommand(new MoveToCommand(value));
vec2.add(value, value, vec2.fromValues(80, 0)); vec2.add(value, value, vec2.fromValues(80, 0));
this.light.sendCommand(new MoveToCommand(value)); this.light.sendCommand(new MoveToCommand(value));
} }

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(this, center, radius, color, lightness); this.light = new CircleLight(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

@ -17,7 +17,7 @@ export class Tunnel extends GameObject {
) { ) {
super(); super();
this.shape = new DrawableTunnel(from, to, fromRadius, toRadius); this.shape = new DrawableTunnel(from, to, fromRadius, toRadius, this);
physics.addStaticBoundingBox(this.shape.boundingBox); physics.addStaticBoundingBox(this.shape.boundingBox);
this.addCommandExecutor(RenderCommand, this.draw.bind(this)); this.addCommandExecutor(RenderCommand, this.draw.bind(this));
} }

View file

@ -1,27 +0,0 @@
import { vec2, vec3 } from 'gl-matrix';
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: Objects,
physics: Physics
): GameObject => {
const light = new Lamp(
vec2.create(),
40,
vec3.fromValues(0.67, 0.0, 0.33),
2
);
const camera = new Camera();
const character = new Character(physics, camera, light);
objects.addObject(light);
objects.addObject(camera);
objects.addObject(character);
return character;
};

View file

@ -1,10 +1,13 @@
import { BoundingBox } from './bounding-box'; import { BoundingBox } from './bounding-box';
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { GameObject } from '../objects/game-object';
export interface IShape { export interface IShape {
readonly isInverted: boolean; readonly isInverted: boolean;
readonly boundingBox: BoundingBox; readonly boundingBox: BoundingBox;
readonly gameObject?: GameObject;
distance(target: vec2): number; distance(target: vec2): number;
normal(from: vec2): vec2; normal(from: vec2): vec2;

View file

@ -3,6 +3,7 @@ import { IShape } from '../i-shape';
import { BoundingBox } from '../bounding-box'; import { BoundingBox } from '../bounding-box';
import { Circle } from './circle'; import { Circle } from './circle';
import { settings } from '../../drawing/settings'; import { settings } from '../../drawing/settings';
import { GameObject } from '../../objects/game-object';
export class Blob implements IShape { export class Blob implements IShape {
private static readonly boundingCircleRadius = 19; private static readonly boundingCircleRadius = 19;
@ -31,7 +32,10 @@ export class Blob implements IShape {
settings.shaderMacros.footRadius settings.shaderMacros.footRadius
); );
public constructor(center: vec2) { public constructor(
center: vec2,
public readonly gameObject: GameObject = null
) {
this.position = center; this.position = center;
} }
@ -68,6 +72,6 @@ export class Blob implements IShape {
} }
public clone(): Blob { public clone(): Blob {
return new Blob(this.boundingCircle.center); return new Blob(this.boundingCircle.center, this.gameObject);
} }
} }

View file

@ -1,11 +1,16 @@
import { vec2 } from 'gl-matrix'; import { vec2 } from 'gl-matrix';
import { IShape } from '../i-shape'; import { IShape } from '../i-shape';
import { BoundingBox } from '../bounding-box'; import { BoundingBox } from '../bounding-box';
import { GameObject } from '../../objects/game-object';
export class Circle implements IShape { export class Circle implements IShape {
public readonly isInverted = false; public readonly isInverted = false;
public constructor(public center = vec2.create(), public radius = 0) {} public constructor(
public center = vec2.create(),
public radius = 0,
public readonly gameObject: GameObject = null
) {}
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;
@ -36,6 +41,6 @@ export class Circle implements IShape {
} }
public clone(): Circle { public clone(): Circle {
return new Circle(vec2.clone(this.center), this.radius); return new Circle(vec2.clone(this.center), this.radius, this.gameObject);
} }
} }

View file

@ -4,6 +4,7 @@ import { clamp01 } from '../../helper/clamp';
import { mix } from '../../helper/mix'; import { mix } from '../../helper/mix';
import { IShape } from '../i-shape'; import { IShape } from '../i-shape';
import { rotate90Deg } from '../../helper/rotate-90-deg'; import { rotate90Deg } from '../../helper/rotate-90-deg';
import { GameObject } from '../../objects/game-object';
export class TunnelShape implements IShape { export class TunnelShape implements IShape {
public readonly isInverted = true; public readonly isInverted = true;
@ -14,7 +15,8 @@ export class TunnelShape implements IShape {
public readonly from: vec2, public readonly from: vec2,
public readonly to: vec2, public readonly to: vec2,
public readonly fromRadius: number, public readonly fromRadius: number,
public readonly toRadius: number public readonly toRadius: number,
public readonly gameObject: GameObject = null
) { ) {
this.toFromDelta = vec2.subtract(vec2.create(), to, from); this.toFromDelta = vec2.subtract(vec2.create(), to, from);
} }
@ -106,7 +108,8 @@ export class TunnelShape implements IShape {
vec2.clone(this.from), vec2.clone(this.from),
vec2.clone(this.to), vec2.clone(this.to),
this.fromRadius, this.fromRadius,
this.toRadius this.toRadius,
this.gameObject
); );
} }
} }