From 5ecaf14b3de10cb6c0ad751f8df384ae4b4cf386 Mon Sep 17 00:00:00 2001 From: Akash Shakdwipeea Date: Sun, 22 Dec 2024 20:01:51 +0530 Subject: [PATCH] a rotating cube (#3) --- core/connection/mainThread.ts | 72 ++++++++++--- core/connection/workerThread.ts | 24 +++-- core/gl/index.ts | 156 ++++++++++++++++++++++++++++ core/gl/vertices.ts | 22 ++++ index.html | 5 +- package.json | 4 + public/shaders/triangle/frag.glsl | 13 +++ public/shaders/triangle/vertex.glsl | 18 ++++ src/main.ts | 7 +- yarn.lock | 34 +++--- 10 files changed, 312 insertions(+), 43 deletions(-) create mode 100644 core/gl/index.ts create mode 100644 core/gl/vertices.ts create mode 100644 public/shaders/triangle/frag.glsl create mode 100644 public/shaders/triangle/vertex.glsl diff --git a/core/connection/mainThread.ts b/core/connection/mainThread.ts index 86f5717..988fcfd 100644 --- a/core/connection/mainThread.ts +++ b/core/connection/mainThread.ts @@ -12,7 +12,7 @@ const EventTypes = { keydown: 3, keyup: 4, onWheel: 5, -} as const +} as const; type EventTypesI = typeof EventTypes; /** @@ -22,13 +22,28 @@ type EventTypesI = typeof EventTypes; const connectWorker = (worker: Worker) => { const eventPassers = { [EventTypes.pointermove]: (e: PointerEvent) => { - worker.postMessage([MessageType.domEvent, EventTypes.pointermove, e.clientX, e.clientY]); + worker.postMessage([ + MessageType.domEvent, + EventTypes.pointermove, + e.clientX, + e.clientY, + ]); }, [EventTypes.pointerup]: (e: PointerEvent) => { - worker.postMessage([MessageType.domEvent, EventTypes.pointerup, e.clientX, e.clientY]); + worker.postMessage([ + MessageType.domEvent, + EventTypes.pointerup, + e.clientX, + e.clientY, + ]); }, [EventTypes.pointerdown]: (e: PointerEvent) => { - worker.postMessage([MessageType.domEvent, EventTypes.pointerdown, e.clientX, e.clientY]); + worker.postMessage([ + MessageType.domEvent, + EventTypes.pointerdown, + e.clientX, + e.clientY, + ]); }, [EventTypes.keydown]: (e: KeyboardEvent) => { worker.postMessage([MessageType.domEvent, EventTypes.keydown, e.key]); @@ -41,33 +56,58 @@ const connectWorker = (worker: Worker) => { }, } as const; - document.addEventListener("pointermove", eventPassers[EventTypes.pointermove]); - document.addEventListener("pointerdown", eventPassers[EventTypes.pointerdown]); + document.addEventListener( + "pointermove", + eventPassers[EventTypes.pointermove] + ); + document.addEventListener( + "pointerdown", + eventPassers[EventTypes.pointerdown] + ); document.addEventListener("pointerup", eventPassers[EventTypes.pointerup]); - // + // document.addEventListener("keydown", eventPassers[EventTypes.keydown]); document.addEventListener("keyup", eventPassers[EventTypes.keyup]); const disconnectWorker = () => { - document.removeEventListener("pointermove", eventPassers[EventTypes.pointermove]); - document.removeEventListener("pointerdown", eventPassers[EventTypes.pointerdown]); - document.removeEventListener("pointerup", eventPassers[EventTypes.pointerup]); - // + document.removeEventListener( + "pointermove", + eventPassers[EventTypes.pointermove] + ); + document.removeEventListener( + "pointerdown", + eventPassers[EventTypes.pointerdown] + ); + document.removeEventListener( + "pointerup", + eventPassers[EventTypes.pointerup] + ); + // document.removeEventListener("keydown", eventPassers[EventTypes.keydown]); document.removeEventListener("keyup", eventPassers[EventTypes.keyup]); - } + }; return disconnectWorker; -} +}; const attachCanvas = (worker: Worker, canvas: HTMLCanvasElement | string) => { - if (typeof canvas === "string") canvas = document.getElementById("rendering-canvas") as HTMLCanvasElement; + if (typeof canvas === "string") + canvas = document.getElementById("rendering-canvas") as HTMLCanvasElement; if (!canvas) { throw new Error("Fatal: Canvas Not Found!"); } + + // set canvas dimensions + // if this is not done, the canvas passed to the web worker + // will have the default size of 300x150 + // this will cause the viewport to be set to the default size + // and eveything will be messed up + canvas.width = canvas.clientWidth; + canvas.height = canvas.clientHeight; + const canvasWorker = canvas.transferControlToOffscreen(); worker.postMessage([MessageType.attachCanvas, canvasWorker], [canvasWorker]); -} +}; export type { EventTypesI, MessageTypeI }; -export { EventTypes, connectWorker, attachCanvas, MessageType } +export { EventTypes, connectWorker, attachCanvas, MessageType }; diff --git a/core/connection/workerThread.ts b/core/connection/workerThread.ts index f08ed0e..a16e876 100644 --- a/core/connection/workerThread.ts +++ b/core/connection/workerThread.ts @@ -1,3 +1,5 @@ +import { Attribute, draw, ProgramData } from "../gl"; +import { cubeVertices } from "../gl/vertices"; import { MessageType } from "./mainThread"; const handleConnection = (msg: MessageEvent) => { @@ -9,17 +11,25 @@ const handleConnection = (msg: MessageEvent) => { switch (data[0]) { case MessageType.attachCanvas: const canvas = data[1]; - const ctxWorker = canvas.getContext("2d"); - ctxWorker.clearRect(0, 0, canvas.width, canvas.height); - ctxWorker.font = "24px Verdana"; - ctxWorker.textAlign = "center"; - ctxWorker.fillText("Hello World", canvas.width / 2, canvas.height / 2); + const ctxWorker = canvas.getContext("webgl2"); + var p: ProgramData> = { + vertexShaderSource: "/shaders/triangle/vertex.glsl", + fragmentShaderSource: "/shaders/triangle/frag.glsl", + attributes: [ + { + name: "position", + data: new Float32Array(cubeVertices), + }, + ], + }; + + requestAnimationFrame(() => draw(ctxWorker, p)); break; } console.log(...data); -} +}; -export { handleConnection }; +export { handleConnection }; diff --git a/core/gl/index.ts b/core/gl/index.ts new file mode 100644 index 0000000..0fc7068 --- /dev/null +++ b/core/gl/index.ts @@ -0,0 +1,156 @@ +import { mat4 } from "gl-matrix"; +import { cubeVertices } from "./vertices"; + +type AttributeDataTypes = Float32Array | Int32Array; + +export interface Attribute { + name: string; + data: T; +} + +export interface Dimensions { + width: number; + height: number; +} + +export interface ProgramData> { + vertexShaderSource: string; + fragmentShaderSource: string; + attributes: T[]; +} + +async function fetchShader(path: string) { + const response = await fetch(path); + return response.text(); +} + +async function createShader( + gl: WebGL2RenderingContext, + type: number, + sourceFile: string +) { + const source = await fetchShader(sourceFile); + + var shader = gl.createShader(type); + if (!shader) return; + + gl.shaderSource(shader, source); + gl.compileShader(shader); + var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (!success) { + console.error(gl.getShaderInfoLog(shader)); // eslint-disable-line + gl.deleteShader(shader); + } + + return shader; +} + +async function setupProgram>( + gl: WebGL2RenderingContext, + programData: ProgramData +) { + var program = gl.createProgram(); + if (!program) return; + + var vertexShader = await createShader( + gl, + gl.VERTEX_SHADER, + programData.vertexShaderSource + ); + if (!vertexShader) return; + + var fragmentShader = await createShader( + gl, + gl.FRAGMENT_SHADER, + programData.fragmentShaderSource + ); + if (!fragmentShader) return; + + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + + var success = gl.getProgramParameter(program, gl.LINK_STATUS); + if (!success) { + console.error(gl.getProgramInfoLog(program)); + gl.deleteProgram(program); + } + + return program; +} + +function setupAttribute>( + gl: WebGL2RenderingContext, + program: WebGLProgram, + attribute: T +) { + const location = gl.getAttribLocation(program, attribute.name); + if (location === -1) return; + + // setup a buffer for the attribute data + const buffer = gl.createBuffer(); + if (!buffer) return; + + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, attribute.data, gl.STATIC_DRAW); + + const vao = gl.createVertexArray(); + if (!vao) return; + + gl.bindVertexArray(vao); + + gl.enableVertexAttribArray(location); + gl.vertexAttribPointer(location, 3, gl.FLOAT, false, 5 * 4, 0); +} + +export async function draw>( + gl: WebGL2RenderingContext, + programData: ProgramData +) { + gl.enable(gl.DEPTH_TEST); + + const program = await setupProgram(gl, programData); + if (!program) { + console.error("failed to setup program"); + return; + } + + gl.useProgram(program); + + for (const attribute of programData.attributes) { + setupAttribute(gl, program, attribute); + } + + let view = mat4.create(); + view = mat4.translate(view, view, [0, 0, -5]); + + let model = mat4.create(); + const angle = Date.now() * 0.001; + model = mat4.rotate(model, model, angle, [0.5, 1, 0]); + + const projection = mat4.create(); + mat4.perspective( + projection, + 45, + gl.canvas.width / gl.canvas.height, + 0.1, + 100.0 + ); + + const viewLoc = gl.getUniformLocation(program, "view"); + const modelLoc = gl.getUniformLocation(program, "model"); + const projectionLoc = gl.getUniformLocation(program, "projection"); + + gl.uniformMatrix4fv(viewLoc, false, view); + gl.uniformMatrix4fv(modelLoc, false, model); + gl.uniformMatrix4fv(projectionLoc, false, projection); + + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + gl.drawArrays(gl.TRIANGLES, 0, 36); + + requestAnimationFrame(() => draw(gl, programData)); +} diff --git a/core/gl/vertices.ts b/core/gl/vertices.ts new file mode 100644 index 0000000..419257f --- /dev/null +++ b/core/gl/vertices.ts @@ -0,0 +1,22 @@ +export const cubeVertices = [ + -0.5, -0.5, -0.5, 0.0, 0.0, 0.5, -0.5, -0.5, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0, + 1.0, 0.5, 0.5, -0.5, 1.0, 1.0, -0.5, 0.5, -0.5, 0.0, 1.0, -0.5, -0.5, -0.5, + 0.0, 0.0, + + -0.5, -0.5, 0.5, 0.0, 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, 0.5, 0.5, 0.5, 1.0, 1.0, + 0.5, 0.5, 0.5, 1.0, 1.0, -0.5, 0.5, 0.5, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, + + -0.5, 0.5, 0.5, 1.0, 0.0, -0.5, 0.5, -0.5, 1.0, 1.0, -0.5, -0.5, -0.5, 0.0, + 1.0, -0.5, -0.5, -0.5, 0.0, 1.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, 0.5, 0.5, + 1.0, 0.0, + + 0.5, 0.5, 0.5, 1.0, 0.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, -0.5, -0.5, 0.0, 1.0, + 0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, 0.5, 0.0, 0.0, 0.5, 0.5, 0.5, 1.0, 0.0, + + -0.5, -0.5, -0.5, 0.0, 1.0, 0.5, -0.5, -0.5, 1.0, 1.0, 0.5, -0.5, 0.5, 1.0, + 0.0, 0.5, -0.5, 0.5, 1.0, 0.0, -0.5, -0.5, 0.5, 0.0, 0.0, -0.5, -0.5, -0.5, + 0.0, 1.0, + + -0.5, 0.5, -0.5, 0.0, 1.0, 0.5, 0.5, -0.5, 1.0, 1.0, 0.5, 0.5, 0.5, 1.0, 0.0, + 0.5, 0.5, 0.5, 1.0, 0.0, -0.5, 0.5, 0.5, 0.0, 0.0, -0.5, 0.5, -0.5, 0.0, 1.0, +]; diff --git a/index.html b/index.html index 44a9335..51baa26 100644 --- a/index.html +++ b/index.html @@ -1,4 +1,4 @@ - + @@ -7,7 +7,8 @@ Vite + TS -
+
+ diff --git a/package.json b/package.json index 9a2ef32..c01ef3e 100644 --- a/package.json +++ b/package.json @@ -11,5 +11,9 @@ "devDependencies": { "typescript": "^5.2.2", "vite": "^5.3.4" + }, + "dependencies": { + "gl-matrix": "^3.4.3", + "tslog": "^4.9.3" } } diff --git a/public/shaders/triangle/frag.glsl b/public/shaders/triangle/frag.glsl new file mode 100644 index 0000000..92d1c10 --- /dev/null +++ b/public/shaders/triangle/frag.glsl @@ -0,0 +1,13 @@ +#version 300 es + +// fragment shaders don't have a default precision so we need +// to pick one. highp is a good default. It means "high precision" +precision highp float; + +// we need to declare an output for the fragment shader +out vec4 outColor; + +void main() { + // Just set the output to a constant redish-purple + outColor = vec4(1, 0, 0.5, 1); +} \ No newline at end of file diff --git a/public/shaders/triangle/vertex.glsl b/public/shaders/triangle/vertex.glsl new file mode 100644 index 0000000..1f17f24 --- /dev/null +++ b/public/shaders/triangle/vertex.glsl @@ -0,0 +1,18 @@ +#version 300 es + +// an attribute is an input (in) to a vertex shader. +// It will receive data from a buffer +in vec3 position; + +// uniforms +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +// all shaders have a main function +void main() { + + // gl_Position is a special variable a vertex shader + // is responsible for setting + gl_Position = projection * view * model * vec4(position, 1.0); +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index a0f00d8..457909d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,7 @@ -import './style.css' +import "./style.css"; import { attachCanvas, connectWorker } from "../core/connection/mainThread.ts"; import MainSceneWorker from "../examples/starterScene?worker"; -document.querySelector('#app')!.innerHTML = ` - - -` - const mainSceneWorker = new MainSceneWorker(); connectWorker(mainSceneWorker); attachCanvas(mainSceneWorker, "rendering-canvas"); diff --git a/yarn.lock b/yarn.lock index 24de839..9a40074 100644 --- a/yarn.lock +++ b/yarn.lock @@ -84,7 +84,7 @@ "@esbuild/linux-x64@0.21.5": version "0.21.5" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== "@esbuild/netbsd-x64@0.21.5": @@ -174,12 +174,12 @@ "@rollup/rollup-linux-x64-gnu@4.19.2": version "4.19.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.2.tgz#6a275282a0080fee98ddd9fda0de23c4c6bafd48" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.2.tgz" integrity sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ== "@rollup/rollup-linux-x64-musl@4.19.2": version "4.19.2" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.2.tgz#64f0c704107e6b45b26dd8c2e1ff64246e4a1251" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.2.tgz" integrity sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg== "@rollup/rollup-win32-arm64-msvc@4.19.2": @@ -199,12 +199,12 @@ "@types/estree@1.0.5": version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== esbuild@^0.21.3: version "0.21.5" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz" integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== optionalDependencies: "@esbuild/aix-ppc64" "0.21.5" @@ -236,19 +236,24 @@ fsevents@~2.3.2, fsevents@~2.3.3: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +gl-matrix@^3.4.3: + version "3.4.3" + resolved "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz" + integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== + nanoid@^3.3.7: version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== picocolors@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== postcss@^8.4.39: version "8.4.40" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz" integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== dependencies: nanoid "^3.3.7" @@ -257,7 +262,7 @@ postcss@^8.4.39: rollup@^4.13.0: version "4.19.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.19.2.tgz#4985cd2028965157e8d674a70e49f33aca9038eb" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.19.2.tgz" integrity sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ== dependencies: "@types/estree" "1.0.5" @@ -282,17 +287,22 @@ rollup@^4.13.0: source-map-js@^1.2.0: version "1.2.0" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== +tslog@^4.9.3: + version "4.9.3" + resolved "https://registry.npmjs.org/tslog/-/tslog-4.9.3.tgz" + integrity sha512-oDWuGVONxhVEBtschLf2cs/Jy8i7h1T+CpdkTNWQgdAF7DhRo2G8vMCgILKe7ojdEkLhICWgI1LYSSKaJsRgcw== + typescript@^5.2.2: version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== vite@^5.3.4: version "5.3.5" - resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8" + resolved "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz" integrity sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA== dependencies: esbuild "^0.21.3"