Refactor rendering
This commit is contained in:
parent
edca93fcfe
commit
76282a4cf7
25 changed files with 239 additions and 213 deletions
|
|
@ -2,6 +2,7 @@ import { IDrawable } from './i-drawable';
|
|||
import { IDrawableDescriptor } from './i-drawable-descriptor';
|
||||
import { settings } from '../settings';
|
||||
import { Blob } from '../../shapes/types/blob';
|
||||
import { vec2, mat2d } from 'gl-matrix';
|
||||
|
||||
export class DrawableBlob extends Blob implements IDrawable {
|
||||
public static descriptor: IDrawableDescriptor = {
|
||||
|
|
@ -10,17 +11,37 @@ export class DrawableBlob extends Blob implements IDrawable {
|
|||
shaderCombinationSteps: settings.shaderCombinations.blobSteps,
|
||||
};
|
||||
|
||||
public serializeToUniforms(uniforms: any): void {
|
||||
public serializeToUniforms(
|
||||
uniforms: any,
|
||||
scale: number,
|
||||
transform: mat2d
|
||||
): void {
|
||||
const uniformName = DrawableBlob.descriptor.uniformName;
|
||||
if (!uniforms.hasOwnProperty(uniformName)) {
|
||||
uniforms[uniformName] = [];
|
||||
}
|
||||
|
||||
uniforms[uniformName].push({
|
||||
headCenter: this.head.center,
|
||||
torsoCenter: this.torso.center,
|
||||
leftFootCenter: this.leftFoot.center,
|
||||
rightFootCenter: this.rightFoot.center,
|
||||
headCenter: vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
this.head.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
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { IDrawable } from './i-drawable';
|
|||
import { TunnelShape } from '../../shapes/types/tunnel-shape';
|
||||
import { IDrawableDescriptor } from './i-drawable-descriptor';
|
||||
import { settings } from '../settings';
|
||||
import { mat2d, vec2 } from 'gl-matrix';
|
||||
|
||||
export class DrawableTunnel extends TunnelShape implements IDrawable {
|
||||
public static descriptor: IDrawableDescriptor = {
|
||||
|
|
@ -10,17 +11,25 @@ export class DrawableTunnel extends TunnelShape implements IDrawable {
|
|||
shaderCombinationSteps: settings.shaderCombinations.lineSteps,
|
||||
};
|
||||
|
||||
public serializeToUniforms(uniforms: any): void {
|
||||
public serializeToUniforms(
|
||||
uniforms: any,
|
||||
scale: number,
|
||||
transform: mat2d
|
||||
): void {
|
||||
const uniformName = DrawableTunnel.descriptor.uniformName;
|
||||
if (!uniforms.hasOwnProperty(uniformName)) {
|
||||
uniforms[uniformName] = [];
|
||||
}
|
||||
|
||||
uniforms[uniformName].push({
|
||||
from: this.from,
|
||||
toFromDelta: this.toFromDelta,
|
||||
fromRadius: this.fromRadius,
|
||||
toRadius: this.toRadius,
|
||||
from: vec2.transformMat2d(vec2.create(), this.from, transform),
|
||||
toFromDelta: vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
this.toFromDelta,
|
||||
transform
|
||||
),
|
||||
fromRadius: this.fromRadius * scale,
|
||||
toRadius: this.toRadius * scale,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { vec2, mat2d } from 'gl-matrix';
|
||||
|
||||
export interface IDrawable {
|
||||
distance(target: vec2): number;
|
||||
serializeToUniforms(uniforms: any): void;
|
||||
serializeToUniforms(uniforms: any, scale: number, transform: mat2d): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { vec2, vec3 } from 'gl-matrix';
|
||||
import { vec2, vec3, mat2d } from 'gl-matrix';
|
||||
import { GameObject } from '../../../objects/game-object';
|
||||
import { settings } from '../../settings';
|
||||
import { IDrawableDescriptor } from '../i-drawable-descriptor';
|
||||
|
|
@ -12,7 +12,6 @@ export class CircleLight implements ILight {
|
|||
};
|
||||
|
||||
constructor(
|
||||
public readonly owner: GameObject,
|
||||
public center: vec2,
|
||||
public radius: number,
|
||||
public color: vec3,
|
||||
|
|
@ -23,7 +22,11 @@ export class CircleLight implements ILight {
|
|||
return 0;
|
||||
}
|
||||
|
||||
public serializeToUniforms(uniforms: any): void {
|
||||
public serializeToUniforms(
|
||||
uniforms: any,
|
||||
scale: number,
|
||||
transform: mat2d
|
||||
): void {
|
||||
const uniformName = CircleLight.descriptor.uniformName;
|
||||
|
||||
if (!uniforms.hasOwnProperty(uniformName)) {
|
||||
|
|
@ -31,8 +34,8 @@ export class CircleLight implements ILight {
|
|||
}
|
||||
|
||||
uniforms[uniformName].push({
|
||||
center: this.center,
|
||||
radius: this.radius,
|
||||
center: vec2.transformMat2d(vec2.create(), this.center, transform),
|
||||
radius: this.radius * scale,
|
||||
value: this.value,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 { settings } from '../../settings';
|
||||
import { GameObject } from '../../../objects/game-object';
|
||||
|
|
@ -12,7 +12,6 @@ export class PointLight implements ILight {
|
|||
};
|
||||
|
||||
public constructor(
|
||||
public readonly owner: GameObject,
|
||||
public center: vec2,
|
||||
public radius: number,
|
||||
public color: vec3,
|
||||
|
|
@ -23,7 +22,11 @@ export class PointLight implements ILight {
|
|||
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;
|
||||
|
||||
if (!uniforms.hasOwnProperty(listName)) {
|
||||
|
|
@ -31,8 +34,8 @@ export class PointLight implements ILight {
|
|||
}
|
||||
|
||||
uniforms[listName].push({
|
||||
center: this.center,
|
||||
radius: this.radius,
|
||||
center: vec2.transformMat2d(vec2.create(), this.center, transform),
|
||||
radius: this.radius * scale,
|
||||
value: this.value,
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { ILight } from './drawables/lights/i-light';
|
||||
import { IDrawable } from './drawables/i-drawable';
|
||||
import { BoundingBoxBase } from '../shapes/bounding-box-base';
|
||||
|
||||
export interface IRenderer {
|
||||
startFrame(deltaTime: DOMHighResTimeStamp): void;
|
||||
|
|
@ -10,7 +11,7 @@ export interface IRenderer {
|
|||
drawLight(light: ILight): void;
|
||||
drawInfoText(text: string): void;
|
||||
|
||||
setCameraPosition(position: vec2): void;
|
||||
readonly canvasSize: vec2;
|
||||
setViewArea(viewArea: BoundingBoxBase): void;
|
||||
setCursorPosition(position: vec2): void;
|
||||
setInViewArea(size: number): vec2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { vec2, mat2d } from 'gl-matrix';
|
||||
import { InfoText } from '../../objects/types/info-text';
|
||||
import { IDrawable } from '../drawables/i-drawable';
|
||||
import { IDrawableDescriptor } from '../drawables/i-drawable-descriptor';
|
||||
|
|
@ -27,20 +27,11 @@ export class RenderingPass {
|
|||
this.drawables.push(drawable);
|
||||
}
|
||||
|
||||
public render(
|
||||
commonUniforms: any,
|
||||
viewBoxCenter: vec2,
|
||||
viewBoxRadius: number,
|
||||
inputTexture?: WebGLTexture
|
||||
) {
|
||||
public render(commonUniforms: any, inputTexture?: WebGLTexture) {
|
||||
this.frame.bindAndClear(inputTexture);
|
||||
const q = 1 / settings.tileMultiplier;
|
||||
const tileUvSize = vec2.fromValues(q, q);
|
||||
|
||||
const possiblyOnScreenDrawables = this.drawables.filter(
|
||||
(p) => p.distance(viewBoxCenter) < viewBoxRadius
|
||||
);
|
||||
|
||||
const origin = vec2.transformMat2d(
|
||||
vec2.create(),
|
||||
vec2.fromValues(0, 0),
|
||||
|
|
@ -59,6 +50,9 @@ export class RenderingPass {
|
|||
|
||||
let sumLineCount = 0;
|
||||
|
||||
const scale = 1;
|
||||
const transform = mat2d.create();
|
||||
|
||||
for (let x = 0; x < 1; x += q) {
|
||||
for (let y = 0; y < 1; y += q) {
|
||||
const uniforms = { ...commonUniforms };
|
||||
|
|
@ -73,24 +67,24 @@ export class RenderingPass {
|
|||
uniforms.uvToWorld
|
||||
);
|
||||
|
||||
const primitivesNearTile = possiblyOnScreenDrawables.filter(
|
||||
const primitivesNearTile = this.drawables.filter(
|
||||
(p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR
|
||||
);
|
||||
|
||||
sumLineCount += primitivesNearTile.length;
|
||||
|
||||
primitivesNearTile.forEach((p) => p.serializeToUniforms(uniforms));
|
||||
primitivesNearTile.forEach((p) =>
|
||||
p.serializeToUniforms(uniforms, scale, transform)
|
||||
);
|
||||
|
||||
this.program.bindAndSetUniforms(uniforms);
|
||||
this.program.draw();
|
||||
}
|
||||
}
|
||||
|
||||
this.drawables = [];
|
||||
|
||||
InfoText.modifyRecord(
|
||||
'nearby ' + this.drawableDescriptors[0].countMacroName,
|
||||
possiblyOnScreenDrawables.length.toFixed(2)
|
||||
this.drawables.length.toFixed(2)
|
||||
);
|
||||
|
||||
InfoText.modifyRecord(
|
||||
|
|
@ -101,5 +95,7 @@ export class RenderingPass {
|
|||
settings.tileMultiplier
|
||||
).toFixed(2)
|
||||
);
|
||||
|
||||
this.drawables = [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ import { IDrawable } from '../drawables/i-drawable';
|
|||
import { DrawableTunnel } from '../drawables/drawable-tunnel';
|
||||
import { enableExtension } from '../graphics-library/helper/enable-extension';
|
||||
import { DrawableBlob } from '../drawables/drawable-blob';
|
||||
import { BoundingBoxBase } from '../../shapes/bounding-box-base';
|
||||
|
||||
export class WebGl2Renderer implements IRenderer {
|
||||
private gl: WebGL2RenderingContext;
|
||||
private stopwatch?: WebGlStopwatch;
|
||||
|
||||
private viewBoxBottomLeft = vec2.create();
|
||||
private cameraPosition = vec2.create();
|
||||
private viewBoxSize = vec2.create();
|
||||
private cursorPosition = vec2.create();
|
||||
|
||||
|
|
@ -92,16 +92,10 @@ export class WebGl2Renderer implements IRenderer {
|
|||
public finishFrame() {
|
||||
this.calculateMatrices();
|
||||
|
||||
const viewBoxRadius = vec2.length(
|
||||
vec2.scale(vec2.create(), this.viewBoxSize, 0.5)
|
||||
);
|
||||
|
||||
this.distancePass.render(this.uniforms, this.cameraPosition, viewBoxRadius);
|
||||
this.distancePass.render(this.uniforms);
|
||||
|
||||
this.lightingPass.render(
|
||||
this.uniforms,
|
||||
this.cameraPosition,
|
||||
viewBoxRadius,
|
||||
this.distanceFieldFrameBuffer.colorTexture
|
||||
);
|
||||
|
||||
|
|
@ -164,11 +158,16 @@ export class WebGl2Renderer implements IRenderer {
|
|||
);
|
||||
}
|
||||
|
||||
public setCameraPosition(position: vec2) {
|
||||
this.cameraPosition = position;
|
||||
this.viewBoxBottomLeft = vec2.fromValues(
|
||||
this.cameraPosition.x - this.viewBoxSize.x / 2,
|
||||
this.cameraPosition.y - this.viewBoxSize.y / 2
|
||||
public get canvasSize(): vec2 {
|
||||
return vec2.fromValues(this.canvas.clientWidth, this.canvas.clientHeight);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (this.overlay.innerText != text) {
|
||||
this.overlay.innerText = text;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const settings = {
|
|||
scaleTargets: [
|
||||
[0.2, 0.1],
|
||||
[0.6, 0.1],
|
||||
[1, 0.3],
|
||||
[1, 1],
|
||||
/*[1.25, 0.75],
|
||||
[1.5, 1],
|
||||
[1.75, 1.25],
|
||||
|
|
@ -20,12 +20,12 @@ export const settings = {
|
|||
multiplicativeDecrease: 1.15,
|
||||
},
|
||||
},
|
||||
tileMultiplier: 5,
|
||||
tileMultiplier: 8,
|
||||
shaderMacros: {
|
||||
edgeSmoothing: 10,
|
||||
headRadius: 5,
|
||||
torsoRadius: 10,
|
||||
footRadius: 4,
|
||||
torsoRadius: 8,
|
||||
footRadius: 2,
|
||||
},
|
||||
shaderCombinations: {
|
||||
lineSteps: [0, 1, 2, 4, 8, 16, 128],
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ uniform float maxMinDistance;
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
in vec2 worldCoordinates;
|
||||
out vec2 fragmentColor;
|
||||
|
||||
|
|
|
|||
|
|
@ -61,40 +61,41 @@ void main() {
|
|||
|
||||
#if CIRCLE_LIGHT_COUNT > 0
|
||||
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(
|
||||
lightCenterDistance / LIGHT_DROP + 1.0, 2.0
|
||||
lightDistance / LIGHT_DROP + 1.0, 2.0
|
||||
);
|
||||
|
||||
float q = INFINITY;
|
||||
float rayLength = startingDistance;
|
||||
float exponentialDecayDistance = rayLength;
|
||||
vec2 direction = normalize(circleLightDirections[i]) / viewBoxSize;
|
||||
|
||||
for (int j = 0; j < 48; j++) {
|
||||
if (rayLength > lightCenterDistance - circleLights[i].radius) {
|
||||
rayLength = lightCenterDistance - circleLights[i].radius;
|
||||
float minDistance = getDistance(uvCoordinates + direction * rayLength);
|
||||
exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0;
|
||||
q = min(q, minDistance / rayLength);
|
||||
|
||||
if (rayLength >= lightDistance) {
|
||||
lighting += lightColorAtPosition * clamp(
|
||||
q / circleLights[i].radius * (lightCenterDistance + 1.0), 0.0, 1.0
|
||||
q / circleLights[i].radius * lightCenterDistance, 0.0, 1.0
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
float minDistance = getDistance(uvCoordinates + direction * rayLength);
|
||||
exponentialDecayDistance = (exponentialDecayDistance + minDistance) / 2.0;
|
||||
q = min(q, exponentialDecayDistance / rayLength);
|
||||
q = min(q, minDistance / rayLength);
|
||||
rayLength += minDistance;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#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);
|
||||
|
||||
vec3 lightColorAtPosition = mix(
|
||||
|
|
@ -120,8 +121,11 @@ void main() {
|
|||
q = min(q, exponentialDecayDistance);
|
||||
rayLength += minDistance;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
#endif
|
||||
|
||||
fragmentColor = vec4(colorAtPosition * lighting * step(0.0, startingDistance), 1.0);
|
||||
fragmentColor = vec4(
|
||||
colorAtPosition * lighting * step(0.0, startingDistance),
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,22 +8,33 @@ import { MouseListener } from './input/mouse-listener';
|
|||
import { TouchListener } from './input/touch-listener';
|
||||
import { Objects } from './objects/objects';
|
||||
import { InfoText } from './objects/types/info-text';
|
||||
import { createCharacter } from './objects/world/create-character';
|
||||
import { createDungeon } from './objects/world/create-dungeon';
|
||||
import { RenderCommand } from './drawing/commands/render';
|
||||
import { Physics } from './physics/physics';
|
||||
import { TeleportToCommand } from './physics/commands/teleport-to';
|
||||
import { IRenderer } from './drawing/i-renderer';
|
||||
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 objects = new Objects();
|
||||
private physics = new Physics();
|
||||
|
||||
private renderer: IRenderer;
|
||||
private previousFpsValues: Array<number> = [];
|
||||
|
||||
private infoText = new InfoText();
|
||||
private character: Character;
|
||||
private renderer: IRenderer;
|
||||
|
||||
constructor() {
|
||||
const canvas: HTMLCanvasElement = document.querySelector('canvas#main');
|
||||
const overlay: HTMLElement = document.querySelector('#overlay');
|
||||
|
|
@ -52,13 +63,29 @@ export class Game {
|
|||
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() {
|
||||
this.objects.addObject(new InfoText());
|
||||
this.objects.addObject(this.infoText);
|
||||
|
||||
const start = createDungeon(this.objects, this.physics);
|
||||
createDungeon(this.objects, this.physics);
|
||||
const character = createCharacter(this.objects, this.physics);
|
||||
console.log('start', start.from);
|
||||
character.sendCommand(new TeleportToCommand(start.from));
|
||||
|
||||
this.character = new Character(this);
|
||||
//this.physics.addDynamicBoundingBox(this.character.boundingBox);
|
||||
this.addObject(this.character);
|
||||
this.addObject(this.camera);
|
||||
this.character.sendCommand(new TeleportToCommand(start.from));
|
||||
}
|
||||
|
||||
private handleVisibilityChange() {
|
||||
|
|
@ -78,10 +105,23 @@ export class Game {
|
|||
this.calculateFps(deltaTime);
|
||||
|
||||
this.objects.sendCommand(new StepCommand(deltaTime));
|
||||
this.camera.sendCommand(new MoveToCommand(this.character.position));
|
||||
|
||||
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();
|
||||
|
||||
window.requestAnimationFrame(this.gameLoop.bind(this));
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import { InfoText } from '../objects/types/info-text';
|
||||
import { last } from './last';
|
||||
|
||||
export function timeIt(interval = 60) {
|
||||
return function (
|
||||
target: any,
|
||||
propertyKey: string,
|
||||
descriptor: PropertyDescriptor
|
||||
) {
|
||||
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
let i = 0;
|
||||
let previousTimes: Array<DOMHighResTimeStamp> = [];
|
||||
|
||||
|
|
@ -19,10 +16,9 @@ export function timeIt(interval = 60) {
|
|||
previousTimes.push(end - start);
|
||||
|
||||
if (i++ % interval == 0) {
|
||||
previousTimes.sort();
|
||||
const text = `Max: ${previousTimes[previousTimes.length - 1].toFixed(
|
||||
2
|
||||
)} ms\n\tMedian: ${previousTimes[
|
||||
previousTimes = previousTimes.sort();
|
||||
|
||||
const text = `Max: ${last(previousTimes).toFixed(2)} ms\n\tMedian: ${previousTimes[
|
||||
Math.floor(previousTimes.length / 2)
|
||||
].toFixed(2)} ms`;
|
||||
|
||||
|
|
|
|||
9
frontend/src/scripts/i-game.ts
Normal file
9
frontend/src/scripts/i-game.ts
Normal 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>;
|
||||
}
|
||||
|
|
@ -9,40 +9,46 @@ import { GameObject } from '../game-object';
|
|||
import { Lamp } from './lamp';
|
||||
|
||||
export class Camera extends GameObject {
|
||||
private inViewArea = 1920 * 1080 * 5;
|
||||
private inViewAreaSize = 1920 * 1080 * 5;
|
||||
private cursorPosition = vec2.create();
|
||||
private boundingBox: BoundingBox;
|
||||
private _viewArea: BoundingBox;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.boundingBox = new BoundingBox(null);
|
||||
this._viewArea = new BoundingBox(null);
|
||||
|
||||
this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this));
|
||||
this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this));
|
||||
this.addCommandExecutor(
|
||||
CursorMoveCommand,
|
||||
this.setCursorPosition.bind(this)
|
||||
);
|
||||
this.addCommandExecutor(CursorMoveCommand, this.setCursorPosition.bind(this));
|
||||
this.addCommandExecutor(ZoomCommand, this.zoom.bind(this));
|
||||
}
|
||||
|
||||
public get viewAreaSize(): vec2 {
|
||||
return this.boundingBox.size;
|
||||
public get viewArea(): BoundingBox {
|
||||
return this._viewArea;
|
||||
}
|
||||
|
||||
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);
|
||||
this.boundingBox.size = c.renderer.setInViewArea(this.inViewArea);
|
||||
}
|
||||
|
||||
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) {
|
||||
this.inViewArea *= c.factor;
|
||||
this.inViewAreaSize *= c.factor;
|
||||
}
|
||||
|
||||
private setCursorPosition(c: CursorMoveCommand) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { vec2, vec3 } from 'gl-matrix';
|
||||
import { KeyDownCommand } from '../../input/commands/key-down';
|
||||
import { KeyUpCommand } from '../../input/commands/key-up';
|
||||
import { SwipeCommand } from '../../input/commands/swipe';
|
||||
|
|
@ -13,21 +13,25 @@ import { Blob } from '../../shapes/types/blob';
|
|||
import { RenderCommand } from '../../drawing/commands/render';
|
||||
import { DrawableBlob } from '../../drawing/drawables/drawable-blob';
|
||||
import { Lamp } from './lamp';
|
||||
import { Game } from '../../game';
|
||||
import { IGame } from '../../i-game';
|
||||
|
||||
export class Character extends GameObject {
|
||||
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;
|
||||
|
||||
constructor(
|
||||
private physics: Physics,
|
||||
private camera: Camera,
|
||||
private light: Lamp
|
||||
) {
|
||||
constructor(private game: IGame) {
|
||||
super();
|
||||
|
||||
this.shape = new DrawableBlob(vec2.create());
|
||||
game.addObject(this.light);
|
||||
|
||||
this.addCommandExecutor(StepCommand, this.stepHandler.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(SwipeCommand, (c) => {
|
||||
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) {
|
||||
c.renderer.drawShape(this.shape);
|
||||
this.light.sendCommand(c);
|
||||
}
|
||||
|
||||
private tryMoving(delta: vec2, isFirstIteration = true) {
|
||||
|
|
@ -87,6 +96,9 @@ export class Character extends GameObject {
|
|||
)
|
||||
.sort((e) => Math.abs(e.distance));
|
||||
|
||||
if (intersecting.length < 1) {
|
||||
return;
|
||||
}
|
||||
const normal = intersecting[0].shape.normal(this.shape.center);
|
||||
|
||||
const maxDistance = intersecting.reduce((p, c) =>
|
||||
|
|
@ -106,7 +118,7 @@ export class Character extends GameObject {
|
|||
private getNearShapesTo(
|
||||
shape: Blob
|
||||
): Array<{ shape: IShape; distance: number }> {
|
||||
return this.physics
|
||||
return this.game
|
||||
.findIntersecting(shape.boundingBox)
|
||||
.filter((b) => b.shape)
|
||||
.map((b) => ({
|
||||
|
|
@ -118,7 +130,6 @@ export class Character extends GameObject {
|
|||
|
||||
private setPosition(value: vec2) {
|
||||
this.shape.position = value;
|
||||
this.camera.sendCommand(new MoveToCommand(value));
|
||||
vec2.add(value, value, vec2.fromValues(80, 0));
|
||||
this.light.sendCommand(new MoveToCommand(value));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export class Lamp extends GameObject {
|
|||
constructor(center: vec2, radius: number, color: vec3, lightness: number) {
|
||||
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(MoveToCommand, this.moveTo.bind(this));
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export class Tunnel extends GameObject {
|
|||
) {
|
||||
super();
|
||||
|
||||
this.shape = new DrawableTunnel(from, to, fromRadius, toRadius);
|
||||
this.shape = new DrawableTunnel(from, to, fromRadius, toRadius, this);
|
||||
physics.addStaticBoundingBox(this.shape.boundingBox);
|
||||
this.addCommandExecutor(RenderCommand, this.draw.bind(this));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
import { BoundingBox } from './bounding-box';
|
||||
import { vec2 } from 'gl-matrix';
|
||||
import { GameObject } from '../objects/game-object';
|
||||
|
||||
export interface IShape {
|
||||
readonly isInverted: boolean;
|
||||
readonly boundingBox: BoundingBox;
|
||||
|
||||
readonly gameObject?: GameObject;
|
||||
|
||||
distance(target: vec2): number;
|
||||
normal(from: vec2): vec2;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { IShape } from '../i-shape';
|
|||
import { BoundingBox } from '../bounding-box';
|
||||
import { Circle } from './circle';
|
||||
import { settings } from '../../drawing/settings';
|
||||
import { GameObject } from '../../objects/game-object';
|
||||
|
||||
export class Blob implements IShape {
|
||||
private static readonly boundingCircleRadius = 19;
|
||||
|
|
@ -31,7 +32,10 @@ export class Blob implements IShape {
|
|||
settings.shaderMacros.footRadius
|
||||
);
|
||||
|
||||
public constructor(center: vec2) {
|
||||
public constructor(
|
||||
center: vec2,
|
||||
public readonly gameObject: GameObject = null
|
||||
) {
|
||||
this.position = center;
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +72,6 @@ export class Blob implements IShape {
|
|||
}
|
||||
|
||||
public clone(): Blob {
|
||||
return new Blob(this.boundingCircle.center);
|
||||
return new Blob(this.boundingCircle.center, this.gameObject);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import { vec2 } from 'gl-matrix';
|
||||
import { IShape } from '../i-shape';
|
||||
import { BoundingBox } from '../bounding-box';
|
||||
import { GameObject } from '../../objects/game-object';
|
||||
|
||||
export class Circle implements IShape {
|
||||
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 {
|
||||
return vec2.distance(this.center, target) - this.radius;
|
||||
|
|
@ -36,6 +41,6 @@ export class Circle implements IShape {
|
|||
}
|
||||
|
||||
public clone(): Circle {
|
||||
return new Circle(vec2.clone(this.center), this.radius);
|
||||
return new Circle(vec2.clone(this.center), this.radius, this.gameObject);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { clamp01 } from '../../helper/clamp';
|
|||
import { mix } from '../../helper/mix';
|
||||
import { IShape } from '../i-shape';
|
||||
import { rotate90Deg } from '../../helper/rotate-90-deg';
|
||||
import { GameObject } from '../../objects/game-object';
|
||||
|
||||
export class TunnelShape implements IShape {
|
||||
public readonly isInverted = true;
|
||||
|
|
@ -14,7 +15,8 @@ export class TunnelShape implements IShape {
|
|||
public readonly from: vec2,
|
||||
public readonly to: vec2,
|
||||
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);
|
||||
}
|
||||
|
|
@ -106,7 +108,8 @@ export class TunnelShape implements IShape {
|
|||
vec2.clone(this.from),
|
||||
vec2.clone(this.to),
|
||||
this.fromRadius,
|
||||
this.toRadius
|
||||
this.toRadius,
|
||||
this.gameObject
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue