diff --git a/frontend/src/scripts/commands/types/before-draw.ts b/frontend/src/scripts/commands/types/before-render.ts similarity index 50% rename from frontend/src/scripts/commands/types/before-draw.ts rename to frontend/src/scripts/commands/types/before-render.ts index d673cc0..735675e 100644 --- a/frontend/src/scripts/commands/types/before-draw.ts +++ b/frontend/src/scripts/commands/types/before-render.ts @@ -1,8 +1,8 @@ import { IRenderer } from '../../drawing/i-renderer'; import { Command } from '../command'; -export class BeforeDrawCommand extends Command { - public constructor(public readonly drawer?: IRenderer) { +export class BeforeRenderCommand extends Command { + public constructor(public readonly renderer?: IRenderer) { super(); } } diff --git a/frontend/src/scripts/commands/types/draw.ts b/frontend/src/scripts/commands/types/draw.ts index e14e602..b87da1e 100644 --- a/frontend/src/scripts/commands/types/draw.ts +++ b/frontend/src/scripts/commands/types/draw.ts @@ -1,8 +1,8 @@ import { IRenderer } from '../../drawing/i-renderer'; import { Command } from '../command'; -export class DrawCommand extends Command { - public constructor(public readonly drawer?: IRenderer) { +export class RenderCommand extends Command { + public constructor(public readonly renderer?: IRenderer) { super(); } } diff --git a/frontend/src/scripts/drawing/graphics-library/frame-buffer/default-frame-buffer.ts b/frontend/src/scripts/drawing/graphics-library/frame-buffer/default-frame-buffer.ts index d1e80ec..07290dd 100644 --- a/frontend/src/scripts/drawing/graphics-library/frame-buffer/default-frame-buffer.ts +++ b/frontend/src/scripts/drawing/graphics-library/frame-buffer/default-frame-buffer.ts @@ -1,9 +1,8 @@ -import { IProgram } from '../program/i-program'; import { FrameBuffer } from './frame-buffer'; export class DefaultFrameBuffer extends FrameBuffer { - constructor(gl: WebGL2RenderingContext, programs: Array) { - super(gl, programs); + constructor(gl: WebGL2RenderingContext) { + super(gl); this.frameBuffer = null; this.setSize(); diff --git a/frontend/src/scripts/drawing/graphics-library/frame-buffer/frame-buffer.ts b/frontend/src/scripts/drawing/graphics-library/frame-buffer/frame-buffer.ts index d254ec7..aeee6e1 100644 --- a/frontend/src/scripts/drawing/graphics-library/frame-buffer/frame-buffer.ts +++ b/frontend/src/scripts/drawing/graphics-library/frame-buffer/frame-buffer.ts @@ -1,5 +1,4 @@ import { vec2 } from 'gl-matrix'; -import { IProgram } from '../program/i-program'; export abstract class FrameBuffer { public renderScale = 1; @@ -8,12 +7,9 @@ export abstract class FrameBuffer { protected size: vec2; protected frameBuffer: WebGLFramebuffer; - constructor( - protected gl: WebGL2RenderingContext, - protected programs: Array - ) {} + constructor(protected gl: WebGL2RenderingContext) {} - public render(uniforms: any, colorInput?: WebGLTexture) { + public bindAndClear(colorInput?: WebGLTexture) { this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.frameBuffer); if (colorInput !== null) { @@ -23,11 +19,6 @@ export abstract class FrameBuffer { this.gl.viewport(0, 0, this.size.x, this.size.y); this.gl.clearColor(0, 0, 0, 0); this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); - - this.programs.forEach((p) => { - p.bindAndSetUniforms(uniforms); - p.draw(); - }); } public setSize() { diff --git a/frontend/src/scripts/drawing/graphics-library/frame-buffer/intermediate-frame-buffer.ts b/frontend/src/scripts/drawing/graphics-library/frame-buffer/intermediate-frame-buffer.ts index c586ef5..f9c2518 100644 --- a/frontend/src/scripts/drawing/graphics-library/frame-buffer/intermediate-frame-buffer.ts +++ b/frontend/src/scripts/drawing/graphics-library/frame-buffer/intermediate-frame-buffer.ts @@ -1,11 +1,10 @@ -import { IProgram } from '../program/i-program'; import { FrameBuffer } from './frame-buffer'; export class IntermediateFrameBuffer extends FrameBuffer { private frameTexture: WebGLTexture; - constructor(gl: WebGL2RenderingContext, programs: Array) { - super(gl, programs); + constructor(gl: WebGL2RenderingContext) { + super(gl); this.frameTexture = this.gl.createTexture(); this.configureTexture(); diff --git a/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts b/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts index 0961026..8bfd496 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/fragment-shader-only-program.ts @@ -10,7 +10,7 @@ export class FragmentShaderOnlyProgram extends Program { substitutions: { [name: string]: string } ) { super(gl, vertexShaderSource, fragmentShaderSource, substitutions); - this.prepareScreenQuad('a_position'); + this.prepareScreenQuad('vertexPosition'); } public bind() { diff --git a/frontend/src/scripts/drawing/graphics-library/program/i-program.ts b/frontend/src/scripts/drawing/graphics-library/program/i-program.ts index f68905e..7bdb18c 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/i-program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/i-program.ts @@ -1,5 +1,8 @@ +import { vec2 } from 'gl-matrix'; + export interface IProgram { bindAndSetUniforms(values: { [name: string]: any }): void; + setDrawingRectangle(topLeft: vec2, size: vec2): void; draw(): void; delete(): void; } diff --git a/frontend/src/scripts/drawing/graphics-library/program/program.ts b/frontend/src/scripts/drawing/graphics-library/program/program.ts index 453430d..6c62d35 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/program.ts @@ -1,3 +1,4 @@ +import { mat2d, vec2 } from 'gl-matrix'; import { createShader } from '../helper/create-shader'; import { loadUniform } from '../helper/load-uniform'; import { IProgram } from './i-program'; @@ -5,6 +6,8 @@ import { IProgram } from './i-program'; export abstract class Program implements IProgram { protected program: WebGLProgram; private shaders: Array = []; + private modelTransform = mat2d.identity(mat2d.create()); + private readonly ndcToUv: mat2d; private uniforms: Array<{ name: Array; @@ -20,15 +23,22 @@ export abstract class Program implements IProgram { ) { this.createProgram(vertexShaderSource, fragmentShaderSource, substitutions); this.queryUniforms(); + + this.ndcToUv = mat2d.fromScaling(mat2d.create(), vec2.fromValues(0.5, 0.5)); + mat2d.translate(this.ndcToUv, this.ndcToUv, vec2.fromValues(1, 1)); } public bindAndSetUniforms(values: { [name: string]: any }) { this.bind(); - this.setUniforms(values); + this.setUniforms({ modelTransform: this.modelTransform, ...values }); } - public bind() { - this.gl.useProgram(this.program); + public setDrawingRectangle(topLeft: vec2, size: vec2) { + mat2d.invert(this.modelTransform, this.ndcToUv); + + mat2d.translate(this.modelTransform, this.modelTransform, topLeft); + mat2d.scale(this.modelTransform, this.modelTransform, size); + mat2d.multiply(this.modelTransform, this.modelTransform, this.ndcToUv); } public setUniforms(values: { [name: string]: any }) { @@ -51,6 +61,10 @@ export abstract class Program implements IProgram { public abstract draw(): void; + protected bind() { + this.gl.useProgram(this.program); + } + private queryUniforms() { const uniformCount = this.gl.getProgramParameter( this.program, diff --git a/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts b/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts index 3f17767..1350c12 100644 --- a/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts +++ b/frontend/src/scripts/drawing/graphics-library/program/uniform-array-autoscaling-program.ts @@ -1,4 +1,4 @@ -import { InfoText } from '../../../objects/types/info-text'; +import { vec2 } from 'gl-matrix'; import { FragmentShaderOnlyProgram } from './fragment-shader-only-program'; import { IProgram } from './i-program'; @@ -9,6 +9,9 @@ export class UniformArrayAutoScalingProgram implements IProgram { }> = []; private current: FragmentShaderOnlyProgram; + private drawingRectangleTopLeft = vec2.fromValues(0, 0); + private drawingRectangleSize = vec2.fromValues(1, 1); + constructor( private gl: WebGL2RenderingContext, private vertexShaderSource: string, @@ -17,6 +20,7 @@ export class UniformArrayAutoScalingProgram implements IProgram { private options: { getValueFromUniforms: (values: { [name: string]: any }) => number; uniformArraySizeName: string; + enablingMacro: string; startingValue: number; steps: number; maximumValue: number; @@ -31,12 +35,10 @@ export class UniformArrayAutoScalingProgram implements IProgram { } } - bindAndSetUniforms(values: { [name: string]: any }): void { + public bindAndSetUniforms(values: { [name: string]: any }): void { let value = this.options.getValueFromUniforms(values); value = Math.min(this.options.maximumValue, value); - InfoText.modifyRecord(this.options.uniformArraySizeName, value.toString()); - const closest = this.programs.find( (p) => value < p.value && value + this.options.steps >= p.value ); @@ -46,14 +48,23 @@ export class UniformArrayAutoScalingProgram implements IProgram { this.current = this.createProgram(value + this.options.steps); } + this.current.setDrawingRectangle( + this.drawingRectangleTopLeft, + this.drawingRectangleSize + ); this.current.bindAndSetUniforms(values); } - draw(): void { + public setDrawingRectangle(topLeft: vec2, size: vec2) { + this.drawingRectangleTopLeft = topLeft; + this.drawingRectangleSize = size; + } + + public draw(): void { this.current.draw(); } - delete(): void { + public delete(): void { this.programs.forEach((p) => p.program.delete()); } @@ -64,6 +75,7 @@ export class UniformArrayAutoScalingProgram implements IProgram { this.fragmentShaderSource, { [this.options.uniformArraySizeName]: Math.floor(arraySize).toString(), + [this.options.enablingMacro]: arraySize > 0 ? '1' : '0', ...this.substitutions, } ); diff --git a/frontend/src/scripts/drawing/i-renderer.ts b/frontend/src/scripts/drawing/i-renderer.ts index 17f575e..d304901 100644 --- a/frontend/src/scripts/drawing/i-renderer.ts +++ b/frontend/src/scripts/drawing/i-renderer.ts @@ -1,14 +1,20 @@ import { vec2 } from 'gl-matrix'; -import { Circle } from '../math/circle'; +import { Circle } from './primitives/circle'; +import { IPrimitive } from './primitives/i-primitive'; export interface IRenderer { startFrame(deltaTime: DOMHighResTimeStamp): void; finishFrame(): void; - giveUniforms(uniforms: any): void; - appendToUniformList(listName: string, ...values: Array): void; + + drawPrimitive(primitive: IPrimitive): void; + setCameraPosition(position: vec2): void; setCursorPosition(position: vec2): void; setInViewArea(size: number): vec2; + + giveUniforms(uniforms: any): void; + appendToUniformList(listName: string, ...values: Array): void; + screenUvToWorldCoordinate(mousePosition: vec2): vec2; drawInfoText(text: string): void; isOnScreen(boundingCircle: Circle): boolean; diff --git a/frontend/src/scripts/drawing/primitives/circle.ts b/frontend/src/scripts/drawing/primitives/circle.ts new file mode 100644 index 0000000..2a9b504 --- /dev/null +++ b/frontend/src/scripts/drawing/primitives/circle.ts @@ -0,0 +1,23 @@ +import { vec2 } from 'gl-matrix'; +import { IPrimitive } from './i-primitive'; + +export class Circle implements IPrimitive { + public constructor(public center: vec2, public radius: number) {} + + public distance(target: vec2): number { + return vec2.distance(this.center, target) - this.radius; + } + + public minimumDistance(target: vec2): number { + return vec2.distance(this.center, target) - this.radius; + } + + public isInside(target: vec2): boolean { + return this.distance(target) < 0; + } + + public areIntersecting(other: Circle): boolean { + const distance = vec2.distance(this.center, other.center); + return distance < this.radius + other.radius; + } +} diff --git a/frontend/src/scripts/drawing/primitives/i-primitive.ts b/frontend/src/scripts/drawing/primitives/i-primitive.ts new file mode 100644 index 0000000..89ff9d2 --- /dev/null +++ b/frontend/src/scripts/drawing/primitives/i-primitive.ts @@ -0,0 +1,6 @@ +import { vec2 } from 'gl-matrix'; + +export interface IPrimitive { + distance(target: vec2): number; + minimumDistance(target: vec2): number; +} diff --git a/frontend/src/scripts/math/rectangle.ts b/frontend/src/scripts/drawing/primitives/rectangle.ts similarity index 100% rename from frontend/src/scripts/math/rectangle.ts rename to frontend/src/scripts/drawing/primitives/rectangle.ts diff --git a/frontend/src/scripts/drawing/primitives/tunnel-shape.ts b/frontend/src/scripts/drawing/primitives/tunnel-shape.ts new file mode 100644 index 0000000..10e8291 --- /dev/null +++ b/frontend/src/scripts/drawing/primitives/tunnel-shape.ts @@ -0,0 +1,46 @@ +import { vec2 } from 'gl-matrix'; +import { clamp01 } from '../../helper/clamp'; +import { mix } from '../../helper/mix'; +import { Circle } from './circle'; +import { IPrimitive } from './i-primitive'; + +export class TunnelShape implements IPrimitive { + public readonly toFromDelta: vec2; + private toFromDeltaLength: number; + + private boundingCircle: Circle; + + constructor( + public readonly from: vec2, + public readonly to: vec2, + public readonly fromRadius: number, + public readonly toRadius: number + ) { + this.toFromDelta = vec2.subtract(vec2.create(), to, from); + this.toFromDeltaLength = vec2.length(this.toFromDelta); + + this.boundingCircle = new Circle( + vec2.fromValues(from.x / 2 + to.x / 2, from.y / 2 + to.y / 2), + Math.max(fromRadius, toRadius) + vec2.distance(from, to) + ); + } + + public distance(target: vec2): number { + const targetFromDelta = vec2.subtract(vec2.create(), target, this.from); + const h = clamp01( + vec2.dot(targetFromDelta, this.toFromDelta) / + this.toFromDeltaLength / + this.toFromDeltaLength + ); + return ( + vec2.distance( + targetFromDelta, + vec2.scale(vec2.create(), this.toFromDelta, h) + ) - mix(this.fromRadius, this.toRadius, h) + ); + } + + public minimumDistance(target: vec2): number { + return this.boundingCircle.distance(target); + } +} diff --git a/frontend/src/scripts/drawing/webgl2-renderer.ts b/frontend/src/scripts/drawing/webgl2-renderer.ts index f71fa12..211e2f5 100644 --- a/frontend/src/scripts/drawing/webgl2-renderer.ts +++ b/frontend/src/scripts/drawing/webgl2-renderer.ts @@ -1,13 +1,16 @@ import { mat2d, vec2 } from 'gl-matrix'; import { clamp } from '../helper/clamp'; -import { Circle } from '../math/circle'; -import { Rectangle } from '../math/rectangle'; import { InfoText } from '../objects/types/info-text'; 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 './primitives/circle'; +import { IPrimitive } from './primitives/i-primitive'; +import { Rectangle } from './primitives/rectangle'; +import { TunnelShape } from './primitives/tunnel-shape'; export class WebGl2Renderer implements IRenderer { private gl: WebGL2RenderingContext; @@ -19,6 +22,12 @@ export class WebGl2Renderer implements IRenderer { private cursorPosition = vec2.create(); private distanceFieldFrameBuffer: IntermediateFrameBuffer; private lightingFrameBuffer: DefaultFrameBuffer; + private distanceProgram: IProgram; + private lightingProgram: IProgram; + + private primitives: Array; + + private tileMultiplier = 5; private targetDeltaTime = (1 / 30) * 1000; private deltaTimeError = (1 / 1000) * 1000; @@ -46,7 +55,7 @@ export class WebGl2Renderer implements IRenderer { this.targetDeltaTime - this.deltaTimeError ) { this.distanceFieldFrameBuffer.renderScale += - this.additiveQualityIncrease / 4; + this.additiveQualityIncrease / 3; this.lightingFrameBuffer.renderScale += this.additiveQualityIncrease; } else if ( this.exponentialDecayedDeltaTime > @@ -91,39 +100,7 @@ export class WebGl2Renderer implements IRenderer { throw new Error('WebGl2 is not supported'); } - const distanceScale = 64; - - this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl, [ - new UniformArrayAutoScalingProgram( - this.gl, - shaderSources[0][0], - shaderSources[0][1], - { distanceScale }, - { - getValueFromUniforms: (v) => (v.lines ? v.lines.length / 2 : 0), - uniformArraySizeName: 'lineCount', - startingValue: 5, - steps: 5, - maximumValue: 100, - } - ), - ]); - - this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl, [ - new UniformArrayAutoScalingProgram( - this.gl, - shaderSources[1][0], - shaderSources[1][1], - { distanceScale }, - { - getValueFromUniforms: (v) => (v.lights ? v.lights.length : 0), - uniformArraySizeName: 'lightCount', - startingValue: 1, - steps: 1, - maximumValue: 8, - } - ), - ]); + this.createPipeline(shaderSources); this.distanceFieldFrameBuffer.renderScale = 0.5; this.lightingFrameBuffer.renderScale = 1; @@ -133,8 +110,49 @@ export class WebGl2Renderer implements IRenderer { } catch {} } + private createPipeline(shaderSources: Array<[string, string]>) { + const distanceScale = 64; + + this.distanceFieldFrameBuffer = new IntermediateFrameBuffer(this.gl); + this.distanceProgram = new UniformArrayAutoScalingProgram( + this.gl, + shaderSources[0][0], + shaderSources[0][1], + { distanceScale }, + { + getValueFromUniforms: (v) => (v.lines ? v.lines.length / 2 : 0), + uniformArraySizeName: 'lineCount', + startingValue: 0, + enablingMacro: 'linesEnabled', + steps: 1, + maximumValue: 15, + } + ); + + this.lightingFrameBuffer = new DefaultFrameBuffer(this.gl); + this.lightingProgram = new UniformArrayAutoScalingProgram( + this.gl, + shaderSources[1][0], + shaderSources[1][1], + { distanceScale }, + { + getValueFromUniforms: (v) => (v.lights ? v.lights.length : 0), + uniformArraySizeName: 'lightCount', + startingValue: 1, + enablingMacro: null, + steps: 1, + maximumValue: 8, + } + ); + } + + public drawPrimitive(primitive: IPrimitive) { + this.primitives.push(primitive); + } + public startFrame(deltaTime: DOMHighResTimeStamp): void { this.configureRenderScale(deltaTime); + this.primitives = []; this.stopwatch?.start(); this.uniforms = {}; @@ -144,11 +162,81 @@ export class WebGl2Renderer implements IRenderer { public finishFrame() { this.calculateOwnUniforms(); - this.distanceFieldFrameBuffer.render(this.uniforms); - this.lightingFrameBuffer.render( - this.uniforms, + + this.distanceFieldFrameBuffer.bindAndClear(); + const q = 1 / this.tileMultiplier; + const uvSize = vec2.fromValues(q, q); + + const possiblyOnScreenPrimitives = this.primitives.filter( + (p) => p.minimumDistance(this.viewCircle.center) < this.viewCircle.radius + ) as Array; + + InfoText.modifyRecord( + 'nearby lines', + possiblyOnScreenPrimitives.length.toString() + ); + + const origin = vec2.transformMat2d( + vec2.create(), + vec2.fromValues(0, 0), + this.uniforms.uvToWorld + ); + + const firstCenter = vec2.transformMat2d( + vec2.create(), + vec2.fromValues(q, q), + this.uniforms.uvToWorld + ); + + vec2.subtract(firstCenter, firstCenter, origin); + + const worldR = vec2.length(firstCenter); + this.uniforms.maxMinDistance = 2 * worldR; + + let sumLineCount = 0; + + for (let x = 0; x < 1; x += q) { + for (let y = 0; y < 1; y += q) { + const uvBottomLeft = vec2.fromValues(x, y); + this.distanceProgram.setDrawingRectangle(uvBottomLeft, uvSize); + + const tileCenterWorldCoordinates = vec2.transformMat2d( + vec2.create(), + vec2.add(vec2.create(), uvBottomLeft, vec2.fromValues(q, q)), + this.uniforms.uvToWorld + ); + + const primitivesNearTile = possiblyOnScreenPrimitives.filter( + (p) => p.distance(tileCenterWorldCoordinates) < 2 * worldR + ); + + sumLineCount += primitivesNearTile.length; + + this.uniforms.lines = []; + this.uniforms.radii = []; + + for (let tunnel of primitivesNearTile) { + this.uniforms.lines.push(tunnel.from); + this.uniforms.lines.push(tunnel.toFromDelta); + this.uniforms.radii.push(tunnel.fromRadius); + this.uniforms.radii.push(tunnel.toRadius); + } + + this.distanceProgram.bindAndSetUniforms(this.uniforms); + this.distanceProgram.draw(); + } + } + + InfoText.modifyRecord( + 'lines', + (sumLineCount / this.tileMultiplier / this.tileMultiplier).toFixed(2) + ); + + this.lightingFrameBuffer.bindAndClear( this.distanceFieldFrameBuffer.colorTexture ); + this.lightingProgram.bindAndSetUniforms(this.uniforms); + this.lightingProgram.draw(); this.stopwatch?.stop(); } @@ -158,13 +246,11 @@ export class WebGl2Renderer implements IRenderer { this.distanceFieldFrameBuffer.getSize() ); - const ndcToWorld = mat2d.fromTranslation( + const uvToWorld = mat2d.fromTranslation( mat2d.create(), this.viewBox.topLeft ); - mat2d.scale(ndcToWorld, ndcToWorld, this.viewBox.size); - mat2d.scale(ndcToWorld, ndcToWorld, vec2.fromValues(0.5, 0.5)); - mat2d.translate(ndcToWorld, ndcToWorld, vec2.fromValues(1, 1)); + mat2d.scale(uvToWorld, uvToWorld, this.viewBox.size); const worldToDistanceUV = mat2d.scale( mat2d.create(), @@ -173,13 +259,20 @@ export class WebGl2Renderer implements IRenderer { ); 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); this.giveUniforms({ distanceScreenToWorld, worldToDistanceUV, cursorPosition, - ndcToWorld, + ndcToUv, + uvToWorld, viewBoxSize: this.viewBox.size, }); } @@ -241,6 +334,7 @@ export class WebGl2Renderer implements IRenderer { ); const halfDiagonal = vec2.scale(vec2.create(), this.viewBox.size, 0.5); + this.viewCircle.center = vec2.add( vec2.create(), this.viewBox.topLeft, diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index a00c895..00351bf 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -4,8 +4,8 @@ import caveVertexShader from '../shaders/passthrough-distance-vs.glsl'; import lightsVertexShader from '../shaders/passthrough-shading-vs.glsl'; // import lightsShader from '../shaders/rainbow-shading-fs.glsl'; import { CommandBroadcaster } from './commands/command-broadcaster'; -import { BeforeDrawCommand } from './commands/types/before-draw'; -import { DrawCommand } from './commands/types/draw'; +import { BeforeRenderCommand } from './commands/types/before-render'; +import { RenderCommand } from './commands/types/draw'; import { StepCommand } from './commands/types/step'; import { WebGl2Renderer } from './drawing/webgl2-renderer'; import { timeIt } from './helper/timing'; @@ -76,8 +76,8 @@ export class Game { this.objects.sendCommand(new StepCommand(deltaTime)); this.renderer.startFrame(deltaTime); - this.objects.sendCommand(new BeforeDrawCommand(this.renderer)); - this.objects.sendCommand(new DrawCommand(this.renderer)); + this.objects.sendCommand(new BeforeRenderCommand(this.renderer)); + this.objects.sendCommand(new RenderCommand(this.renderer)); this.renderer.finishFrame(); window.requestAnimationFrame(this.gameLoop.bind(this)); diff --git a/frontend/src/scripts/helper/clamp.ts b/frontend/src/scripts/helper/clamp.ts index 33aa210..d1d639b 100644 --- a/frontend/src/scripts/helper/clamp.ts +++ b/frontend/src/scripts/helper/clamp.ts @@ -1,4 +1,5 @@ export const clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, value)); -export const clamp01 = (value: number): number => clamp(value, 0, 1); +export const clamp01 = (value: number): number => + Math.min(1, Math.max(0, value)); diff --git a/frontend/src/scripts/helper/mix.ts b/frontend/src/scripts/helper/mix.ts new file mode 100644 index 0000000..6af50f8 --- /dev/null +++ b/frontend/src/scripts/helper/mix.ts @@ -0,0 +1,2 @@ +export const mix = (from: number, to: number, q: number) => + from + (to - from) * q; diff --git a/frontend/src/scripts/math/circle.ts b/frontend/src/scripts/math/circle.ts deleted file mode 100644 index 20a65a2..0000000 --- a/frontend/src/scripts/math/circle.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { vec2 } from 'gl-matrix'; - -export class Circle { - public constructor(public center: vec2, public radius: number) {} - - public isInside(position: vec2): boolean { - const distance = vec2.distance(this.center, position); - return distance < this.radius; - } - - public areIntersecting(other: Circle): boolean { - const distance = vec2.distance(this.center, other.center); - return distance < this.radius + other.radius; - } -} diff --git a/frontend/src/scripts/objects/types/camera.ts b/frontend/src/scripts/objects/types/camera.ts index 43f1f7f..5d84c3c 100644 --- a/frontend/src/scripts/objects/types/camera.ts +++ b/frontend/src/scripts/objects/types/camera.ts @@ -1,5 +1,5 @@ import { vec2 } from 'gl-matrix'; -import { BeforeDrawCommand } from '../../commands/types/before-draw'; +import { BeforeRenderCommand } from '../../commands/types/before-render'; import { CursorMoveCommand } from '../../commands/types/cursor-move-command'; import { MoveToCommand } from '../../commands/types/move-to'; import { ZoomCommand } from '../../commands/types/zoom'; @@ -13,7 +13,7 @@ export class Camera extends GameObject { constructor(private light: CircleLight) { super(); - this.addCommandExecutor(BeforeDrawCommand, this.draw.bind(this)); + this.addCommandExecutor(BeforeRenderCommand, this.draw.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); this.addCommandExecutor( CursorMoveCommand, @@ -22,10 +22,10 @@ export class Camera extends GameObject { this.addCommandExecutor(ZoomCommand, this.zoom.bind(this)); } - private draw(c: BeforeDrawCommand) { - c.drawer.setCameraPosition(this.position); - c.drawer.setCursorPosition(this.cursorPosition); - this._boundingBoxSize = c.drawer.setInViewArea(this.inViewArea); + private draw(c: BeforeRenderCommand) { + c.renderer.setCameraPosition(this.position); + c.renderer.setCursorPosition(this.cursorPosition); + this._boundingBoxSize = c.renderer.setInViewArea(this.inViewArea); } private moveTo(c: MoveToCommand) { diff --git a/frontend/src/scripts/objects/types/circle-light.ts b/frontend/src/scripts/objects/types/circle-light.ts index 0578ed6..db3907b 100644 --- a/frontend/src/scripts/objects/types/circle-light.ts +++ b/frontend/src/scripts/objects/types/circle-light.ts @@ -1,7 +1,7 @@ import { vec2, vec3 } from 'gl-matrix'; -import { DrawCommand } from '../../commands/types/draw'; +import { RenderCommand } from '../../commands/types/draw'; import { MoveToCommand } from '../../commands/types/move-to'; -import { Circle } from '../../math/circle'; +import { Circle } from '../../drawing/primitives/circle'; import { GameObject } from '../game-object'; const range = 2000; @@ -18,13 +18,13 @@ export class CircleLight extends GameObject { this.boundingCircle = new Circle(center, range); - this.addCommandExecutor(DrawCommand, this.draw.bind(this)); + this.addCommandExecutor(RenderCommand, this.draw.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); } - private draw(c: DrawCommand) { - if (c.drawer.isPositionOnScreen(this.center)) { - c.drawer.appendToUniformList('lights', { + private draw(c: RenderCommand) { + if (c.renderer.isPositionOnScreen(this.center)) { + c.renderer.appendToUniformList('lights', { center: this.center, radius: this.radius, value: this.value, diff --git a/frontend/src/scripts/objects/types/info-text.ts b/frontend/src/scripts/objects/types/info-text.ts index 9afe750..a1b9df8 100644 --- a/frontend/src/scripts/objects/types/info-text.ts +++ b/frontend/src/scripts/objects/types/info-text.ts @@ -1,11 +1,11 @@ +import { RenderCommand } from '../../commands/types/draw'; import { GameObject } from '../game-object'; -import { DrawCommand } from '../../commands/types/draw'; export class InfoText extends GameObject { constructor() { super(); - this.addCommandExecutor(DrawCommand, this.draw.bind(this)); + this.addCommandExecutor(RenderCommand, this.draw.bind(this)); } private static records: Map = new Map(); @@ -14,9 +14,9 @@ export class InfoText extends GameObject { InfoText.records.set(key, value); } - private draw(e: DrawCommand) { + private draw(e: RenderCommand) { let text = ''; InfoText.records.forEach((v, k) => (text += `${k}\n\t${v}\n`)); - e.drawer.drawInfoText(text); + e.renderer.drawInfoText(text); } } diff --git a/frontend/src/scripts/objects/types/tunnel.ts b/frontend/src/scripts/objects/types/tunnel.ts index 2f5fc58..f4ad078 100644 --- a/frontend/src/scripts/objects/types/tunnel.ts +++ b/frontend/src/scripts/objects/types/tunnel.ts @@ -1,36 +1,20 @@ import { vec2 } from 'gl-matrix'; -import { DrawCommand } from '../../commands/types/draw'; -import { Circle } from '../../math/circle'; +import { RenderCommand } from '../../commands/types/draw'; +import { TunnelShape } from '../../drawing/primitives/tunnel-shape'; import { GameObject } from '../game-object'; export interface Line {} export class Tunnel extends GameObject { - private boundingCircle: Circle; - private tangent: vec2; + private primitive: TunnelShape; - constructor( - private from: vec2, - private to: vec2, - private radiusFrom: number, - private radiusTo: number - ) { + constructor(from: vec2, to: vec2, fromRadius: number, toRadius: number) { super(); - - this.boundingCircle = new Circle( - vec2.fromValues(from.x / 2 + to.x / 2, from.y / 2 + to.y / 2), - radiusFrom + radiusTo + vec2.distance(from, to) - ); - - this.tangent = vec2.subtract(vec2.create(), to, from); - - this.addCommandExecutor(DrawCommand, this.draw.bind(this)); + this.primitive = new TunnelShape(from, to, fromRadius, toRadius); + this.addCommandExecutor(RenderCommand, this.draw.bind(this)); } - private draw(c: DrawCommand) { - if (c.drawer.isOnScreen(this.boundingCircle)) { - c.drawer.appendToUniformList('lines', this.from, this.tangent); - c.drawer.appendToUniformList('radii', this.radiusFrom, this.radiusTo); - } + private draw(c: RenderCommand) { + c.renderer.drawPrimitive(this.primitive); } } diff --git a/frontend/src/shaders/cave-distance-fs.glsl b/frontend/src/shaders/cave-distance-fs.glsl index cbd4aa1..e309fae 100644 --- a/frontend/src/shaders/cave-distance-fs.glsl +++ b/frontend/src/shaders/cave-distance-fs.glsl @@ -2,36 +2,46 @@ precision mediump float; -#define INFINITY 200.0 +#define LINES_ENABLED {linesEnabled} #define LINE_COUNT {lineCount} #define CAVE_COLOR vec3(0.36, 0.38, 0.76) #define AIR_COLOR vec3(0.7) #define DISTANCE_SCALE {distanceScale} -// start, end - start -uniform vec2[LINE_COUNT * 2] lines; -// startRadius, endRadois -uniform float[LINE_COUNT * 2] radii; +uniform float maxMinDistance; + +#if LINES_ENABLED + // start, end - start + uniform vec2[LINE_COUNT * 2] lines; + // startRadius, endRadois + uniform float[LINE_COUNT * 2] radii; +#endif in vec2 worldCoordinates; out vec4 fragmentColor; void main() { - float minDistance = INFINITY; + #if LINES_ENABLED + float minDistance = maxMinDistance; - for (int i = 0; i < LINE_COUNT; i++) { - vec2 pa = worldCoordinates - lines[2 * i]; - vec2 ba = lines[2 * i + 1]; - float baLength = length(ba); - float h = clamp(dot(pa / baLength, ba / baLength), 0.0, 1.0); - float lineDistance = distance(pa, ba * h) - mix(radii[2 * i], radii[2 * i + 1], h); + for (int i = 0; i < LINE_COUNT; i++) { + vec2 pa = worldCoordinates - lines[2 * i]; + vec2 ba = lines[2 * i + 1]; + float baLength = length(ba); + // todo: do we really want this dot(pa / baLength, ba / baLength) + float h = clamp(dot(pa / baLength, ba / baLength), 0.0, 1.0); + float lineDistance = distance(pa, ba * h) - mix(radii[2 * i], radii[2 * i + 1], h); - minDistance = min(minDistance, lineDistance); - } + minDistance = min(minDistance, lineDistance); + } - float distance = -minDistance; - fragmentColor = vec4( - mix(CAVE_COLOR, AIR_COLOR, clamp(distance, -10.0, 0.0) / 10.0 + 1.0), - distance / DISTANCE_SCALE - ); + float distance = -minDistance; + fragmentColor = vec4( + mix(CAVE_COLOR, AIR_COLOR, clamp(distance, -10.0, 0.0) / 10.0 + 1.0), + distance / DISTANCE_SCALE + ); + #else + + fragmentColor = vec4(AIR_COLOR, maxMinDistance / DISTANCE_SCALE); + #endif } diff --git a/frontend/src/shaders/passthrough-distance-vs.glsl b/frontend/src/shaders/passthrough-distance-vs.glsl index 17fc0c9..5248949 100644 --- a/frontend/src/shaders/passthrough-distance-vs.glsl +++ b/frontend/src/shaders/passthrough-distance-vs.glsl @@ -2,11 +2,15 @@ precision mediump float; -uniform mat3 ndcToWorld; -in vec4 a_position; +uniform mat3 modelTransform; +uniform mat3 uvToWorld; +uniform mat3 ndcToUv; + +in vec4 vertexPosition; out vec2 worldCoordinates; void main() { - worldCoordinates = (vec3(a_position.xy, 1.0) * ndcToWorld).xy; - gl_Position = a_position; + vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0) * modelTransform; + worldCoordinates = (vertexPosition2D * ndcToUv * uvToWorld).xy; + gl_Position = vec4(vertexPosition2D.xy, 0.0, 1.0); } diff --git a/frontend/src/shaders/passthrough-shading-vs.glsl b/frontend/src/shaders/passthrough-shading-vs.glsl index f77ce3b..6874fcc 100644 --- a/frontend/src/shaders/passthrough-shading-vs.glsl +++ b/frontend/src/shaders/passthrough-shading-vs.glsl @@ -4,12 +4,14 @@ precision mediump float; #define LIGHT_COUNT {lightCount} -uniform mat3 ndcToWorld; -in vec4 a_position; +uniform mat3 ndcToUv; +uniform mat3 uvToWorld; + +in vec4 vertexPosition; + out vec2 worldCoordinates; out vec2 uvCoordinates; - uniform struct Light { vec2 center; float radius; @@ -19,12 +21,14 @@ uniform struct Light { out vec2[LIGHT_COUNT] directions; void main() { - worldCoordinates = (vec3(a_position.xy, 1.0) * ndcToWorld).xy; - uvCoordinates = ((a_position.xy + vec2(1.0)) / 2.0).xy; + vec3 vertexPosition2D = vec3(vertexPosition.xy, 1.0); + vec3 uvCoordinates1 = vertexPosition2D * ndcToUv; + worldCoordinates = (uvCoordinates1 * uvToWorld).xy; + uvCoordinates = (uvCoordinates1).xy; for (int i = 0; i < LIGHT_COUNT; i++) { directions[i] = lights[i].center - worldCoordinates; } - gl_Position = a_position; + gl_Position = vertexPosition; }