diff --git a/README.md b/README.md index 0ef7251..87883bb 100644 --- a/README.md +++ b/README.md @@ -21,5 +21,4 @@ A good-looking 2D adventure. - vs code glsl formatter - docker engine dashboard? - local dev env -- vs code config - prettier script diff --git a/frontend/package.json b/frontend/package.json index 3b07fb7..f5e3552 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -48,7 +48,6 @@ "svg-url-loader": "^6.0.0", "terser-webpack-plugin": "^2.3.5", "ts-loader": "^8.0.1", - "twgl.js": "^4.15.2", "typescript": "^3.8.3", "uuid": "^8.2.0", "webpack": "^4.43.0", diff --git a/frontend/src/index.html b/frontend/src/index.html index ab49e95..d899376 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -15,12 +15,8 @@ Test -
- -
-
- -
-
+ +
+ diff --git a/frontend/src/scripts/drawing/drawer.ts b/frontend/src/scripts/drawing/drawer.ts index 25c8ede..d03b3df 100644 --- a/frontend/src/scripts/drawing/drawer.ts +++ b/frontend/src/scripts/drawing/drawer.ts @@ -1,9 +1,9 @@ import { Vec2 } from '../math/vec2'; export interface Drawer { - startWaitingForInstructions(); - finishWaitingForInstructions(); + startFrame(); + finishFrame(); setCameraPosition(position: Vec2); - setViewBoxSize(size: Vec2); - drawCornerText(text: string); + setInViewWidth(size: number): Vec2; + drawInfoText(text: string); } diff --git a/frontend/src/scripts/drawing/graphics-library.ts b/frontend/src/scripts/drawing/graphics-library.ts deleted file mode 100644 index 9c73922..0000000 --- a/frontend/src/scripts/drawing/graphics-library.ts +++ /dev/null @@ -1 +0,0 @@ -//export function diff --git a/frontend/src/scripts/drawing/graphics-library/create-program.ts b/frontend/src/scripts/drawing/graphics-library/create-program.ts new file mode 100644 index 0000000..9b6ad6e --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/create-program.ts @@ -0,0 +1,24 @@ +import { createShader } from './create-shader'; + +export const createProgram = ( + gl: WebGL2RenderingContext, + vertexShader: string, + fragmentShader: string +): WebGLProgram => { + const program = gl.createProgram(); + + gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexShader)); + gl.attachShader( + program, + createShader(gl, gl.FRAGMENT_SHADER, fragmentShader) + ); + gl.linkProgram(program); + + const success = gl.getProgramParameter(program, gl.LINK_STATUS); + if (success) { + return program; + } + + console.log(gl.getProgramInfoLog(program)); + gl.deleteProgram(program); +}; diff --git a/frontend/src/scripts/drawing/graphics-library/create-shader.ts b/frontend/src/scripts/drawing/graphics-library/create-shader.ts new file mode 100644 index 0000000..37c82db --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/create-shader.ts @@ -0,0 +1,16 @@ +export const createShader = ( + gl: WebGL2RenderingContext, + type: GLenum, + source: string +): WebGLShader => { + const shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (success) { + return shader; + } + + console.log(gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); +}; diff --git a/frontend/src/scripts/drawing/graphics-library/prepare-screen-quad.ts b/frontend/src/scripts/drawing/graphics-library/prepare-screen-quad.ts new file mode 100644 index 0000000..4a33466 --- /dev/null +++ b/frontend/src/scripts/drawing/graphics-library/prepare-screen-quad.ts @@ -0,0 +1,26 @@ +export const prepareScreenQuad = ( + gl: WebGL2RenderingContext, + program: WebGLProgram, + attributeName: string +): WebGLVertexArrayObject => { + const positionAttributeLocation = gl.getAttribLocation( + program, + attributeName + ); + + const positionBuffer = gl.createBuffer(); + + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + new Float32Array([-1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0]), + gl.STATIC_DRAW + ); + const vao = gl.createVertexArray(); + + gl.bindVertexArray(vao); + gl.enableVertexAttribArray(positionAttributeLocation); + gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); + + return vao; +}; diff --git a/frontend/src/scripts/drawing/renderer.ts b/frontend/src/scripts/drawing/renderer.ts index 8ffed11..dc42ef4 100644 --- a/frontend/src/scripts/drawing/renderer.ts +++ b/frontend/src/scripts/drawing/renderer.ts @@ -1,84 +1,102 @@ -const twgl = require('twgl.js'); - import { Drawer } from './drawer'; import { Vec2 } from '../math/vec2'; +import { createProgram } from './graphics-library/create-program'; +import { prepareScreenQuad } from './graphics-library/prepare-screen-quad'; export class WebGl2Renderer implements Drawer { private gl: WebGL2RenderingContext; - private programInfo: any; - private bufferInfo: any; - private vao: any; + private program: WebGLProgram; + private vao: WebGLVertexArrayObject; - constructor( - private canvas: HTMLCanvasElement, - private overlay: HTMLElement, - shaderSources: Array - ) { - twgl.setDefaults({ attribPrefix: 'a_' }); - - this.gl = this.canvas.getContext('webgl2'); - if (!this.gl) { - throw new Error('WebGl2 not supported'); - } - - this.programInfo = twgl.createProgramInfo(this.gl, shaderSources); - - const arrays = { - position: { - numComponents: 3, - data: [-1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, -1.0, 0.0], - }, - indices: { - numComponents: 3, - data: [0, 1, 2, 0, 2, 3], - }, - }; - this.bufferInfo = twgl.createBufferInfoFromArrays(this.gl, arrays); - this.vao = twgl.createVAOFromBufferInfo( - this.gl, - this.programInfo, - this.bufferInfo - ); - } - - startWaitingForInstructions() { - //throw new Error("Method not implemented."); - } + public enableHighDpiRendering = false; + public renderScale = 0.33; private cameraPosition: Vec2; private viewBoxSize: Vec2; - finishWaitingForInstructions() { - const gl = this.gl; + constructor( + private canvas: HTMLCanvasElement, + private overlay: HTMLElement, + shaderSources: [string, string] + ) { + this.gl = this.canvas.getContext('webgl2'); + if (!this.gl) { + throw new Error('WebGl2 is not supported'); + } - twgl.resizeCanvasToDisplaySize(this.canvas); - this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height); + this.program = createProgram(this.gl, ...shaderSources); + this.vao = prepareScreenQuad(this.gl, this.program, 'a_position'); + } - gl.clear(gl.COLOR_BUFFER_BIT); + private handleResize() { + const realToCssPixels = window.devicePixelRatio * this.renderScale; - const uniforms = { - cameraPosition: this.cameraPosition.list, - viewBoxSize: this.viewBoxSize.list, - resolution: [this.gl.canvas.width, this.gl.canvas.height], - }; + const displayWidth = Math.floor(this.canvas.clientWidth * realToCssPixels); + const displayHeight = Math.floor( + this.canvas.clientHeight * realToCssPixels + ); - this.gl.useProgram(this.programInfo.program); + if ( + this.canvas.width !== displayWidth || + this.canvas.height !== displayHeight + ) { + this.canvas.width = displayWidth; + this.canvas.height = displayHeight; + } + + this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); + } + + public startFrame() { + this.handleResize(); + this.gl.clearColor(0, 0, 0, 0); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT); + this.gl.useProgram(this.program); this.gl.bindVertexArray(this.vao); - //twgl.setBuffersAndAttributes(this.gl, this.programInfo, this.bufferInfo); + } - twgl.setUniforms(this.programInfo, uniforms); - twgl.drawBufferInfo(this.gl, this.bufferInfo); + finishFrame() { + const resolutionUniformLocation = this.gl.getUniformLocation( + this.program, + 'resolution' + ); + this.gl.uniform2f( + resolutionUniformLocation, + this.canvas.width, + this.canvas.height + ); + + const viewBoxSizeUniformLocation = this.gl.getUniformLocation( + this.program, + 'viewBoxSize' + ); + this.gl.uniform2f(viewBoxSizeUniformLocation, ...this.viewBoxSize.list); + + const cameraPositionUniformLocation = this.gl.getUniformLocation( + this.program, + 'cameraPosition' + ); + this.gl.uniform2f( + cameraPositionUniformLocation, + ...this.cameraPosition.list + ); + + this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4); } setCameraPosition(position: Vec2) { this.cameraPosition = position; } - setViewBoxSize(size: Vec2) { - this.viewBoxSize = size; + setInViewWidth(size: number): Vec2 { + const canvasAspectRatio = + this.canvas.clientHeight / this.canvas.clientWidth; + + this.viewBoxSize = new Vec2(size, size * canvasAspectRatio); + return this.viewBoxSize; } - drawCornerText(text: string) { + drawInfoText(text: string) { if (this.overlay.innerText != text) { this.overlay.innerText = text; } diff --git a/frontend/src/scripts/game.ts b/frontend/src/scripts/game.ts index 621b4a6..13cf469 100644 --- a/frontend/src/scripts/game.ts +++ b/frontend/src/scripts/game.ts @@ -16,6 +16,7 @@ export class Game { private previousTime: DOMHighResTimeStamp = 0; private objects: ObjectContainer = new ObjectContainer(); private renderer: WebGl2Renderer; + private frameCount = 0; constructor() { const canvas: HTMLCanvasElement = document.querySelector('canvas#main'); @@ -47,14 +48,18 @@ export class Game { @timeIt() private gameLoop(time: DOMHighResTimeStamp) { const deltaTime = time - this.previousTime; + if (this.frameCount++ % 30 == 0) { + InfoText.modifyRecord('FPS', (1000 / deltaTime).toFixed(1)); + } + this.previousTime = time; this.objects.sendCommand(new StepCommand(deltaTime)); - this.renderer.startWaitingForInstructions(); + this.renderer.startFrame(); this.objects.sendCommand(new DrawCommand(this.renderer)); - this.renderer.finishWaitingForInstructions(); + this.renderer.finishFrame(); - requestAnimationFrame(this.gameLoop.bind(this)); + window.requestAnimationFrame(this.gameLoop.bind(this)); } } diff --git a/frontend/src/scripts/objects/types/camera.ts b/frontend/src/scripts/objects/types/camera.ts index 1fb2234..d5442a5 100644 --- a/frontend/src/scripts/objects/types/camera.ts +++ b/frontend/src/scripts/objects/types/camera.ts @@ -1,23 +1,22 @@ import { GameObject } from '../game-object'; import { DrawCommand } from '../../commands/types/draw'; -import { Vec2 } from '../../math/vec2'; import { MoveToCommand } from '../../commands/types/move-to'; export class Camera extends GameObject { + private inViewWidth = 1500; constructor() { super(); - this._boundingBoxSize = new Vec2(1200, 800); this.addCommandExecutor(DrawCommand, this.draw.bind(this)); this.addCommandExecutor(MoveToCommand, this.moveTo.bind(this)); } - private draw(e: DrawCommand) { - e.drawer.setCameraPosition(this.position); - e.drawer.setViewBoxSize(this.boundingBoxSize); + private draw(c: DrawCommand) { + c.drawer.setCameraPosition(this.position); + this._boundingBoxSize = c.drawer.setInViewWidth(this.inViewWidth); } - private moveTo(e: MoveToCommand) { - this._position = e.position; + private moveTo(c: MoveToCommand) { + this._position = c.position; } } diff --git a/frontend/src/scripts/objects/types/info-text.ts b/frontend/src/scripts/objects/types/info-text.ts index c71527e..9afe750 100644 --- a/frontend/src/scripts/objects/types/info-text.ts +++ b/frontend/src/scripts/objects/types/info-text.ts @@ -17,6 +17,6 @@ export class InfoText extends GameObject { private draw(e: DrawCommand) { let text = ''; InfoText.records.forEach((v, k) => (text += `${k}\n\t${v}\n`)); - e.drawer.drawCornerText(text); + e.drawer.drawInfoText(text); } } diff --git a/frontend/src/shaders/dist copy 2.frag b/frontend/src/shaders/dist copy 2.frag new file mode 100644 index 0000000..3b3c5aa --- /dev/null +++ b/frontend/src/shaders/dist copy 2.frag @@ -0,0 +1,157 @@ +#version 300 es + +precision mediump float; + +const float smoothing = 10.0; +const float inf = 1000000.0; +const float pi = atan(1.0) * 4.0; + +float interpolate(float from, float to, float quotient) { + float steppedQ = sin(quotient * pi - pi * 0.5) * 0.5 + 0.5; + return from + (to - from) * clamp(steppedQ, 0.0, 1.0); +} + +vec2 rotate90deg(in vec2 vector) { + return vec2(-vector.y, vector.x); +} + +struct Line { + vec2 a; + vec2 b; + vec2 normal; + bool isLineEnd; +}[16] lines; + +float noise(float x){ + return fract(sin(x) * 43758.5453123); +} + +float terrain(float x) { + float result = 0.0; + + float frequency = 0.01; + float amplitude = 1.0; + + for (int i = 0; i < 8; i++) { + result += sin(2.0 * pi * x * frequency - 2.0 * pi * noise(float(i) * 200.0)) * amplitude; + frequency *= 1.5; + amplitude /= 1.2; + } + + return result; +} + +vec2 random2(vec2 st){ + st = vec2( dot(st,vec2(127.1,311.7)), + dot(st,vec2(269.5,183.3)) ); + return -1.0 + 2.0*fract(sin(st)*43758.5453123); +} + +float noise(vec2 st) { + vec2 i = floor(st); + vec2 f = fract(st); + + vec2 u = f*f*(3.0-2.0*f); + + return mix( mix( dot( random2(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ), + dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x), + mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ), + dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y); +} + +float lineDistance(in vec2 position, in Line line, out float h) { + vec2 pa = position - line.a, ba = line.b - line.a; + h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + vec2 delta = pa - ba*h; + // sign can return 0, double sign prevents this + float side = sign(sign(dot(delta, line.normal)) - 0.5); + return length(delta) * side; //+ terrain(length(ba * h)); +} + + +Line endDummyLineFromLine(Line line) { + return Line(line.b, line.b + rotate90deg(line.normal), line.normal, false); +} + +float getDistance(in vec2 target) { + float minDistance = inf; + + float leftJoinAcuteness = 0.0; + vec2 splitterLineNormalStart = vec2(-1.0, 0.0); + bool skipDistanceToPrevious = true; + for (int i = 0; i < lines.length(); i++) { + Line current = lines[i]; + + Line next; + if (current.isLineEnd || i + 1 == lines.length()) { + next = endDummyLineFromLine(current); + } else { + next = lines[i + 1]; + } + + vec2 splitterLineNormalEnd = rotate90deg(normalize(current.normal + next.normal)); + + float h; + float distanceToCurrent = lineDistance(target, current, h); + float rightJoinAcuteness = dot(next.b - current.a, next.normal - current.normal); + distanceToCurrent -= interpolate( + sign(leftJoinAcuteness) * smoothing, + sign(rightJoinAcuteness) * smoothing, h + ); + leftJoinAcuteness = rightJoinAcuteness; + + if ( + !( + dot(target - current.a, splitterLineNormalStart * -sign(dot(current.b - current.a, splitterLineNormalStart))) > 0.0 + || dot(target - current.b, splitterLineNormalEnd * sign(dot(current.a - current.b, splitterLineNormalEnd))) <= 0.0 + ) && abs(distanceToCurrent) < abs(minDistance) + ) { + minDistance = distanceToCurrent; + } + splitterLineNormalStart = splitterLineNormalEnd; + } + + return minDistance; +} + +void createWorld() { + lines[0] = Line(vec2(0.0, 300.0), vec2(550.0, 140.0), vec2(1.0), false); + lines[1] = Line(vec2(550.0, 140.0), vec2(750.0, 130.0), vec2(1.0), false); + lines[2] = Line(vec2(750.0, 130.0), vec2(650.0, 230.0), vec2(1.0), false); + lines[3] = Line(vec2(650.0, 230.0), vec2(850.0, 230.0), vec2(1.0), false); + lines[4] = Line(vec2(850.0, 230.0), vec2(800.0, 150.0), vec2(1.0), false); + lines[5] = Line(vec2(800.0, 150.0), vec2(1000.0, 120.0), vec2(1.0), false); + lines[6] = Line(vec2(1000.0, 120.0), vec2(1150, 120.0), vec2(1.0), false); + lines[7] = Line(vec2(1150, 120.0), vec2(10200, 350.0), vec2(1.0), true); + lines[8] = Line(vec2(0.0, 600.0), vec2(550.0, 440.0), vec2(-1.0), false); + lines[9] = Line(vec2(550.0, 440.0), vec2(750.0, 430.0), vec2(-1.0), false); + lines[10] = Line(vec2(750.0, 430.0), vec2(650.0, 530.0), vec2(-1.0), false); + lines[11] = Line(vec2(650.0, 530.0), vec2(850.0, 530.0), vec2(-1.0), false); + lines[12] = Line(vec2(850.0, 530.0), vec2(820.0, 450.0), vec2(-1.0), false); + lines[13] = Line(vec2(820.0, 450.0), vec2(1000.0, 420.0), vec2(-1.0), false); + lines[14] = Line(vec2(1000.0, 420.0), vec2(1150, 420.0), vec2(-1.0), false); + lines[15] = Line(vec2(1150, 420.0), vec2(10200, 650.0), vec2(-1.0), true); + + for (int i = 0; i < lines.length(); i++) { + vec2 tangent = lines[i].b - lines[i].a; + lines[i].normal = normalize( + vec2(-lines[i].normal.x * tangent.y, lines[i].normal.x * tangent.x) + ); + } +} + + +uniform vec2 cameraPosition; +uniform vec2 viewBoxSize; +uniform vec2 resolution; + +out vec4 fragmentColor; + +void main() { + createWorld(); + + vec2 pixelPosition = gl_FragCoord.xy + vec2(0.5); + vec2 position = pixelPosition / resolution * viewBoxSize + cameraPosition; + + fragmentColor = vec4(vec3(1.0) * clamp(0.0, 1.0, getDistance(position)), 1.0); +} diff --git a/frontend/src/shaders/dist.frag b/frontend/src/shaders/dist.frag index efc382b..30d8842 100644 --- a/frontend/src/shaders/dist.frag +++ b/frontend/src/shaders/dist.frag @@ -22,53 +22,15 @@ struct Line { bool isLineEnd; }[16] lines; -float noise(float x){ - return fract(sin(x) * 43758.5453123); -} - -float terrain(float x) { - float result = 0.0; - - float frequency = 0.01; - float amplitude = 1.0; - - for (int i = 0; i < 8; i++) { - result += sin(2.0 * pi * x * frequency - 2.0 * pi * noise(float(i) * 200.0)) * amplitude; - frequency *= 1.5; - amplitude /= 1.2; - } - - return result; -} - -vec2 random2(vec2 st){ - st = vec2( dot(st,vec2(127.1,311.7)), - dot(st,vec2(269.5,183.3)) ); - return -1.0 + 2.0*fract(sin(st)*43758.5453123); -} - -float noise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - - vec2 u = f*f*(3.0-2.0*f); - - return mix( mix( dot( random2(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ), - dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x), - mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ), - dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y); -} - float lineDistance(in vec2 position, in Line line, out float h) { vec2 pa = position - line.a, ba = line.b - line.a; h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); vec2 delta = pa - ba*h; - // sign can return 0, double sign prevents this + // sign can return 0, double sign prevents this float side = sign(sign(dot(delta, line.normal)) - 0.5); - return length(delta) * side + terrain(length(ba * h)); + return length(delta) * side; } - Line endDummyLineFromLine(Line line) { return Line(line.b, line.b + rotate90deg(line.normal), line.normal, false); } @@ -140,7 +102,6 @@ void createWorld() { } } - uniform vec2 cameraPosition; uniform vec2 viewBoxSize; uniform vec2 resolution; @@ -152,6 +113,6 @@ void main() { vec2 pixelPosition = gl_FragCoord.xy + vec2(0.5); vec2 position = pixelPosition / resolution * viewBoxSize + cameraPosition; - + //fragmentColor = getDistance(position) > 0.0 ? vec4(0.0, 0.0, 0.0, 0.0) : vec4(0.0, 0.0, 0.0, 1.0); fragmentColor = vec4(vec3(1.0) * clamp(0.0, 1.0, getDistance(position)), 1.0); } diff --git a/frontend/src/styles/main.scss b/frontend/src/styles/main.scss index aad4d1b..5c72e86 100644 --- a/frontend/src/styles/main.scss +++ b/frontend/src/styles/main.scss @@ -1,39 +1,33 @@ html, body, -main { +canvas { height: 100%; + width: 100%; } body { + position: relative; margin: 0; - background-color: #333; - main { - position: relative; + #overlay { + font-family: Helvetica, Arial, sans-serif; + font-size: 1em; + margin: 0.75em 1em; + user-select: none; + pointer-events: none; + position: absolute; + right: 0; + $outline-width: 0.5px; + text-shadow: -$outline-width -$outline-width 0 #000, + $outline-width -$outline-width 0 #000, + -$outline-width $outline-width 0 #000, + $outline-width $outline-width 0 #000; + color: white; + white-space: pre; + } - .canvas-container { - position: absolute; - top: 50%; - transform: translateY(-50%); - width: 100%; - - #overlay { - font-family: Helvetica, Arial, sans-serif; - font-size: 1em; - padding: 0.5em 1em; - user-select: none; - pointer-events: none; - position: absolute; - right: 0; - -webkit-text-stroke: 1px black; - -webkit-text-fill-color: white; - white-space: pre; - } - - canvas#main { - width: 100%; - background-color: hotpink; - } - } + canvas#main { + width: 100%; + background-color: hotpink; } } diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index bda4e9e..171c4b6 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -19,10 +19,27 @@ module.exports = { disableHostCheck: true, }, optimization: { + minimize: true, minimizer: [ - /*new TerserJSPlugin({ + new TerserJSPlugin({ sourceMap: !isProduction, - }),*/ + cache: true, + terserOptions: { + ecma: 5, + warnings: false, + parse: {}, + compress: {}, + mangle: true, + module: false, + output: null, + toplevel: false, + nameCache: null, + ie8: false, + keep_classnames: true, + keep_fnames: true, + safari10: false, + }, + }), new OptimizeCSSAssetsPlugin({}), ], },