From 767397d26db548da904d67155f77a287fa866a40 Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Tue, 14 Jul 2020 21:50:35 +0200 Subject: [PATCH] Removed custom WebGL and WebGPU renderers. All 3D rendering is now done by THREE.js again. --- assets/shaders/pos_norm.frag.spv | Bin 1620 -> 0 bytes assets/shaders/pos_norm.vert.spv | Bin 1652 -> 0 bytes assets/shaders/pos_tex.frag.spv | Bin 920 -> 0 bytes assets/shaders/pos_tex.vert.spv | Bin 1480 -> 0 bytes .../resources/shaders/pos_norm.frag | 23 - .../resources/shaders/pos_norm.vert | 16 - .../resources/shaders/pos_tex.frag | 15 - .../resources/shaders/pos_tex.vert | 15 - assets_generation/update_shaders.ts | 36 -- package.json | 4 - src/application/index.test.ts | 4 +- src/application/index.ts | 2 +- src/core/math/linear_algebra.ts | 446 ------------------ src/core/math/quaternions.test.ts | 31 -- src/core/math/quaternions.ts | 57 --- src/core/rendering/Camera.ts | 168 ------- src/core/rendering/Gfx.ts | 22 - src/core/rendering/GfxRenderer.ts | 139 ------ src/core/rendering/Mesh.ts | 54 --- src/core/rendering/MeshBuilder.ts | 106 ----- src/core/rendering/Renderer.ts | 139 +++++- src/core/rendering/Scene.ts | 38 -- src/core/rendering/ShaderProgram.ts | 109 ----- src/core/rendering/Texture.ts | 33 -- src/core/rendering/ThreeRenderer.ts | 141 ------ src/core/rendering/VertexFormat.ts | 41 -- src/core/rendering/conversion/index.ts | 5 - .../rendering/conversion/ninja_geometry.ts | 257 +++++++--- .../rendering/conversion/ninja_textures.ts | 23 - .../conversion/ninja_three_geometry.ts | 312 ------------ src/core/rendering/meshes.ts | 58 --- src/core/rendering/webgl/WebglGfx.ts | 160 ------- src/core/rendering/webgl/WebglRenderer.ts | 135 ------ src/core/rendering/webgl/pos_norm.frag | 23 - src/core/rendering/webgl/pos_norm.vert | 17 - src/core/rendering/webgl/pos_tex.frag | 13 - src/core/rendering/webgl/pos_tex.vert | 16 - src/core/rendering/webgl/shader_sources.ts | 8 - src/core/rendering/webgpu/ShaderLoader.ts | 9 - src/core/rendering/webgpu/WebgpuGfx.ts | 187 -------- src/core/rendering/webgpu/WebgpuRenderer.ts | 326 ------------- src/index.ts | 2 +- .../gui/QuestEditorRendererView.ts | 2 +- .../gui/QuestRunnerRendererView.ts | 2 +- src/quest_editor/index.ts | 2 +- src/quest_editor/loading/EntityAssetLoader.ts | 2 +- .../rendering/EntityImageRenderer.ts | 2 +- src/quest_editor/rendering/QuestRenderer.ts | 4 +- .../rendering/conversion/areas.ts | 2 +- src/viewer/gui/model/ModelView.test.ts | 4 +- src/viewer/gui/texture/TextureView.test.ts | 6 +- .../__snapshots__/TextureView.test.ts.snap | 4 +- src/viewer/index.ts | 48 +- src/viewer/rendering/ModelGfxRenderer.ts | 88 ---- src/viewer/rendering/ModelRenderer.ts | 6 +- src/viewer/rendering/TextureRenderer.ts | 126 ++--- test/src/core/rendering/StubGfxRenderer.ts | 15 - .../{StubThreeRenderer.ts => StubRenderer.ts} | 4 +- webpack.common.js | 4 - yarn.lock | 18 - 60 files changed, 422 insertions(+), 3107 deletions(-) delete mode 100644 assets/shaders/pos_norm.frag.spv delete mode 100644 assets/shaders/pos_norm.vert.spv delete mode 100644 assets/shaders/pos_tex.frag.spv delete mode 100644 assets/shaders/pos_tex.vert.spv delete mode 100644 assets_generation/resources/shaders/pos_norm.frag delete mode 100644 assets_generation/resources/shaders/pos_norm.vert delete mode 100644 assets_generation/resources/shaders/pos_tex.frag delete mode 100644 assets_generation/resources/shaders/pos_tex.vert delete mode 100644 assets_generation/update_shaders.ts delete mode 100644 src/core/math/linear_algebra.ts delete mode 100644 src/core/math/quaternions.test.ts delete mode 100644 src/core/math/quaternions.ts delete mode 100644 src/core/rendering/Camera.ts delete mode 100644 src/core/rendering/Gfx.ts delete mode 100644 src/core/rendering/GfxRenderer.ts delete mode 100644 src/core/rendering/Mesh.ts delete mode 100644 src/core/rendering/MeshBuilder.ts delete mode 100644 src/core/rendering/Scene.ts delete mode 100644 src/core/rendering/ShaderProgram.ts delete mode 100644 src/core/rendering/Texture.ts delete mode 100644 src/core/rendering/ThreeRenderer.ts delete mode 100644 src/core/rendering/VertexFormat.ts delete mode 100644 src/core/rendering/conversion/ninja_three_geometry.ts delete mode 100644 src/core/rendering/meshes.ts delete mode 100644 src/core/rendering/webgl/WebglGfx.ts delete mode 100644 src/core/rendering/webgl/WebglRenderer.ts delete mode 100644 src/core/rendering/webgl/pos_norm.frag delete mode 100644 src/core/rendering/webgl/pos_norm.vert delete mode 100644 src/core/rendering/webgl/pos_tex.frag delete mode 100644 src/core/rendering/webgl/pos_tex.vert delete mode 100644 src/core/rendering/webgl/shader_sources.ts delete mode 100644 src/core/rendering/webgpu/ShaderLoader.ts delete mode 100644 src/core/rendering/webgpu/WebgpuGfx.ts delete mode 100644 src/core/rendering/webgpu/WebgpuRenderer.ts delete mode 100644 src/viewer/rendering/ModelGfxRenderer.ts delete mode 100644 test/src/core/rendering/StubGfxRenderer.ts rename test/src/core/rendering/{StubThreeRenderer.ts => StubRenderer.ts} (75%) diff --git a/assets/shaders/pos_norm.frag.spv b/assets/shaders/pos_norm.frag.spv deleted file mode 100644 index 3eebdf4bb8503a64e23019651548d9b0c2fcc139..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1620 zcmZ{kTTc@~6vs!}q88+05%0QK6qUA=M2*p4-{1u!i65X@+qG#}cFDFS@l9j=27VtC zpW*X(Nlg6zW_QSFbegmCKesuTIn&Zq{j4!(%sKs9CSQwYN)lsc%(Rd(JG-xUTcdHm z_2_XMi#bzbvo`bcD+^ad2jMV5J}bH@s*4t6i%vmACF>(7v}(`(szAir*a%)7zD)a* zINHn7UNjm-{h${QqhuT$P2&A9*=)C~|Fjy1*&rG>qvXh0w*F(O!?JhH!*r-aNw!*# zhe;G{R>{BoM-A}jl~*qv*_tcTQrFuo9CVX3I|yS*O6H&f&IMJtR}a+@8Jg?UiWar!%Yt_c=(=&?-sbG zsbTt7^kweA%la~3fu*u0(#@OEos^BP*Xpqi#8VS9hx~cL(`RJs;yG(h^z5Ye#qskw z-)ZPx@gq-ckVDR{Cal$Q{1wZ-?<^_r>%zppp`BRp#E0j-AS2%u?c_jx*x%8f*PPqK z*9)18vnu}N_;{-!PyFc@GDqa$bo3S8rze_AV(?a3#Qv_Fr?rzKJ!MA8%kj*d&ofi- z{3B2c{b6m1r)SJAeg3Q{*fCeg=;aGV=X_+&pT4s2$DX;y=2PL%Y}0S#9|{>Wjtm`V zk|Q#ArkgK!CbN&t$HJfar!UCyrzT|Bb6?CnclAxWKs)QFbjWXu9*XE2*u^Yn;4>?fs<8W=hm2|AK$X zU*(I5>pPuOYodqjp0n0o`|QgZW~S>EW2VfU{#s_VR?M^*V-`$VCu7=2$4AX9KW#pE zxQ}AV%&=L_vbkW-CH?mOFa|8iuFG!88nTM?+{cBH&sr@7>^SaypEHPMVwq zSr(jnCs7#0xpzK{KKb#%{{H+wRk@$`f_yKC&z-XQAEho!zw8Z?P?u8Vd_4-|z&n^X zretPie`+tO=SAUO)a?Z6#~{sv&w^q{JZF4K7Up3Ri>+8ay;xp3j>E43I_G1C^1X_} z!Rs)~{rDsp`vxYu~Ma=1>eXVJ(cdF-JZ)cBi9XIC^s&XSi8e zTORI1cPNJbSiA#zhwGO8o72Z?E?M0uj=Ym`VB%iL++A=kj@;Z&?zVC>n;jX;;o1mV z%$%wdm|0EYMuEw(s$1!^reFHxyslqzd$J9&bK1f9*YwMOk0)a_3aro4&T~tcnW2ZP z>W!J3y8|EQNq12@`S3p}Fy9ayd6u<%vMCvPz|4`gDZHYcd#Q^$FKZ_+@%UG@!-1>9 z%#av(@~>%UrtstkGg~jR$bCa*%(v(0>pGE}8o@UU?Bc<^ z4I02FVl27Xb~f@{jC`;81gxp^1( JU+VI)>^D}bhA;pC diff --git a/assets/shaders/pos_tex.frag.spv b/assets/shaders/pos_tex.frag.spv deleted file mode 100644 index d63fd7e51524c14f6a5bcf48a579d93ea8b7e07d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 920 zcmZ{iUvCmo5XDDufr8Rfv5cBHS`-;DIKdQV%D;nJhSP|=bKSpEJhcXV;F6#Vm#;2 z>h@TEAiay03Gt@1Cp{96xEe^So-bG48(;UAn$YL`Jp8!&kS^A-`;?_~m*;K~&f}#^ zig2@zzemYrJZ}DzRYcjXD~2xF?3knfFjXx3Ib5Yn6=l%oS-ebKIMJ|yZm>UpAjBQg zbupW#ahk~wH1$*2!u^mRSf})7d33jmU8YFQ-}ze>-OfZ(Vwx>-n<|f5$W(psUwKqw zYQFvpp9=fEE0;b9x%m5BFkE&>pJQ1BpZz1`;s5DbwHN-RoPQ+;_){1bh7 zyHMIwv>^-TJk<9<79R2IE#`j2`Z{gN!e_pH*I#$ o$KILci)t~)GbtS6aOlg<>G@S0_Ql@e5r41w*&DHYb$ly*0R8b(vj6}9 diff --git a/assets/shaders/pos_tex.vert.spv b/assets/shaders/pos_tex.vert.spv deleted file mode 100644 index 745379058ec73877e8674bc02b2252d4f3445817..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1480 zcmZ{kUuzRV6vd}aH;JjX*4jU6jT=+7Dos;R5ky54(uazXqOV~|wqbFz8+JEAeex^# zmHbq`2%g_&Cq~c-cki5Y&)k`NXGm$eSutkGRP}pcW@E!Fi!o-^lr=M^b9#2#O0z-h z(c?CXHB-Xnnssv_pF8@FydVOs$*#-p$_``|>B+|^nf*NH4-s%jNAA1v+juYu{caNX z{WSFlZa)nCD08Qi@RJuEx7)RUsxmJb`q`l$O|7!^AElBdUk=A{prjnK)(nHlcaLkv zIA%rm=j@JZz9>8ldtE>I=qH)~Sy1eZXHG0mgDi+6v6Wm;Ejh0}&VsK#Ivb;hioFhl z@tYvcyr}QbV|0?(Bn;;hxU}tj^q=qfR)y)lnZDwOEch+={HD2>H;R zh@l<^eMfI`GiU$i^f=9B89C79?@>9Z^|p-BDR5&3b4=a!1Ozr65>gql9+Z@C&SGr5`>__}rfw_A) z_F0#AWlJ*l0n-o0uJDFDc{x{ItLPCNxi{sh502bm`UXdQtC*vw#$9>N%-+PW$+Is! z>%sIDjL<$(M!fFsi6&$s7 zwUU}$8SBv#r#Ih~msmr#BXeb(k6txp+%@NWq3XcsY(4frfV0w*`!f20mcD_1=~Pc; FzX1VLf3pAp diff --git a/assets_generation/resources/shaders/pos_norm.frag b/assets_generation/resources/shaders/pos_norm.frag deleted file mode 100644 index 18f01629..00000000 --- a/assets_generation/resources/shaders/pos_norm.frag +++ /dev/null @@ -1,23 +0,0 @@ -#version 450 - -precision mediump float; - -const vec3 light_pos = normalize(vec3(-1, 1, 1)); -const vec4 sky_color = vec4(1, 1, 1, 1); -const vec4 ground_color = vec4(0.1, 0.1, 0.1, 1); - -layout(location = 0) in vec3 frag_normal; - -layout(location = 0) out vec4 out_color; - -void main() { - float cos0 = dot(frag_normal, light_pos); - float a = 0.5 + 0.5 * cos0; - float a_back = 1.0 - a; - - if (gl_FrontFacing) { - out_color = mix(ground_color, sky_color, a); - } else { - out_color = mix(ground_color, sky_color, a_back); - } -} diff --git a/assets_generation/resources/shaders/pos_norm.vert b/assets_generation/resources/shaders/pos_norm.vert deleted file mode 100644 index 9f6267b5..00000000 --- a/assets_generation/resources/shaders/pos_norm.vert +++ /dev/null @@ -1,16 +0,0 @@ -#version 450 - -layout(set = 0, binding = 0) uniform Uniforms { - mat4 mvp_mat; - mat3 normal_mat; -} uniforms; - -layout(location = 0) in vec3 pos; -layout(location = 1) in vec3 normal; - -layout(location = 0) out vec3 frag_normal; - -void main() { - gl_Position = uniforms.mvp_mat * vec4(pos, 1.0); - frag_normal = normalize(uniforms.normal_mat * normal); -} diff --git a/assets_generation/resources/shaders/pos_tex.frag b/assets_generation/resources/shaders/pos_tex.frag deleted file mode 100644 index 333bfb46..00000000 --- a/assets_generation/resources/shaders/pos_tex.frag +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 - -precision mediump float; -precision mediump sampler; - -layout(set = 0, binding = 1) uniform sampler tex_sampler; -layout(set = 0, binding = 2) uniform texture2D tex; - -layout(location = 0) in vec2 frag_tex_coords; - -layout(location = 0) out vec4 out_color; - -void main() { - out_color = texture(sampler2D(tex, tex_sampler), frag_tex_coords); -} diff --git a/assets_generation/resources/shaders/pos_tex.vert b/assets_generation/resources/shaders/pos_tex.vert deleted file mode 100644 index a765a3bf..00000000 --- a/assets_generation/resources/shaders/pos_tex.vert +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 - -layout(set = 0, binding = 0) uniform Uniforms { - mat4 mvp_mat; -} uniforms; - -layout(location = 0) in vec3 pos; -layout(location = 2) in vec2 tex_coords; - -layout(location = 0) out vec2 frag_tex_coords; - -void main() { - gl_Position = uniforms.mvp_mat * vec4(pos, 1.0); - frag_tex_coords = tex_coords; -} diff --git a/assets_generation/update_shaders.ts b/assets_generation/update_shaders.ts deleted file mode 100644 index 1d8cd64a..00000000 --- a/assets_generation/update_shaders.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable no-console */ -import glsl_module, { Glslang, ShaderStage } from "@webgpu/glslang"; -import * as fs from "fs"; -import { RESOURCE_DIR, ASSETS_DIR } from "./index"; -const glsl = (glsl_module() as any) as Glslang; - -const SHADER_RESOURCES_DIR = `${RESOURCE_DIR}/shaders`; -const SHADER_ASSETS_DIR = `${ASSETS_DIR}/shaders`; - -function compile_shader(source_file: string, shader_stage: ShaderStage): void { - const source = fs.readFileSync(`${SHADER_RESOURCES_DIR}/${source_file}`, "utf8"); - const spir_v = glsl.compileGLSL(source, shader_stage, true); - fs.writeFileSync( - `${SHADER_ASSETS_DIR}/${source_file}.spv`, - new Uint8Array(spir_v.buffer, spir_v.byteOffset, spir_v.byteLength), - ); -} - -for (const file of fs.readdirSync(SHADER_RESOURCES_DIR)) { - console.info(`Compiling ${file}.`); - - let shader_stage: ShaderStage; - - switch (file.slice(-4)) { - case "vert": - shader_stage = "vertex"; - break; - case "frag": - shader_stage = "fragment"; - break; - default: - throw new Error(`Unsupported shader type: ${file.slice(-4)}`); - } - - compile_shader(file, shader_stage); -} diff --git a/package.json b/package.json index 05931722..fa35f155 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,6 @@ "test": "jest", "update_generic_data": "ts-node --project=tsconfig-scripts.json assets_generation/update_generic_data.ts", "update_ephinea_data": "ts-node --project=tsconfig-scripts.json assets_generation/update_ephinea_data.ts", - "update_shaders": "ts-node --project=tsconfig-scripts.json assets_generation/update_shaders.ts", "lint": "prettier --check \"{src,assets_generation,test}/**/*.{ts,tsx}\" && echo Linting... && eslint \"{src,assets_generation,test}/**/*.{ts,tsx}\" && echo All code passes the prettier and eslint checks." }, "devDependencies": { @@ -38,8 +37,6 @@ "@types/node-fetch": "^2.5.7", "@typescript-eslint/eslint-plugin": "^3.6.1", "@typescript-eslint/parser": "^3.6.1", - "@webgpu/glslang": "^0.0.15", - "@webgpu/types": "^0.0.27", "cheerio": "^1.0.0-rc.3", "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "^6.0.3", @@ -58,7 +55,6 @@ "node-fetch": "^2.6.0", "optimize-css-assets-webpack-plugin": "^5.0.3", "prettier": "^2.0.5", - "raw-loader": "^4.0.1", "terser-webpack-plugin": "^2.3.7", "ts-jest": "^26.1.2", "ts-loader": "^8.0.0", diff --git a/src/application/index.test.ts b/src/application/index.test.ts index 5bb3826f..9106b03b 100644 --- a/src/application/index.test.ts +++ b/src/application/index.test.ts @@ -5,7 +5,7 @@ import { timeout } from "../../test/src/utils"; import { Random } from "../core/Random"; import { Severity } from "../core/Severity"; import { StubClock } from "../../test/src/core/StubClock"; -import { STUB_THREE_RENDERER } from "../../test/src/core/rendering/StubThreeRenderer"; +import { STUB_RENDERER } from "../../test/src/core/rendering/StubRenderer"; for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) { const with_path = path == undefined ? "without specific path" : `with path ${path}`; @@ -28,7 +28,7 @@ for (const path of [undefined, "/viewer", "/quest_editor", "/hunt_optimizer"]) { new FileSystemHttpClient(), new Random(() => 0.27), new StubClock(new Date("2020-01-01T15:40:20Z")), - () => STUB_THREE_RENDERER, + () => STUB_RENDERER, ); expect(app).toBeDefined(); diff --git a/src/application/index.ts b/src/application/index.ts index 13e09b3f..1377323c 100644 --- a/src/application/index.ts +++ b/src/application/index.ts @@ -5,7 +5,7 @@ import { create_item_type_stores } from "../core/stores/ItemTypeStore"; import { create_item_drop_stores } from "../hunt_optimizer/stores/ItemDropStore"; import { ApplicationView } from "./gui/ApplicationView"; import { throttle } from "lodash"; -import { DisposableThreeRenderer } from "../core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer } from "../core/rendering/Renderer"; import { Disposer } from "../core/observable/Disposer"; import { disposable_custom_listener, disposable_listener } from "../core/gui/dom"; import { Random } from "../core/Random"; diff --git a/src/core/math/linear_algebra.ts b/src/core/math/linear_algebra.ts deleted file mode 100644 index 23413c1c..00000000 --- a/src/core/math/linear_algebra.ts +++ /dev/null @@ -1,446 +0,0 @@ -import { assert } from "../util"; -import { Quat } from "./quaternions"; - -export class Vec2 { - constructor(public x: number, public y: number) {} - - get u(): number { - return this.x; - } - - get v(): number { - return this.y; - } -} - -export function vec2_diff(v: Vec2, w: Vec2): Vec2 { - return new Vec2(v.x - w.x, v.y - w.y); -} - -export class Vec3 { - constructor(public x: number, public y: number, public z: number) {} - - magnitude(): number { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - } - - normalize(): void { - const inv_mag = 1 / this.magnitude(); - this.x *= inv_mag; - this.y *= inv_mag; - this.z *= inv_mag; - } -} - -export function vec3_sub(v: Vec3, w: Vec3): Vec3 { - return new Vec3(v.x - w.x, v.y - w.y, v.z - w.z); -} - -/** - * Computes the distance between points `p` and `q`. Equivalent to `vec3_diff(p, q).magnitude()`. - */ -export function vec3_dist(p: Vec3, q: Vec3): number { - const x = p.x - q.x; - const y = p.y - q.y; - const z = p.z - q.z; - return Math.sqrt(x * x + y * y + z * z); -} - -/** - * Computes the cross product of `p` and `q`. - */ -export function vec3_cross(p: Vec3, q: Vec3): Vec3 { - return new Vec3(p.y * q.z - p.z * q.y, p.z * q.x - p.x * q.z, p.x * q.y - p.y * q.x); -} - -/** - * Computes the dot product of `p` and `q`. - */ -export function vec3_dot(p: Vec3, q: Vec3): number { - return p.x * q.x + p.y * q.y + p.z * q.z; -} - -/** - * Computes the cross product of `p` and `q` and stores it in `result`. - */ -export function vec3_cross_into(p: Vec3, q: Vec3, result: Vec3): void { - const x = p.y * q.z - p.z * q.y; - const y = p.z * q.x - p.x * q.z; - const z = p.x * q.y - p.y * q.x; - - result.x = x; - result.y = y; - result.z = z; -} - -/** - * Stores data in column-major order. - */ -export class Mat3 { - // prettier-ignore - static of( - m00: number, m01: number, m02: number, - m10: number, m11: number, m12: number, - m20: number, m21: number, m22: number, - ): Mat3 { - return new Mat3(new Float32Array([ - m00, m10, m20, - m01, m11, m21, - m02, m12, m22, - ])); - } - - static identity(): Mat3 { - // prettier-ignore - return Mat3.of( - 1, 0, 0, - 0, 1, 0, - 0, 0, 1, - ) - } - - constructor(readonly data: Float32Array) { - assert(data.length === 9, "data should be of length 9."); - } - - get(i: number, j: number): number { - return this.data[i + j * 3]; - } - - set(i: number, j: number, value: number): void { - this.data[i + j * 3] = value; - } - - /** - * @returns a copy of this matrix. - */ - clone(): Mat3 { - return new Mat3(new Float32Array(this.data)); - } - - /** - * Transposes this matrix in-place. - */ - transpose(): void { - let tmp: number; - const m = this.data; - - tmp = m[1]; - m[1] = m[3]; - m[3] = tmp; - - tmp = m[2]; - m[2] = m[6]; - m[6] = tmp; - - tmp = m[5]; - m[5] = m[7]; - m[7] = tmp; - } - - /** - * Computes the inverse of this matrix and returns it as a new {@link Mat3}. - * - * @returns the inverse of this matrix. - */ - inverse(): Mat3 { - const m = this.clone(); - m.invert(); - return m; - } - - /** - * Computes the inverse of this matrix in-place. Will revert to identity if this matrix is - * degenerate. - */ - invert(): void { - const n11 = this.data[0]; - const n21 = this.data[1]; - const n31 = this.data[2]; - const n12 = this.data[3]; - const n22 = this.data[4]; - const n32 = this.data[5]; - const n13 = this.data[6]; - const n23 = this.data[7]; - const n33 = this.data[8]; - const t11 = n33 * n22 - n32 * n23; - const t12 = n32 * n13 - n33 * n12; - const t13 = n23 * n12 - n22 * n13; - const det = n11 * t11 + n21 * t12 + n31 * t13; - - if (det === 0) { - // Revert to identity if matrix is degenerate. - this.data[0] = 1; - this.data[1] = 0; - this.data[2] = 0; - - this.data[3] = 0; - this.data[4] = 1; - this.data[5] = 0; - - this.data[6] = 0; - this.data[7] = 0; - this.data[8] = 1; - - return; - } - - const det_inv = 1 / det; - - this.data[0] = t11 * det_inv; - this.data[1] = (n31 * n23 - n33 * n21) * det_inv; - this.data[2] = (n32 * n21 - n31 * n22) * det_inv; - - this.data[3] = t12 * det_inv; - this.data[4] = (n33 * n11 - n31 * n13) * det_inv; - this.data[5] = (n31 * n12 - n32 * n11) * det_inv; - - this.data[6] = t13 * det_inv; - this.data[7] = (n21 * n13 - n23 * n11) * det_inv; - this.data[8] = (n22 * n11 - n21 * n12) * det_inv; - } -} - -export function mat3_multiply(a: Mat3, b: Mat3): Mat3 { - const c = new Mat3(new Float32Array(9)); - mat3_product_into_array(c.data, a, b); - return c; -} - -export function mat3_multiply_into(a: Mat3, b: Mat3, result: Mat3): void { - const array = new Float32Array(9); - mat3_product_into_array(array, a, b); - result.data.set(array); -} - -function mat3_product_into_array(array: Float32Array, a: Mat3, b: Mat3): void { - for (let i = 0; i < 3; i++) { - for (let j = 0; j < 3; j++) { - for (let k = 0; k < 3; k++) { - array[i + j * 3] += a.data[i + k * 3] * b.data[k + j * 3]; - } - } - } -} - -/** - * Computes the product of `m` and `v` and stores it in `result`. - */ -export function mat3_vec3_multiply_into(m: Mat3, v: Vec3, result: Vec3): void { - const x = m.get(0, 0) * v.x + m.get(0, 1) * v.y + m.get(0, 2) * v.z; - const y = m.get(1, 0) * v.x + m.get(1, 1) * v.y + m.get(1, 2) * v.z; - const z = m.get(2, 0) * v.x + m.get(2, 1) * v.y + m.get(2, 2) * v.z; - - result.x = x; - result.y = y; - result.z = z; -} - -/** - * Stores data in column-major order. - */ -export class Mat4 { - // prettier-ignore - static of( - m00: number, m01: number, m02: number, m03: number, - m10: number, m11: number, m12: number, m13: number, - m20: number, m21: number, m22: number, m23: number, - m30: number, m31: number, m32: number, m33: number, - ): Mat4 { - return new Mat4(new Float32Array([ - m00, m10, m20, m30, - m01, m11, m21, m31, - m02, m12, m22, m32, - m03, m13, m23, m33, - ])); - } - - static identity(): Mat4 { - // prettier-ignore - return Mat4.of( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1, - ) - } - - static translation(x: number, y: number, z: number): Mat4 { - // prettier-ignore - return Mat4.of( - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1, - ); - } - - static scale(x: number, y: number, z: number): Mat4 { - // prettier-ignore - return Mat4.of( - x, 0, 0, 1, - 0, y, 0, 1, - 0, 0, z, 1, - 0, 0, 0, 1, - ); - } - - static compose(translation: Vec3, rotation: Quat, scale: Vec3): Mat4 { - const w = rotation.w; - const x = rotation.x; - const y = rotation.y; - const z = rotation.z; - const x2 = x + x; - const y2 = y + y; - const z2 = z + z; - const xx = x * x2; - const xy = x * y2; - const xz = x * z2; - const yy = y * y2; - const yz = y * z2; - const zz = z * z2; - const wx = w * x2; - const wy = w * y2; - const wz = w * z2; - - const sx = scale.x; - const sy = scale.y; - const sz = scale.z; - - // prettier-ignore - return Mat4.of( - (1 - (yy + zz)) * sx, (xy - wz) * sy, (xz + wy) * sz, translation.x, - (xy + wz) * sx, (1 - (xx + zz)) * sy, (yz - wx) * sz, translation.y, - (xz - wy) * sx, (yz + wx) * sy, (1 - (xx + yy)) * sz, translation.z, - 0, 0, 0, 1 - ) - } - - constructor(readonly data: Float32Array) { - assert(data.length === 16, "data should be of length 16."); - } - - get(i: number, j: number): number { - return this.data[i + j * 4]; - } - - set(i: number, j: number, value: number): void { - this.data[i + j * 4] = value; - } - - // prettier-ignore - set_all( - m00: number, m01: number, m02: number, m03: number, - m10: number, m11: number, m12: number, m13: number, - m20: number, m21: number, m22: number, m23: number, - m30: number, m31: number, m32: number, m33: number, - ):void { - this.data[0] = m00; - this.data[1] = m10; - this.data[2] = m20; - this.data[3] = m30; - - this.data[4] = m01; - this.data[5] = m11; - this.data[6] = m21; - this.data[7] = m31; - - this.data[8] = m02; - this.data[9] = m12; - this.data[10] = m22; - this.data[11] = m32; - - this.data[12] = m03; - this.data[13] = m13; - this.data[14] = m23; - this.data[15] = m33; - } - - /** - * Transposes this matrix in-place. - */ - transpose(): void { - let tmp: number; - const m = this.data; - - tmp = m[1]; - m[1] = m[4]; - m[4] = tmp; - - tmp = m[2]; - m[2] = m[8]; - m[8] = tmp; - - tmp = m[6]; - m[6] = m[9]; - m[9] = tmp; - - tmp = m[3]; - m[3] = m[12]; - m[12] = tmp; - - tmp = m[7]; - m[7] = m[13]; - m[13] = tmp; - - tmp = m[11]; - m[11] = m[14]; - m[14] = tmp; - } - - clone(): Mat4 { - return new Mat4(new Float32Array(this.data)); - } - - /** - * Computes a 3 x 3 surface normal transformation matrix. - */ - normal_mat3(): Mat3 { - // prettier-ignore - const m = Mat3.of( - this.data[0], this.data[4], this.data[8], - this.data[1], this.data[5], this.data[9], - this.data[2], this.data[6], this.data[10], - ); - m.invert(); - m.transpose(); - return m; - } -} - -export function mat4_multiply(a: Mat4, b: Mat4): Mat4 { - const c = new Mat4(new Float32Array(16)); - mat4_product_into_array(c.data, a, b); - return c; -} - -/** - * Computes the product of `a` and `b` and stores it in `result`. - */ -export function mat4_multiply_into(a: Mat4, b: Mat4, result: Mat4): void { - const array = new Float32Array(16); - mat4_product_into_array(array, a, b); - result.data.set(array); -} - -function mat4_product_into_array(array: Float32Array, a: Mat4, b: Mat4): void { - for (let i = 0; i < 4; i++) { - for (let j = 0; j < 4; j++) { - for (let k = 0; k < 4; k++) { - array[i + j * 4] += a.data[i + k * 4] * b.data[k + j * 4]; - } - } - } -} - -/** - * Computes the product of `m` and `v` and stores it in `result`. Assumes `m` is affine. - */ -export function mat4_vec3_multiply_into(m: Mat4, v: Vec3, result: Vec3): void { - const x = m.get(0, 0) * v.x + m.get(0, 1) * v.y + m.get(0, 2) * v.z + m.get(0, 3); - const y = m.get(1, 0) * v.x + m.get(1, 1) * v.y + m.get(1, 2) * v.z + m.get(1, 3); - const z = m.get(2, 0) * v.x + m.get(2, 1) * v.y + m.get(2, 2) * v.z + m.get(2, 3); - result.x = x; - result.y = y; - result.z = z; -} diff --git a/src/core/math/quaternions.test.ts b/src/core/math/quaternions.test.ts deleted file mode 100644 index db4e8cd5..00000000 --- a/src/core/math/quaternions.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { EulerOrder, Quat, quat_product } from "./quaternions"; - -test("euler_angles ZYX order", () => { - for (let angle = 0; angle < 2 * Math.PI; angle += Math.PI / 360) { - const x = Quat.euler_angles(angle, 0, 0, EulerOrder.ZYX); - const y = Quat.euler_angles(0, angle, 0, EulerOrder.ZYX); - const z = Quat.euler_angles(0, 0, angle, EulerOrder.ZYX); - const q = quat_product(quat_product(z, y), x); - const q2 = Quat.euler_angles(angle, angle, angle, EulerOrder.ZYX); - - expect(q.w).toBeCloseTo(q2.w, 5); - expect(q.x).toBeCloseTo(q2.x, 5); - expect(q.y).toBeCloseTo(q2.y, 5); - expect(q.z).toBeCloseTo(q2.z, 5); - } -}); - -test("euler_angles ZXY order", () => { - for (let angle = 0; angle < 2 * Math.PI; angle += Math.PI / 360) { - const x = Quat.euler_angles(angle, 0, 0, EulerOrder.ZXY); - const y = Quat.euler_angles(0, angle, 0, EulerOrder.ZXY); - const z = Quat.euler_angles(0, 0, angle, EulerOrder.ZXY); - const q = quat_product(quat_product(z, x), y); - const q2 = Quat.euler_angles(angle, angle, angle, EulerOrder.ZXY); - - expect(q.w).toBeCloseTo(q2.w, 5); - expect(q.x).toBeCloseTo(q2.x, 5); - expect(q.y).toBeCloseTo(q2.y, 5); - expect(q.z).toBeCloseTo(q2.z, 5); - } -}); diff --git a/src/core/math/quaternions.ts b/src/core/math/quaternions.ts deleted file mode 100644 index 85352a93..00000000 --- a/src/core/math/quaternions.ts +++ /dev/null @@ -1,57 +0,0 @@ -export enum EulerOrder { - ZXY, - ZYX, -} - -export class Quat { - /** - * Creates a quaternion from Euler angles. - * - * @param x - Rotation around the x-axis in radians. - * @param y - Rotation around the y-axis in radians. - * @param z - Rotation around the z-axis in radians. - * @param order - Order in which rotations are applied. - */ - static euler_angles(x: number, y: number, z: number, order: EulerOrder): Quat { - const cos_x = Math.cos(x * 0.5); - const sin_x = Math.sin(x * 0.5); - const cos_y = Math.cos(y * 0.5); - const sin_y = Math.sin(y * 0.5); - const cos_z = Math.cos(z * 0.5); - const sin_z = Math.sin(z * 0.5); - - switch (order) { - case EulerOrder.ZXY: - return new Quat( - cos_x * cos_y * cos_z - sin_x * sin_y * sin_z, - sin_x * cos_y * cos_z - cos_x * sin_y * sin_z, - cos_x * sin_y * cos_z + sin_x * cos_y * sin_z, - cos_x * cos_y * sin_z + sin_x * sin_y * cos_z, - ); - case EulerOrder.ZYX: - return new Quat( - cos_x * cos_y * cos_z + sin_x * sin_y * sin_z, - sin_x * cos_y * cos_z - cos_x * sin_y * sin_z, - cos_x * sin_y * cos_z + sin_x * cos_y * sin_z, - cos_x * cos_y * sin_z - sin_x * sin_y * cos_z, - ); - } - } - - constructor(public w: number, public x: number, public y: number, public z: number) {} - - conjugate(): void { - this.x *= -1; - this.y *= -1; - this.z *= -1; - } -} - -export function quat_product(p: Quat, q: Quat): Quat { - return new Quat( - p.w * q.w - p.x * q.x - p.y * q.y - p.z * q.z, - p.w * q.x + p.x * q.w + p.y * q.z - p.z * q.y, - p.w * q.y - p.x * q.z + p.y * q.w + p.z * q.x, - p.w * q.z + p.x * q.y - p.y * q.x + p.z * q.w, - ); -} diff --git a/src/core/rendering/Camera.ts b/src/core/rendering/Camera.ts deleted file mode 100644 index 4b089325..00000000 --- a/src/core/rendering/Camera.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { Mat4, Vec3, vec3_cross, vec3_dot, vec3_sub } from "../math/linear_algebra"; -import { clamp, deg_to_rad } from "../math"; - -export enum Projection { - Orthographic, - Perspective, -} - -export class Camera { - /** - * Only applicable in perspective mode. - */ - private readonly fov = deg_to_rad(75); - private readonly target: Vec3 = new Vec3(0, 0, 0); - - // Spherical coordinates. - private radius = 0; - private azimuth = 0; - private polar = Math.PI / 2; - - private _zoom: number = 1; - - /** - * Effective field of view in radians. Only applicable in perspective mode. - */ - private get effective_fov(): number { - return 2 * Math.atan(Math.tan(0.5 * this.fov) / this._zoom); - } - - readonly view_matrix = Mat4.identity(); - readonly projection_matrix = Mat4.identity(); - - constructor( - private viewport_width: number, - private viewport_height: number, - readonly projection: Projection, - ) { - this.set_viewport(viewport_width, viewport_height); - } - - set_viewport(width: number, height: number): void { - this.viewport_width = width; - this.viewport_height = height; - - switch (this.projection) { - case Projection.Orthographic: - { - const w = width; - const h = height; - const n = 0; - const f = 100; - - // prettier-ignore - this.projection_matrix.set_all( - 2/w, 0, 0, 0, - 0, 2/h, 0, 0, - 0, 0, 2/(n-f), 0, - 0, 0, 0, 1, - ); - } - break; - - case Projection.Perspective: - { - const aspect = width / height; - - const n /* near */ = 0.1; - const f /* far */ = 2000; - const t /* top */ = (n * Math.tan(0.5 * this.fov)) / this._zoom; - const h /* height */ = 2 * t; - const w /* width */ = 2 * aspect * t; - - // prettier-ignore - this.projection_matrix.set_all( - 2*n / w, 0, 0, 0, - 0, 2*n / h, 0, 0, - 0, 0, (n+f) / (n-f), 2*n*f / (n-f), - 0, 0, -1, 0, - ); - } - break; - } - } - - pan(x: number, y: number, z: number): this { - let pan_factor: number; - - switch (this.projection) { - case Projection.Orthographic: - pan_factor = 1; - break; - - case Projection.Perspective: - pan_factor = - (3 * this.radius * Math.tan(0.5 * this.effective_fov)) / this.viewport_width; - break; - } - - x *= pan_factor; - y *= pan_factor; - - this.target.x += x; - this.target.y += y; - - this.radius += z; - - this.update_matrix(); - return this; - } - - rotate(azimuth: number, polar: number): this { - this.azimuth += azimuth; - const max_pole_dist = Math.PI / 1800; // tenth of a degree. - this.polar = clamp(this.polar + polar, max_pole_dist, Math.PI - max_pole_dist); - this.update_matrix(); - return this; - } - - /** - * Increase (or decrease) zoom by a factor. - */ - zoom(factor: number): this { - this._zoom *= factor; - this.target.x *= factor; - this.target.y *= factor; - this.target.z *= factor; - this.update_matrix(); - return this; - } - - reset(): this { - this.target.x = 0; - this.target.y = 0; - this.target.z = 0; - this._zoom = 1; - this.update_matrix(); - return this; - } - - private update_matrix(): void { - // Convert spherical coordinates to cartesian coordinates. - const radius_sin_polar = this.radius * Math.sin(this.polar); - const camera_pos = new Vec3( - this.target.x + radius_sin_polar * Math.sin(this.azimuth), - this.target.y + this.radius * Math.cos(this.polar), - this.target.z + radius_sin_polar * Math.cos(this.azimuth), - ); - - // Compute forward (z-axis), right (x-axis) and up (y-axis) vectors. - const forward = vec3_sub(camera_pos, this.target); - forward.normalize(); - - const right = vec3_cross(new Vec3(0, 1, 0), forward); - right.normalize(); - - const up = vec3_cross(forward, right); - - const zoom = this._zoom; - - // prettier-ignore - this.view_matrix.set_all( - right.x * zoom, right.y, right.z, -vec3_dot( right, camera_pos), - up.x, up.y* zoom, up.z, -vec3_dot( up, camera_pos), - forward.x, forward.y, forward.z* zoom, -vec3_dot(forward, camera_pos), - 0, 0, 0, 1, - ); - } -} diff --git a/src/core/rendering/Gfx.ts b/src/core/rendering/Gfx.ts deleted file mode 100644 index 6d869f9a..00000000 --- a/src/core/rendering/Gfx.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Texture, TextureFormat } from "./Texture"; -import { VertexFormatType } from "./VertexFormat"; - -export interface Gfx { - create_gfx_mesh( - format: VertexFormatType, - vertex_data: ArrayBuffer, - index_data: ArrayBuffer, - texture?: Texture, - ): GfxMesh; - - destroy_gfx_mesh(gfx_mesh?: GfxMesh): void; - - create_texture( - format: TextureFormat, - width: number, - height: number, - data: ArrayBuffer, - ): GfxTexture; - - destroy_texture(texture?: GfxTexture): void; -} diff --git a/src/core/rendering/GfxRenderer.ts b/src/core/rendering/GfxRenderer.ts deleted file mode 100644 index 4deab5f0..00000000 --- a/src/core/rendering/GfxRenderer.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { Renderer } from "./Renderer"; -import { Scene } from "./Scene"; -import { Camera, Projection } from "./Camera"; -import { Gfx } from "./Gfx"; -import { Mat4, Vec2, vec2_diff } from "../math/linear_algebra"; - -export abstract class GfxRenderer implements Renderer { - private pointer_pos?: Vec2; - /** - * Is defined when an animation frame is scheduled. - */ - private animation_frame?: number; - - protected width: number = 800; - protected height: number = 600; - - abstract readonly gfx: Gfx; - readonly scene = new Scene(); - readonly camera: Camera; - readonly canvas_element: HTMLCanvasElement; - - protected constructor(canvas_element: HTMLCanvasElement, projection: Projection) { - this.canvas_element = canvas_element; - this.canvas_element.width = this.width; - this.canvas_element.height = this.height; - this.canvas_element.addEventListener("mousedown", this.mousedown); - this.canvas_element.addEventListener("wheel", this.wheel, { passive: true }); - - this.camera = new Camera(this.width, this.height, projection); - } - - dispose(): void { - this.destroy_scene(); - } - - set_size(width: number, height: number): void { - this.width = width; - this.height = height; - this.camera.set_viewport(width, height); - this.schedule_render(); - } - - start_rendering(): void { - this.schedule_render(); - } - - stop_rendering(): void { - if (this.animation_frame != undefined) { - cancelAnimationFrame(this.animation_frame); - } - - this.animation_frame = undefined; - } - - schedule_render = (): void => { - if (this.animation_frame == undefined) { - this.animation_frame = requestAnimationFrame(this.call_render); - } - }; - - private call_render = (): void => { - this.animation_frame = undefined; - this.render(); - }; - - protected abstract render(): void; - - /** - * Destroys all GPU objects related to the scene and resets the scene. - */ - destroy_scene(): void { - this.scene.traverse(node => { - node.mesh?.destroy(this.gfx); - node.mesh?.texture?.destroy(); - node.mesh = undefined; - }, undefined); - - this.scene.root_node.clear_children(); - this.scene.root_node.transform = Mat4.identity(); - } - - private mousedown = (evt: MouseEvent): void => { - this.pointer_pos = new Vec2(evt.clientX, evt.clientY); - - window.addEventListener("mousemove", this.mousemove); - window.addEventListener("mouseup", this.mouseup); - window.addEventListener("contextmenu", this.contextmenu); - }; - - private mousemove = (evt: MouseEvent): void => { - const new_pos = new Vec2(evt.clientX, evt.clientY); - const diff = vec2_diff(new_pos, this.pointer_pos!); - - if (evt.buttons === 1) { - this.camera.pan(-diff.x, diff.y, 0); - } else if (evt.buttons === 2) { - this.camera.rotate(-diff.x / (20 * Math.PI), -diff.y / (20 * Math.PI)); - } - - this.pointer_pos = new_pos; - this.schedule_render(); - }; - - private mouseup = (evt: MouseEvent): void => { - evt.preventDefault(); - - this.pointer_pos = undefined; - - window.removeEventListener("mousemove", this.mousemove); - window.removeEventListener("mouseup", this.mouseup); - }; - - private wheel = (evt: WheelEvent): void => { - switch (this.camera.projection) { - case Projection.Orthographic: - if (evt.deltaY < 0) { - this.camera.zoom(1.1); - } else { - this.camera.zoom(0.9); - } - break; - - case Projection.Perspective: - if (evt.deltaY < 0) { - this.camera.pan(0, 0, -2); - } else { - this.camera.pan(0, 0, 2); - } - break; - } - - this.schedule_render(); - }; - - private contextmenu = (evt: Event): void => { - evt.preventDefault(); - window.removeEventListener("contextmenu", this.contextmenu); - }; -} diff --git a/src/core/rendering/Mesh.ts b/src/core/rendering/Mesh.ts deleted file mode 100644 index 27abd707..00000000 --- a/src/core/rendering/Mesh.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { VertexFormatType } from "./VertexFormat"; -import { Texture } from "./Texture"; -import { Gfx } from "./Gfx"; -import { - MeshBuilder, - PosNormMeshBuilder, - PosNormTexMeshBuilder, - PosTexMeshBuilder, -} from "./MeshBuilder"; - -export class Mesh { - /* eslint-disable no-dupe-class-members */ - static builder(format: VertexFormatType.PosNorm): PosNormMeshBuilder; - static builder(format: VertexFormatType.PosTex): PosTexMeshBuilder; - static builder(format: VertexFormatType.PosNormTex): PosNormTexMeshBuilder; - static builder(format: VertexFormatType): MeshBuilder { - switch (format) { - case VertexFormatType.PosNorm: - return new PosNormMeshBuilder(); - case VertexFormatType.PosTex: - return new PosTexMeshBuilder(); - case VertexFormatType.PosNormTex: - return new PosNormTexMeshBuilder(); - } - } - /* eslint-enable no-dupe-class-members */ - - gfx_mesh: unknown; - - constructor( - readonly format: VertexFormatType, - readonly vertex_data: ArrayBuffer, - readonly index_data: ArrayBuffer, - readonly index_count: number, - readonly texture?: Texture, - ) {} - - upload(gfx: Gfx): void { - this.texture?.upload(); - - if (this.gfx_mesh == undefined) { - this.gfx_mesh = gfx.create_gfx_mesh( - this.format, - this.vertex_data, - this.index_data, - this.texture, - ); - } - } - - destroy(gfx: Gfx): void { - gfx.destroy_gfx_mesh(this.gfx_mesh); - } -} diff --git a/src/core/rendering/MeshBuilder.ts b/src/core/rendering/MeshBuilder.ts deleted file mode 100644 index 4a1b0e9c..00000000 --- a/src/core/rendering/MeshBuilder.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { Texture } from "./Texture"; -import { VERTEX_FORMATS, VertexFormat, VertexFormatType } from "./VertexFormat"; -import { Mesh } from "./Mesh"; -import { Vec2, Vec3 } from "../math/linear_algebra"; - -export abstract class MeshBuilder { - private readonly format: VertexFormat; - - protected readonly vertex_data: { - pos: Vec3; - normal?: Vec3; - tex?: Vec2; - }[] = []; - protected readonly index_data: number[] = []; - protected _texture?: Texture; - - get vertex_count(): number { - return this.vertex_data.length; - } - - protected constructor(format_type: VertexFormatType) { - this.format = VERTEX_FORMATS[format_type]; - } - - triangle(v1: number, v2: number, v3: number): this { - this.index_data.push(v1, v2, v3); - return this; - } - - build(): Mesh { - const v_size = this.format.size; - const v_normal_offset = this.format.normal_offset; - const v_tex_offset = this.format.tex_offset; - const v_data = new ArrayBuffer(this.vertex_data.length * v_size); - const v_view = new DataView(v_data); - let i = 0; - - for (const { pos, normal, tex } of this.vertex_data) { - v_view.setFloat32(i, pos.x, true); - v_view.setFloat32(i + 4, pos.y, true); - v_view.setFloat32(i + 8, pos.z, true); - - if (v_normal_offset != undefined) { - v_view.setFloat32(i + v_normal_offset, normal!.x, true); - v_view.setFloat32(i + v_normal_offset + 4, normal!.y, true); - v_view.setFloat32(i + v_normal_offset + 8, normal!.z, true); - } - - if (v_tex_offset != undefined) { - v_view.setUint16(i + v_tex_offset, tex!.x * 0xffff, true); - v_view.setUint16(i + v_tex_offset + 2, tex!.y * 0xffff, true); - } - - i += v_size; - } - - // Make index data divisible by 4 for WebGPU. - const i_data = new ArrayBuffer(4 * Math.ceil(this.index_data.length / 2)); - new Uint16Array(i_data).set(this.index_data); - - return new Mesh(this.format.type, v_data, i_data, this.index_data.length, this._texture); - } -} - -export class PosNormMeshBuilder extends MeshBuilder { - constructor() { - super(VertexFormatType.PosNorm); - } - - vertex(pos: Vec3, normal: Vec3): this { - this.vertex_data.push({ pos, normal }); - return this; - } -} - -export class PosTexMeshBuilder extends MeshBuilder { - constructor() { - super(VertexFormatType.PosTex); - } - - vertex(pos: Vec3, tex: Vec2): this { - this.vertex_data.push({ pos, tex }); - return this; - } - - texture(tex: Texture): this { - this._texture = tex; - return this; - } -} - -export class PosNormTexMeshBuilder extends MeshBuilder { - constructor() { - super(VertexFormatType.PosNormTex); - } - - vertex(pos: Vec3, normal: Vec3, tex: Vec2): this { - this.vertex_data.push({ pos, normal, tex }); - return this; - } - - texture(tex: Texture): this { - this._texture = tex; - return this; - } -} diff --git a/src/core/rendering/Renderer.ts b/src/core/rendering/Renderer.ts index 76ed3ae4..6610ed52 100644 --- a/src/core/rendering/Renderer.ts +++ b/src/core/rendering/Renderer.ts @@ -1,11 +1,140 @@ +import CameraControls from "camera-controls"; +import * as THREE from "three"; +import { + Clock, + Color, + Group, + HemisphereLight, + OrthographicCamera, + PerspectiveCamera, + Scene, + Vector2, + Vector3, +} from "three"; import { Disposable } from "../observable/Disposable"; -export interface Renderer extends Disposable { - readonly canvas_element: HTMLCanvasElement; +CameraControls.install({ + // Hack to make panning and orbiting work the way we want. + THREE: { + ...THREE, + MOUSE: { ...THREE.MOUSE, LEFT: THREE.MOUSE.RIGHT, RIGHT: THREE.MOUSE.LEFT }, + }, +}); - start_rendering(): void; +export interface DisposableThreeRenderer extends THREE.WebGLRenderer, Disposable {} - stop_rendering(): void; +/** + * Uses THREE.js for rendering. + */ +export abstract class Renderer implements Disposable { + private _debug = false; - set_size(width: number, height: number): void; + get debug(): boolean { + return this._debug; + } + + set debug(debug: boolean) { + this._debug = debug; + } + + abstract readonly camera: PerspectiveCamera | OrthographicCamera; + readonly controls!: CameraControls; + readonly scene = new Scene(); + readonly light_holder = new Group(); + + private readonly renderer: DisposableThreeRenderer; + private render_scheduled = false; + private animation_frame_handle?: number = undefined; + private readonly light = new HemisphereLight(0xffffff, 0x505050, 1.0); + private readonly controls_clock = new Clock(); + private readonly size = new Vector2(0, 0); + + protected constructor(three_renderer: DisposableThreeRenderer) { + this.renderer = three_renderer; + this.renderer.domElement.tabIndex = 0; + this.renderer.domElement.addEventListener("mousedown", this.on_mouse_down); + this.renderer.domElement.style.outline = "none"; + + this.scene.background = new Color(0x181818); + this.light_holder.add(this.light); + this.scene.add(this.light_holder); + } + + get canvas_element(): HTMLCanvasElement { + return this.renderer.domElement; + } + + set_size(width: number, height: number): void { + this.size.set(width, height); + this.renderer.setSize(width, height); + this.schedule_render(); + } + + pointer_pos_to_device_coords(pos: Vector2): void { + pos.set((pos.x / this.size.width) * 2 - 1, (pos.y / this.size.height) * -2 + 1); + } + + start_rendering(): void { + if (this.animation_frame_handle == undefined) { + this.schedule_render(); + this.animation_frame_handle = requestAnimationFrame(this.call_render); + } + } + + stop_rendering(): void { + if (this.animation_frame_handle != undefined) { + cancelAnimationFrame(this.animation_frame_handle); + this.animation_frame_handle = undefined; + } + } + + schedule_render = (): void => { + this.render_scheduled = true; + }; + + reset_camera(position: Vector3, look_at: Vector3): void { + this.controls.setLookAt( + position.x, + position.y, + position.z, + look_at.x, + look_at.y, + look_at.z, + ); + } + + dispose(): void { + this.renderer.dispose(); + this.controls.dispose(); + } + + protected init_camera_controls(): void { + (this.controls as CameraControls) = new CameraControls( + this.camera, + this.renderer.domElement, + ); + this.controls.dampingFactor = 1; + this.controls.draggingDampingFactor = 1; + } + + protected render(): void { + this.renderer.render(this.scene, this.camera); + } + + private on_mouse_down = (e: Event): void => { + if (e.currentTarget) (e.currentTarget as HTMLElement).focus(); + }; + + private call_render = (): void => { + const controls_updated = this.controls.update(this.controls_clock.getDelta()); + const should_render = this.render_scheduled || controls_updated; + + this.render_scheduled = false; + + if (should_render) { + this.render(); + } + + this.animation_frame_handle = requestAnimationFrame(this.call_render); + }; } diff --git a/src/core/rendering/Scene.ts b/src/core/rendering/Scene.ts deleted file mode 100644 index 2a950b0d..00000000 --- a/src/core/rendering/Scene.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Mesh } from "./Mesh"; -import { Mat4 } from "../math/linear_algebra"; - -export class Scene { - readonly root_node = new SceneNode(undefined, Mat4.identity()); - - traverse(f: (node: SceneNode, data: T) => T, data: T): void { - this.traverse_node(this.root_node, f, data); - } - - private traverse_node(node: SceneNode, f: (node: SceneNode, data: T) => T, data: T): void { - const child_data = f(node, data); - - for (const child of node.children) { - this.traverse_node(child, f, child_data); - } - } -} - -export class SceneNode { - private readonly _children: SceneNode[]; - - get children(): readonly SceneNode[] { - return this._children; - } - - constructor(public mesh: Mesh | undefined, public transform: Mat4, ...children: SceneNode[]) { - this._children = children; - } - - add_child(child: SceneNode): void { - this._children.push(child); - } - - clear_children(): void { - this._children.splice(0); - } -} diff --git a/src/core/rendering/ShaderProgram.ts b/src/core/rendering/ShaderProgram.ts deleted file mode 100644 index 9d571143..00000000 --- a/src/core/rendering/ShaderProgram.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Mat3, Mat4 } from "../math/linear_algebra"; -import { VERTEX_NORMAL_LOC, VERTEX_POS_LOC, VERTEX_TEX_LOC } from "./VertexFormat"; - -export class ShaderProgram { - private readonly gl: WebGL2RenderingContext; - private readonly program: WebGLProgram; - private readonly mat_projection_loc: WebGLUniformLocation; - private readonly mat_model_view_loc: WebGLUniformLocation; - private readonly mat_normal_loc: WebGLUniformLocation | null; - private readonly tex_sampler_loc: WebGLUniformLocation | null; - - constructor(gl: WebGL2RenderingContext, vertex_source: string, frag_source: string) { - this.gl = gl; - const program = gl.createProgram(); - if (program == null) throw new Error("Failed to create program."); - this.program = program; - - let vertex_shader: WebGLShader | null = null; - let frag_shader: WebGLShader | null = null; - - try { - vertex_shader = create_shader(gl, gl.VERTEX_SHADER, vertex_source); - gl.attachShader(program, vertex_shader); - - frag_shader = create_shader(gl, gl.FRAGMENT_SHADER, frag_source); - gl.attachShader(program, frag_shader); - - gl.bindAttribLocation(program, VERTEX_POS_LOC, "pos"); - gl.bindAttribLocation(program, VERTEX_NORMAL_LOC, "normal"); - gl.bindAttribLocation(program, VERTEX_TEX_LOC, "tex"); - gl.linkProgram(program); - - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - const log = gl.getProgramInfoLog(program); - throw new Error("Shader linking failed. Program log:\n" + log); - } - - this.mat_projection_loc = this.get_required_uniform_location(program, "mat_projection"); - this.mat_model_view_loc = this.get_required_uniform_location(program, "mat_model_view"); - this.mat_normal_loc = gl.getUniformLocation(program, "mat_normal"); - - this.tex_sampler_loc = gl.getUniformLocation(program, "tex_sampler"); - - gl.detachShader(program, vertex_shader); - gl.detachShader(program, frag_shader); - } catch (e) { - gl.deleteProgram(program); - throw e; - } finally { - // Always delete shaders after we're done. - gl.deleteShader(vertex_shader); - gl.deleteShader(frag_shader); - } - } - - set_mat_projection_uniform(matrix: Mat4): void { - this.gl.uniformMatrix4fv(this.mat_projection_loc, false, matrix.data); - } - - set_mat_model_view_uniform(matrix: Mat4): void { - this.gl.uniformMatrix4fv(this.mat_model_view_loc, false, matrix.data); - } - - set_mat_normal_uniform(matrix: Mat3): void { - this.gl.uniformMatrix3fv(this.mat_normal_loc, false, matrix.data); - } - - set_texture_uniform(unit: GLenum): void { - this.gl.uniform1i(this.tex_sampler_loc, unit - this.gl.TEXTURE0); - } - - bind(): void { - this.gl.useProgram(this.program); - } - - unbind(): void { - this.gl.useProgram(null); - } - - delete(): void { - this.gl.deleteProgram(this.program); - } - - private get_required_uniform_location( - program: WebGLProgram, - uniform: string, - ): WebGLUniformLocation { - const loc = this.gl.getUniformLocation(program, uniform); - if (loc == null) throw new Error(`Couldn't get ${uniform} uniform location.`); - return loc; - } -} - -function create_shader(gl: WebGL2RenderingContext, type: GLenum, source: string): WebGLShader { - const shader = gl.createShader(type); - if (shader == null) throw new Error(`Failed to create shader of type ${type}.`); - - gl.shaderSource(shader, source); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - const log = gl.getShaderInfoLog(shader); - gl.deleteShader(shader); - - throw new Error("Vertex shader compilation failed. Shader log:\n" + log); - } - - return shader; -} diff --git a/src/core/rendering/Texture.ts b/src/core/rendering/Texture.ts deleted file mode 100644 index abaf75f7..00000000 --- a/src/core/rendering/Texture.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Gfx } from "./Gfx"; - -export enum TextureFormat { - RGBA_S3TC_DXT1, - RGBA_S3TC_DXT3, -} - -export class Texture { - gfx_texture: unknown; - - constructor( - private readonly gfx: Gfx, - private readonly format: TextureFormat, - private readonly width: number, - private readonly height: number, - private readonly data: ArrayBuffer, - ) {} - - upload(): void { - if (this.gfx_texture == undefined) { - this.gfx_texture = this.gfx.create_texture( - this.format, - this.width, - this.height, - this.data, - ); - } - } - - destroy(): void { - this.gfx.destroy_texture(this.gfx_texture); - } -} diff --git a/src/core/rendering/ThreeRenderer.ts b/src/core/rendering/ThreeRenderer.ts deleted file mode 100644 index 7deb3eef..00000000 --- a/src/core/rendering/ThreeRenderer.ts +++ /dev/null @@ -1,141 +0,0 @@ -import CameraControls from "camera-controls"; -import * as THREE from "three"; -import { - Clock, - Color, - Group, - HemisphereLight, - OrthographicCamera, - PerspectiveCamera, - Scene, - Vector2, - Vector3, -} from "three"; -import { Disposable } from "../observable/Disposable"; -import { Renderer } from "./Renderer"; - -CameraControls.install({ - // Hack to make panning and orbiting work the way we want. - THREE: { - ...THREE, - MOUSE: { ...THREE.MOUSE, LEFT: THREE.MOUSE.RIGHT, RIGHT: THREE.MOUSE.LEFT }, - }, -}); - -export interface DisposableThreeRenderer extends THREE.WebGLRenderer, Disposable {} - -/** - * Uses THREE.js for rendering. - */ -export abstract class ThreeRenderer implements Renderer { - private _debug = false; - - get debug(): boolean { - return this._debug; - } - - set debug(debug: boolean) { - this._debug = debug; - } - - abstract readonly camera: PerspectiveCamera | OrthographicCamera; - readonly controls!: CameraControls; - readonly scene = new Scene(); - readonly light_holder = new Group(); - - private readonly renderer: DisposableThreeRenderer; - private render_scheduled = false; - private animation_frame_handle?: number = undefined; - private readonly light = new HemisphereLight(0xffffff, 0x505050, 1.0); - private readonly controls_clock = new Clock(); - private readonly size = new Vector2(0, 0); - - protected constructor(three_renderer: DisposableThreeRenderer) { - this.renderer = three_renderer; - this.renderer.domElement.tabIndex = 0; - this.renderer.domElement.addEventListener("mousedown", this.on_mouse_down); - this.renderer.domElement.style.outline = "none"; - - this.scene.background = new Color(0x181818); - this.light_holder.add(this.light); - this.scene.add(this.light_holder); - } - - get canvas_element(): HTMLCanvasElement { - return this.renderer.domElement; - } - - set_size(width: number, height: number): void { - this.size.set(width, height); - this.renderer.setSize(width, height); - this.schedule_render(); - } - - pointer_pos_to_device_coords(pos: Vector2): void { - pos.set((pos.x / this.size.width) * 2 - 1, (pos.y / this.size.height) * -2 + 1); - } - - start_rendering(): void { - if (this.animation_frame_handle == undefined) { - this.schedule_render(); - this.animation_frame_handle = requestAnimationFrame(this.call_render); - } - } - - stop_rendering(): void { - if (this.animation_frame_handle != undefined) { - cancelAnimationFrame(this.animation_frame_handle); - this.animation_frame_handle = undefined; - } - } - - schedule_render = (): void => { - this.render_scheduled = true; - }; - - reset_camera(position: Vector3, look_at: Vector3): void { - this.controls.setLookAt( - position.x, - position.y, - position.z, - look_at.x, - look_at.y, - look_at.z, - ); - } - - dispose(): void { - this.renderer.dispose(); - this.controls.dispose(); - } - - protected init_camera_controls(): void { - (this.controls as CameraControls) = new CameraControls( - this.camera, - this.renderer.domElement, - ); - this.controls.dampingFactor = 1; - this.controls.draggingDampingFactor = 1; - } - - protected render(): void { - this.renderer.render(this.scene, this.camera); - } - - private on_mouse_down = (e: Event): void => { - if (e.currentTarget) (e.currentTarget as HTMLElement).focus(); - }; - - private call_render = (): void => { - const controls_updated = this.controls.update(this.controls_clock.getDelta()); - const should_render = this.render_scheduled || controls_updated; - - this.render_scheduled = false; - - if (should_render) { - this.render(); - } - - this.animation_frame_handle = requestAnimationFrame(this.call_render); - }; -} diff --git a/src/core/rendering/VertexFormat.ts b/src/core/rendering/VertexFormat.ts deleted file mode 100644 index 3f157dd8..00000000 --- a/src/core/rendering/VertexFormat.ts +++ /dev/null @@ -1,41 +0,0 @@ -export enum VertexFormatType { - PosNorm, - PosTex, - PosNormTex, -} - -export type VertexFormat = { - readonly type: VertexFormatType; - readonly size: number; - readonly normal_offset?: number; - readonly tex_offset?: number; - readonly uniform_buffer_size: number; -}; - -export const VERTEX_FORMATS: readonly VertexFormat[] = [ - { - type: VertexFormatType.PosNorm, - size: 24, - normal_offset: 12, - tex_offset: undefined, - uniform_buffer_size: 4 * (16 + 9), - }, - { - type: VertexFormatType.PosTex, - size: 16, - normal_offset: undefined, - tex_offset: 12, - uniform_buffer_size: 4 * 16, - }, - // TODO: add VertexFormat for PosNormTex. - // { - // type: VertexFormatType.PosNormTex, - // size: 28, - // normal_offset: 12, - // tex_offset: 24, - // }, -]; - -export const VERTEX_POS_LOC = 0; -export const VERTEX_NORMAL_LOC = 1; -export const VERTEX_TEX_LOC = 2; diff --git a/src/core/rendering/conversion/index.ts b/src/core/rendering/conversion/index.ts index d198fe75..7edbd0b7 100644 --- a/src/core/rendering/conversion/index.ts +++ b/src/core/rendering/conversion/index.ts @@ -1,11 +1,6 @@ import { Vec3 } from "../../data_formats/vector"; import { Vector3 } from "three"; -import { Vec3 as MathVec3 } from "../../math/linear_algebra"; export function vec3_to_threejs(v: Vec3): Vector3 { return new Vector3(v.x, v.y, v.z); } - -export function vec3_to_math(v: Vec3): MathVec3 { - return new MathVec3(v.x, v.y, v.z); -} diff --git a/src/core/rendering/conversion/ninja_geometry.ts b/src/core/rendering/conversion/ninja_geometry.ts index 107bfc6b..6c647e9d 100644 --- a/src/core/rendering/conversion/ninja_geometry.ts +++ b/src/core/rendering/conversion/ninja_geometry.ts @@ -1,30 +1,28 @@ +import { Bone, BufferGeometry, Euler, Matrix3, Matrix4, Quaternion, Vector2, Vector3 } from "three"; +import { vec3_to_threejs } from "./index"; import { is_njcm_model, NjModel, NjObject } from "../../data_formats/parsing/ninja"; import { NjcmModel } from "../../data_formats/parsing/ninja/njcm"; import { XjModel } from "../../data_formats/parsing/ninja/xj"; -import { vec3_to_math } from "./index"; -import { Mesh } from "../Mesh"; -import { VertexFormatType } from "../VertexFormat"; -import { EulerOrder, Quat } from "../../math/quaternions"; -import { - mat3_vec3_multiply_into, - Mat4, - mat4_multiply, - mat4_vec3_multiply_into, - Vec3, -} from "../../math/linear_algebra"; +import { GeometryBuilder } from "./GeometryBuilder"; -const DEFAULT_NORMAL = new Vec3(0, 1, 0); -const NO_TRANSLATION = new Vec3(0, 0, 0); -const NO_ROTATION = new Quat(1, 0, 0, 0); -const NO_SCALE = new Vec3(1, 1, 1); +const DEFAULT_NORMAL = new Vector3(0, 1, 0); +const DEFAULT_UV = new Vector2(0, 0); +const NO_TRANSLATION = new Vector3(0, 0, 0); +const NO_ROTATION = new Quaternion(0, 0, 0, 1); +const NO_SCALE = new Vector3(1, 1, 1); -export function ninja_object_to_mesh(object: NjObject): Mesh { - return new MeshCreator().to_mesh(object); +export function ninja_object_to_geometry_builder(object: NjObject, builder: GeometryBuilder): void { + new GeometryCreator(builder).to_geometry_builder(object); +} + +export function ninja_object_to_buffer_geometry(object: NjObject): BufferGeometry { + return new GeometryCreator(new GeometryBuilder()).create_buffer_geometry(object); } type Vertex = { - position: Vec3; - normal?: Vec3; + bone_id: number; + position: Vector3; + normal?: Vector3; bone_weight: number; bone_weight_status: number; calc_continue: boolean; @@ -52,16 +50,29 @@ class VerticesHolder { } } -class MeshCreator { +class GeometryCreator { private readonly vertices = new VerticesHolder(); - private readonly builder = Mesh.builder(VertexFormatType.PosNorm); + private readonly builder: GeometryBuilder; + private bone_id = 0; - to_mesh(object: NjObject): Mesh { - this.object_to_mesh(object, Mat4.identity()); + constructor(builder: GeometryBuilder) { + this.builder = builder; + } + + to_geometry_builder(object: NjObject): void { + this.object_to_geometry(object, undefined, new Matrix4()); + } + + create_buffer_geometry(object: NjObject): BufferGeometry { + this.to_geometry_builder(object); return this.builder.build(); } - private object_to_mesh(object: NjObject, parent_matrix: Mat4): void { + private object_to_geometry( + object: NjObject, + parent_bone: Bone | undefined, + parent_matrix: Matrix4, + ): void { const { no_translate, no_rotate, @@ -69,59 +80,76 @@ class MeshCreator { hidden, break_child_trace, zxy_rotation_order, + skip, } = object.evaluation_flags; const { position, rotation, scale } = object; - const matrix = mat4_multiply( - parent_matrix, - Mat4.compose( - no_translate ? NO_TRANSLATION : vec3_to_math(position), - no_rotate - ? NO_ROTATION - : Quat.euler_angles( - rotation.x, - rotation.y, - rotation.z, - zxy_rotation_order ? EulerOrder.ZXY : EulerOrder.ZYX, - ), - no_scale ? NO_SCALE : vec3_to_math(scale), - ), + const euler = new Euler( + rotation.x, + rotation.y, + rotation.z, + zxy_rotation_order ? "ZXY" : "ZYX", ); + const matrix = new Matrix4() + .compose( + no_translate ? NO_TRANSLATION : vec3_to_threejs(position), + no_rotate ? NO_ROTATION : new Quaternion().setFromEuler(euler), + no_scale ? NO_SCALE : vec3_to_threejs(scale), + ) + .premultiply(parent_matrix); + + let bone: Bone | undefined; + + if (skip) { + bone = parent_bone; + } else { + bone = new Bone(); + bone.name = this.bone_id.toString(); + + bone.position.set(position.x, position.y, position.z); + bone.setRotationFromEuler(euler); + bone.scale.set(scale.x, scale.y, scale.z); + + this.builder.add_bone(bone); + + if (parent_bone) { + parent_bone.add(bone); + } + } if (object.model && !hidden) { - this.model_to_mesh(object.model, matrix); + this.model_to_geometry(object.model, matrix); } + this.bone_id++; + if (!break_child_trace) { for (const child of object.children) { - this.object_to_mesh(child, matrix); + this.object_to_geometry(child, bone, matrix); } } } - private model_to_mesh(model: NjModel, matrix: Mat4): void { + private model_to_geometry(model: NjModel, matrix: Matrix4): void { if (is_njcm_model(model)) { - this.njcm_model_to_mesh(model, matrix); + this.njcm_model_to_geometry(model, matrix); } else { - this.xj_model_to_mesh(model, matrix); + this.xj_model_to_geometry(model, matrix); } } - private njcm_model_to_mesh(model: NjcmModel, matrix: Mat4): void { - const normal_matrix = matrix.normal_mat3(); + private njcm_model_to_geometry(model: NjcmModel, matrix: Matrix4): void { + const normal_matrix = new Matrix3().getNormalMatrix(matrix); const new_vertices = model.vertices.map(vertex => { - const position = vec3_to_math(vertex.position); - mat4_vec3_multiply_into(matrix, position, position); + const position = vec3_to_threejs(vertex.position); + const normal = vertex.normal ? vec3_to_threejs(vertex.normal) : new Vector3(0, 1, 0); - let normal: Vec3 | undefined = undefined; - - if (vertex.normal) { - normal = vec3_to_math(vertex.normal); - mat3_vec3_multiply_into(normal_matrix, normal, normal); - } + position.applyMatrix4(matrix); + normal.applyMatrix3(normal_matrix); return { + bone_id: this.bone_id, position, normal, bone_weight: vertex.bone_weight, @@ -133,61 +161,152 @@ class MeshCreator { this.vertices.put(new_vertices); for (const mesh of model.meshes) { + const start_index_count = this.builder.index_count; + for (let i = 0; i < mesh.vertices.length; ++i) { const mesh_vertex = mesh.vertices[i]; const vertices = this.vertices.get(mesh_vertex.index); if (vertices.length) { const vertex = vertices[0]; - const normal = - vertex.normal ?? - (mesh_vertex.normal ? vec3_to_math(mesh_vertex.normal) : DEFAULT_NORMAL); + const normal = vertex.normal ?? mesh_vertex.normal ?? DEFAULT_NORMAL; const index = this.builder.vertex_count; - this.builder.vertex(vertex.position, normal); + this.builder.add_vertex( + vertex.position, + normal, + mesh.has_tex_coords ? mesh_vertex.tex_coords! : DEFAULT_UV, + ); if (i >= 2) { if (i % 2 === (mesh.clockwise_winding ? 1 : 0)) { - this.builder.triangle(index - 2, index - 1, index); + this.builder.add_index(index - 2); + this.builder.add_index(index - 1); + this.builder.add_index(index); } else { - this.builder.triangle(index - 2, index, index - 1); + this.builder.add_index(index - 2); + this.builder.add_index(index); + this.builder.add_index(index - 1); } } + + const bones = [ + [0, 0], + [0, 0], + [0, 0], + [0, 0], + ]; + + for (let j = vertices.length - 1; j >= 0; j--) { + const vertex = vertices[j]; + bones[vertex.bone_weight_status] = [vertex.bone_id, vertex.bone_weight]; + } + + const total_weight = bones.reduce((total, [, weight]) => total + weight, 0); + + for (const [bone_index, bone_weight] of bones) { + this.builder.add_bone_weight( + bone_index, + total_weight > 0 ? bone_weight / total_weight : bone_weight, + ); + } } } + + this.builder.add_group( + start_index_count, + this.builder.index_count - start_index_count, + mesh.texture_id, + mesh.use_alpha, + mesh.src_alpha !== 4 || mesh.dst_alpha !== 5, + ); } } - private xj_model_to_mesh(model: XjModel, matrix: Mat4): void { + private xj_model_to_geometry(model: XjModel, matrix: Matrix4): void { const index_offset = this.builder.vertex_count; - const normal_matrix = matrix.normal_mat3(); + const normal_matrix = new Matrix3().getNormalMatrix(matrix); - for (const { position, normal } of model.vertices) { - const p = vec3_to_math(position); - mat4_vec3_multiply_into(matrix, p, p); + for (const { position, normal, uv } of model.vertices) { + const p = vec3_to_threejs(position).applyMatrix4(matrix); - const n = normal ? vec3_to_math(normal) : new Vec3(0, 1, 0); - mat3_vec3_multiply_into(normal_matrix, n, n); + const local_n = normal ? vec3_to_threejs(normal) : new Vector3(0, 1, 0); + const n = local_n.applyMatrix3(normal_matrix); - this.builder.vertex(p, n); + const tuv = uv || DEFAULT_UV; + + this.builder.add_vertex(p, n, tuv); } + let current_mat_idx: number | undefined; + let current_src_alpha: number | undefined; + let current_dst_alpha: number | undefined; + for (const mesh of model.meshes) { + const start_index_count = this.builder.index_count; let clockwise = false; for (let j = 2; j < mesh.indices.length; ++j) { const a = index_offset + mesh.indices[j - 2]; const b = index_offset + mesh.indices[j - 1]; const c = index_offset + mesh.indices[j]; + const pa = this.builder.get_position(a); + const pb = this.builder.get_position(b); + const pc = this.builder.get_position(c); + const na = this.builder.get_normal(a); + const nb = this.builder.get_normal(b); + const nc = this.builder.get_normal(c); + + // Calculate a surface normal and reverse the vertex winding if at least 2 of the + // vertex normals point in the opposite direction. This hack fixes the winding for + // most models. + const normal = pb.clone().sub(pa).cross(pc.clone().sub(pa)); if (clockwise) { - this.builder.triangle(b, a, c); + normal.negate(); + } + + const opposite_count = + (normal.dot(na) < 0 ? 1 : 0) + + (normal.dot(nb) < 0 ? 1 : 0) + + (normal.dot(nc) < 0 ? 1 : 0); + + if (opposite_count >= 2) { + clockwise = !clockwise; + } + + if (clockwise) { + this.builder.add_index(b); + this.builder.add_index(a); + this.builder.add_index(c); } else { - this.builder.triangle(a, b, c); + this.builder.add_index(a); + this.builder.add_index(b); + this.builder.add_index(c); } clockwise = !clockwise; } + + if (mesh.material_properties.texture_id != undefined) { + current_mat_idx = mesh.material_properties.texture_id; + } + + if (mesh.material_properties.src_alpha != undefined) { + current_src_alpha = mesh.material_properties.src_alpha; + } + + if (mesh.material_properties.dst_alpha != undefined) { + current_dst_alpha = mesh.material_properties.dst_alpha; + } + + this.builder.add_group( + start_index_count, + this.builder.index_count - start_index_count, + current_mat_idx, + true, + current_src_alpha !== 4 || current_dst_alpha !== 5, + ); } } } diff --git a/src/core/rendering/conversion/ninja_textures.ts b/src/core/rendering/conversion/ninja_textures.ts index f32c0c4d..5163305d 100644 --- a/src/core/rendering/conversion/ninja_textures.ts +++ b/src/core/rendering/conversion/ninja_textures.ts @@ -8,29 +8,6 @@ import { Texture as ThreeTexture, } from "three"; import { Xvm, XvrTexture } from "../../data_formats/parsing/ninja/texture"; -import { Texture, TextureFormat } from "../Texture"; -import { Gfx } from "../Gfx"; - -export function xvr_texture_to_texture(gfx: Gfx, xvr: XvrTexture): Texture { - let format: TextureFormat; - let data_size: number; - - // Ignore mipmaps. - switch (xvr.format[1]) { - case 6: - format = TextureFormat.RGBA_S3TC_DXT1; - data_size = (xvr.width * xvr.height) / 2; - break; - case 7: - format = TextureFormat.RGBA_S3TC_DXT3; - data_size = xvr.width * xvr.height; - break; - default: - throw new Error(`Format ${xvr.format.join(", ")} not supported.`); - } - - return new Texture(gfx, format, xvr.width, xvr.height, xvr.data.slice(0, data_size)); -} export function xvm_to_three_textures(xvm: Xvm): ThreeTexture[] { return xvm.textures.map(xvr_texture_to_three_texture); diff --git a/src/core/rendering/conversion/ninja_three_geometry.ts b/src/core/rendering/conversion/ninja_three_geometry.ts deleted file mode 100644 index 6c647e9d..00000000 --- a/src/core/rendering/conversion/ninja_three_geometry.ts +++ /dev/null @@ -1,312 +0,0 @@ -import { Bone, BufferGeometry, Euler, Matrix3, Matrix4, Quaternion, Vector2, Vector3 } from "three"; -import { vec3_to_threejs } from "./index"; -import { is_njcm_model, NjModel, NjObject } from "../../data_formats/parsing/ninja"; -import { NjcmModel } from "../../data_formats/parsing/ninja/njcm"; -import { XjModel } from "../../data_formats/parsing/ninja/xj"; -import { GeometryBuilder } from "./GeometryBuilder"; - -const DEFAULT_NORMAL = new Vector3(0, 1, 0); -const DEFAULT_UV = new Vector2(0, 0); -const NO_TRANSLATION = new Vector3(0, 0, 0); -const NO_ROTATION = new Quaternion(0, 0, 0, 1); -const NO_SCALE = new Vector3(1, 1, 1); - -export function ninja_object_to_geometry_builder(object: NjObject, builder: GeometryBuilder): void { - new GeometryCreator(builder).to_geometry_builder(object); -} - -export function ninja_object_to_buffer_geometry(object: NjObject): BufferGeometry { - return new GeometryCreator(new GeometryBuilder()).create_buffer_geometry(object); -} - -type Vertex = { - bone_id: number; - position: Vector3; - normal?: Vector3; - bone_weight: number; - bone_weight_status: number; - calc_continue: boolean; -}; - -class VerticesHolder { - private readonly vertices_stack: Vertex[][] = []; - - put(vertices: Vertex[]): void { - this.vertices_stack.push(vertices); - } - - get(index: number): Vertex[] { - const vertices: Vertex[] = []; - - for (let i = this.vertices_stack.length - 1; i >= 0; i--) { - const vertex = this.vertices_stack[i][index]; - - if (vertex) { - vertices.push(vertex); - } - } - - return vertices; - } -} - -class GeometryCreator { - private readonly vertices = new VerticesHolder(); - private readonly builder: GeometryBuilder; - private bone_id = 0; - - constructor(builder: GeometryBuilder) { - this.builder = builder; - } - - to_geometry_builder(object: NjObject): void { - this.object_to_geometry(object, undefined, new Matrix4()); - } - - create_buffer_geometry(object: NjObject): BufferGeometry { - this.to_geometry_builder(object); - return this.builder.build(); - } - - private object_to_geometry( - object: NjObject, - parent_bone: Bone | undefined, - parent_matrix: Matrix4, - ): void { - const { - no_translate, - no_rotate, - no_scale, - hidden, - break_child_trace, - zxy_rotation_order, - skip, - } = object.evaluation_flags; - const { position, rotation, scale } = object; - - const euler = new Euler( - rotation.x, - rotation.y, - rotation.z, - zxy_rotation_order ? "ZXY" : "ZYX", - ); - const matrix = new Matrix4() - .compose( - no_translate ? NO_TRANSLATION : vec3_to_threejs(position), - no_rotate ? NO_ROTATION : new Quaternion().setFromEuler(euler), - no_scale ? NO_SCALE : vec3_to_threejs(scale), - ) - .premultiply(parent_matrix); - - let bone: Bone | undefined; - - if (skip) { - bone = parent_bone; - } else { - bone = new Bone(); - bone.name = this.bone_id.toString(); - - bone.position.set(position.x, position.y, position.z); - bone.setRotationFromEuler(euler); - bone.scale.set(scale.x, scale.y, scale.z); - - this.builder.add_bone(bone); - - if (parent_bone) { - parent_bone.add(bone); - } - } - - if (object.model && !hidden) { - this.model_to_geometry(object.model, matrix); - } - - this.bone_id++; - - if (!break_child_trace) { - for (const child of object.children) { - this.object_to_geometry(child, bone, matrix); - } - } - } - - private model_to_geometry(model: NjModel, matrix: Matrix4): void { - if (is_njcm_model(model)) { - this.njcm_model_to_geometry(model, matrix); - } else { - this.xj_model_to_geometry(model, matrix); - } - } - - private njcm_model_to_geometry(model: NjcmModel, matrix: Matrix4): void { - const normal_matrix = new Matrix3().getNormalMatrix(matrix); - - const new_vertices = model.vertices.map(vertex => { - const position = vec3_to_threejs(vertex.position); - const normal = vertex.normal ? vec3_to_threejs(vertex.normal) : new Vector3(0, 1, 0); - - position.applyMatrix4(matrix); - normal.applyMatrix3(normal_matrix); - - return { - bone_id: this.bone_id, - position, - normal, - bone_weight: vertex.bone_weight, - bone_weight_status: vertex.bone_weight_status, - calc_continue: vertex.calc_continue, - }; - }); - - this.vertices.put(new_vertices); - - for (const mesh of model.meshes) { - const start_index_count = this.builder.index_count; - - for (let i = 0; i < mesh.vertices.length; ++i) { - const mesh_vertex = mesh.vertices[i]; - const vertices = this.vertices.get(mesh_vertex.index); - - if (vertices.length) { - const vertex = vertices[0]; - const normal = vertex.normal ?? mesh_vertex.normal ?? DEFAULT_NORMAL; - const index = this.builder.vertex_count; - - this.builder.add_vertex( - vertex.position, - normal, - mesh.has_tex_coords ? mesh_vertex.tex_coords! : DEFAULT_UV, - ); - - if (i >= 2) { - if (i % 2 === (mesh.clockwise_winding ? 1 : 0)) { - this.builder.add_index(index - 2); - this.builder.add_index(index - 1); - this.builder.add_index(index); - } else { - this.builder.add_index(index - 2); - this.builder.add_index(index); - this.builder.add_index(index - 1); - } - } - - const bones = [ - [0, 0], - [0, 0], - [0, 0], - [0, 0], - ]; - - for (let j = vertices.length - 1; j >= 0; j--) { - const vertex = vertices[j]; - bones[vertex.bone_weight_status] = [vertex.bone_id, vertex.bone_weight]; - } - - const total_weight = bones.reduce((total, [, weight]) => total + weight, 0); - - for (const [bone_index, bone_weight] of bones) { - this.builder.add_bone_weight( - bone_index, - total_weight > 0 ? bone_weight / total_weight : bone_weight, - ); - } - } - } - - this.builder.add_group( - start_index_count, - this.builder.index_count - start_index_count, - mesh.texture_id, - mesh.use_alpha, - mesh.src_alpha !== 4 || mesh.dst_alpha !== 5, - ); - } - } - - private xj_model_to_geometry(model: XjModel, matrix: Matrix4): void { - const index_offset = this.builder.vertex_count; - const normal_matrix = new Matrix3().getNormalMatrix(matrix); - - for (const { position, normal, uv } of model.vertices) { - const p = vec3_to_threejs(position).applyMatrix4(matrix); - - const local_n = normal ? vec3_to_threejs(normal) : new Vector3(0, 1, 0); - const n = local_n.applyMatrix3(normal_matrix); - - const tuv = uv || DEFAULT_UV; - - this.builder.add_vertex(p, n, tuv); - } - - let current_mat_idx: number | undefined; - let current_src_alpha: number | undefined; - let current_dst_alpha: number | undefined; - - for (const mesh of model.meshes) { - const start_index_count = this.builder.index_count; - let clockwise = false; - - for (let j = 2; j < mesh.indices.length; ++j) { - const a = index_offset + mesh.indices[j - 2]; - const b = index_offset + mesh.indices[j - 1]; - const c = index_offset + mesh.indices[j]; - const pa = this.builder.get_position(a); - const pb = this.builder.get_position(b); - const pc = this.builder.get_position(c); - const na = this.builder.get_normal(a); - const nb = this.builder.get_normal(b); - const nc = this.builder.get_normal(c); - - // Calculate a surface normal and reverse the vertex winding if at least 2 of the - // vertex normals point in the opposite direction. This hack fixes the winding for - // most models. - const normal = pb.clone().sub(pa).cross(pc.clone().sub(pa)); - - if (clockwise) { - normal.negate(); - } - - const opposite_count = - (normal.dot(na) < 0 ? 1 : 0) + - (normal.dot(nb) < 0 ? 1 : 0) + - (normal.dot(nc) < 0 ? 1 : 0); - - if (opposite_count >= 2) { - clockwise = !clockwise; - } - - if (clockwise) { - this.builder.add_index(b); - this.builder.add_index(a); - this.builder.add_index(c); - } else { - this.builder.add_index(a); - this.builder.add_index(b); - this.builder.add_index(c); - } - - clockwise = !clockwise; - } - - if (mesh.material_properties.texture_id != undefined) { - current_mat_idx = mesh.material_properties.texture_id; - } - - if (mesh.material_properties.src_alpha != undefined) { - current_src_alpha = mesh.material_properties.src_alpha; - } - - if (mesh.material_properties.dst_alpha != undefined) { - current_dst_alpha = mesh.material_properties.dst_alpha; - } - - this.builder.add_group( - start_index_count, - this.builder.index_count - start_index_count, - current_mat_idx, - true, - current_src_alpha !== 4 || current_dst_alpha !== 5, - ); - } - } -} diff --git a/src/core/rendering/meshes.ts b/src/core/rendering/meshes.ts deleted file mode 100644 index 9c9a73fa..00000000 --- a/src/core/rendering/meshes.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Mesh } from "./Mesh"; -import { VertexFormatType } from "./VertexFormat"; -import { Vec3 } from "../math/linear_algebra"; - -export function cube_mesh(): Mesh { - return ( - Mesh.builder(VertexFormatType.PosNorm) - // Front - .vertex(new Vec3(1, 1, -1), new Vec3(0, 0, -1)) - .vertex(new Vec3(-1, 1, -1), new Vec3(0, 0, -1)) - .vertex(new Vec3(-1, -1, -1), new Vec3(0, 0, -1)) - .vertex(new Vec3(1, -1, -1), new Vec3(0, 0, -1)) - .triangle(0, 1, 2) - .triangle(0, 2, 3) - - // Back - .vertex(new Vec3(1, 1, 1), new Vec3(0, 0, 1)) - .vertex(new Vec3(1, -1, 1), new Vec3(0, 0, 1)) - .vertex(new Vec3(-1, -1, 1), new Vec3(0, 0, 1)) - .vertex(new Vec3(-1, 1, 1), new Vec3(0, 0, 1)) - .triangle(4, 5, 6) - .triangle(4, 6, 7) - - // Top - .vertex(new Vec3(1, 1, 1), new Vec3(0, 1, 0)) - .vertex(new Vec3(-1, 1, 1), new Vec3(0, 1, 0)) - .vertex(new Vec3(-1, 1, -1), new Vec3(0, 1, 0)) - .vertex(new Vec3(1, 1, -1), new Vec3(0, 1, 0)) - .triangle(8, 9, 10) - .triangle(8, 10, 11) - - // Bottom - .vertex(new Vec3(1, -1, 1), new Vec3(0, -1, 0)) - .vertex(new Vec3(1, -1, -1), new Vec3(0, -1, 0)) - .vertex(new Vec3(-1, -1, -1), new Vec3(0, -1, 0)) - .vertex(new Vec3(-1, -1, 1), new Vec3(0, -1, 0)) - .triangle(12, 13, 14) - .triangle(12, 14, 15) - - // Right - .vertex(new Vec3(1, 1, 1), new Vec3(1, 0, 0)) - .vertex(new Vec3(1, 1, -1), new Vec3(1, 0, 0)) - .vertex(new Vec3(1, -1, -1), new Vec3(1, 0, 0)) - .vertex(new Vec3(1, -1, 1), new Vec3(1, 0, 0)) - .triangle(16, 17, 18) - .triangle(16, 18, 19) - - // Left - .vertex(new Vec3(-1, 1, 1), new Vec3(-1, 0, 0)) - .vertex(new Vec3(-1, -1, 1), new Vec3(-1, 0, 0)) - .vertex(new Vec3(-1, -1, -1), new Vec3(-1, 0, 0)) - .vertex(new Vec3(-1, 1, -1), new Vec3(-1, 0, 0)) - .triangle(20, 21, 22) - .triangle(20, 22, 23) - - .build() - ); -} diff --git a/src/core/rendering/webgl/WebglGfx.ts b/src/core/rendering/webgl/WebglGfx.ts deleted file mode 100644 index 1a2d92db..00000000 --- a/src/core/rendering/webgl/WebglGfx.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { Gfx } from "../Gfx"; -import { Texture, TextureFormat } from "../Texture"; -import { - VERTEX_FORMATS, - VERTEX_NORMAL_LOC, - VERTEX_POS_LOC, - VERTEX_TEX_LOC, - VertexFormatType, -} from "../VertexFormat"; - -export type WebglMesh = { - readonly vao: WebGLVertexArrayObject; - readonly vertex_buffer: WebGLBuffer; - readonly index_buffer: WebGLBuffer; -}; - -export class WebglGfx implements Gfx { - constructor(private readonly gl: WebGL2RenderingContext) {} - - create_gfx_mesh( - format_type: VertexFormatType, - vertex_data: ArrayBuffer, - index_data: ArrayBuffer, - texture?: Texture, - ): WebglMesh { - const gl = this.gl; - let vao: WebGLVertexArrayObject | null = null; - let vertex_buffer: WebGLBuffer | null = null; - let index_buffer: WebGLBuffer | null = null; - - try { - vao = gl.createVertexArray(); - if (vao == null) throw new Error("Failed to create VAO."); - - vertex_buffer = gl.createBuffer(); - if (vertex_buffer == null) throw new Error("Failed to create vertex buffer."); - - index_buffer = gl.createBuffer(); - if (index_buffer == null) throw new Error("Failed to create index buffer."); - - gl.bindVertexArray(vao); - - // Vertex data. - gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); - gl.bufferData(gl.ARRAY_BUFFER, vertex_data, gl.STATIC_DRAW); - - const format = VERTEX_FORMATS[format_type]; - const vertex_size = format.size; - - gl.vertexAttribPointer(VERTEX_POS_LOC, 3, gl.FLOAT, true, vertex_size, 0); - gl.enableVertexAttribArray(VERTEX_POS_LOC); - - if (format.normal_offset != undefined) { - gl.vertexAttribPointer( - VERTEX_NORMAL_LOC, - 3, - gl.FLOAT, - true, - vertex_size, - format.normal_offset, - ); - gl.enableVertexAttribArray(VERTEX_NORMAL_LOC); - } - - if (format.tex_offset != undefined) { - gl.vertexAttribPointer( - VERTEX_TEX_LOC, - 2, - gl.UNSIGNED_SHORT, - true, - vertex_size, - format.tex_offset, - ); - gl.enableVertexAttribArray(VERTEX_TEX_LOC); - } - - gl.bindBuffer(gl.ARRAY_BUFFER, null); - - // Index data. - gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer); - gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index_data, gl.STATIC_DRAW); - - gl.bindVertexArray(null); - - texture?.upload(); - - return { - vao, - vertex_buffer, - index_buffer, - }; - } catch (e) { - gl.deleteVertexArray(vao); - gl.deleteBuffer(vertex_buffer); - gl.deleteBuffer(index_buffer); - throw e; - } - } - - destroy_gfx_mesh(gfx_mesh?: WebglMesh): void { - if (gfx_mesh) { - const gl = this.gl; - gl.deleteVertexArray(gfx_mesh.vao); - gl.deleteBuffer(gfx_mesh.vertex_buffer); - gl.deleteBuffer(gfx_mesh.index_buffer); - } - } - - create_texture( - format: TextureFormat, - width: number, - height: number, - data: ArrayBuffer, - ): WebGLTexture { - const gl = this.gl; - - const ext = gl.getExtension("WEBGL_compressed_texture_s3tc"); - - if (!ext) { - throw new Error("Extension WEBGL_compressed_texture_s3tc not supported."); - } - - const gl_texture = gl.createTexture(); - if (gl_texture == null) throw new Error("Failed to create texture."); - - let gl_format: GLenum; - - switch (format) { - case TextureFormat.RGBA_S3TC_DXT1: - gl_format = ext.COMPRESSED_RGBA_S3TC_DXT1_EXT; - break; - case TextureFormat.RGBA_S3TC_DXT3: - gl_format = ext.COMPRESSED_RGBA_S3TC_DXT3_EXT; - break; - } - - gl.bindTexture(gl.TEXTURE_2D, gl_texture); - gl.compressedTexImage2D( - gl.TEXTURE_2D, - 0, - gl_format, - width, - height, - 0, - new Uint8Array(data), - ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.bindTexture(gl.TEXTURE_2D, null); - - return gl_texture; - } - - destroy_texture(texture?: WebGLTexture): void { - if (texture != undefined) { - this.gl.deleteTexture(texture); - } - } -} diff --git a/src/core/rendering/webgl/WebglRenderer.ts b/src/core/rendering/webgl/WebglRenderer.ts deleted file mode 100644 index c47a04c4..00000000 --- a/src/core/rendering/webgl/WebglRenderer.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Mat4, mat4_multiply } from "../../math/linear_algebra"; -import { ShaderProgram } from "../ShaderProgram"; -import pos_norm_vert_shader_source from "./pos_norm.vert"; -import pos_norm_frag_shader_source from "./pos_norm.frag"; -import pos_tex_vert_shader_source from "./pos_tex.vert"; -import pos_tex_frag_shader_source from "./pos_tex.frag"; -import { GfxRenderer } from "../GfxRenderer"; -import { WebglGfx, WebglMesh } from "./WebglGfx"; -import { Projection } from "../Camera"; -import { VertexFormatType } from "../VertexFormat"; -import { SceneNode } from "../Scene"; - -export class WebglRenderer extends GfxRenderer { - private readonly gl: WebGL2RenderingContext; - private readonly shader_programs: ShaderProgram[]; - - readonly gfx: WebglGfx; - - constructor(projection: Projection) { - super(document.createElement("canvas"), projection); - - const gl = this.canvas_element.getContext("webgl2"); - - if (gl == null) { - throw new Error("Failed to initialize webgl2 context."); - } - - this.gl = gl; - this.gfx = new WebglGfx(gl); - - gl.enable(gl.DEPTH_TEST); - gl.clearColor(0.1, 0.1, 0.1, 1); - - this.shader_programs = []; - this.shader_programs[VertexFormatType.PosNorm] = new ShaderProgram( - gl, - pos_norm_vert_shader_source, - pos_norm_frag_shader_source, - ); - this.shader_programs[VertexFormatType.PosTex] = new ShaderProgram( - gl, - pos_tex_vert_shader_source, - pos_tex_frag_shader_source, - ); - - this.set_size(800, 600); - } - - dispose(): void { - for (const program of this.shader_programs) { - program.delete(); - } - - super.dispose(); - } - - set_size(width: number, height: number): void { - this.canvas_element.width = width; - this.canvas_element.height = height; - this.gl.viewport(0, 0, width, height); - - super.set_size(width, height); - } - - protected render(): void { - const gl = this.gl; - - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - - // this.render_node(this.scene.root_node, this.camera.view_matrix); - - this.scene.traverse((node, parent_mat) => { - const mat = mat4_multiply(parent_mat, node.transform); - - if (node.mesh) { - const program = this.shader_programs[node.mesh.format]; - program.bind(); - - program.set_mat_projection_uniform(this.camera.projection_matrix); - program.set_mat_model_view_uniform(mat); - program.set_mat_normal_uniform(mat.normal_mat3()); - - if (node.mesh.texture?.gfx_texture) { - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, node.mesh.texture.gfx_texture as WebGLTexture); - program.set_texture_uniform(gl.TEXTURE0); - } - - const gfx_mesh = node.mesh.gfx_mesh as WebglMesh; - gl.bindVertexArray(gfx_mesh.vao); - gl.drawElements(gl.TRIANGLES, node.mesh.index_count, gl.UNSIGNED_SHORT, 0); - gl.bindVertexArray(null); - - gl.bindTexture(gl.TEXTURE_2D, null); - - program.unbind(); - } - - return mat; - }, this.camera.view_matrix); - } - - private render_node(node: SceneNode, parent_mat: Mat4): void { - const gl = this.gl; - const mat = mat4_multiply(parent_mat, node.transform); - - if (node.mesh) { - const program = this.shader_programs[node.mesh.format]; - program.bind(); - - program.set_mat_projection_uniform(this.camera.projection_matrix); - program.set_mat_model_view_uniform(mat); - program.set_mat_normal_uniform(mat.normal_mat3()); - - if (node.mesh.texture?.gfx_texture) { - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, node.mesh.texture.gfx_texture as WebGLTexture); - program.set_texture_uniform(gl.TEXTURE0); - } - - const gfx_mesh = node.mesh.gfx_mesh as WebglMesh; - gl.bindVertexArray(gfx_mesh.vao); - gl.drawElements(gl.TRIANGLES, node.mesh.index_count, gl.UNSIGNED_SHORT, 0); - gl.bindVertexArray(null); - - gl.bindTexture(gl.TEXTURE_2D, null); - - program.unbind(); - } - - for (const child of node.children) { - this.render_node(child, mat); - } - } -} diff --git a/src/core/rendering/webgl/pos_norm.frag b/src/core/rendering/webgl/pos_norm.frag deleted file mode 100644 index 0a1e9a3b..00000000 --- a/src/core/rendering/webgl/pos_norm.frag +++ /dev/null @@ -1,23 +0,0 @@ -#version 300 es - -precision mediump float; - -const vec3 light_pos = normalize(vec3(-1, 1, 1)); -const vec4 sky_color = vec4(1, 1, 1, 1); -const vec4 ground_color = vec4(0.1, 0.1, 0.1, 1); - -in vec3 frag_normal; - -out vec4 frag_color; - -void main() { - float cos0 = dot(frag_normal, light_pos); - float a = 0.5 + 0.5 * cos0; - float a_back = 1.0 - a; - - if (gl_FrontFacing) { - frag_color = mix(ground_color, sky_color, a); - } else { - frag_color = mix(ground_color, sky_color, a_back); - } -} diff --git a/src/core/rendering/webgl/pos_norm.vert b/src/core/rendering/webgl/pos_norm.vert deleted file mode 100644 index 12ae3eb4..00000000 --- a/src/core/rendering/webgl/pos_norm.vert +++ /dev/null @@ -1,17 +0,0 @@ -#version 300 es - -precision mediump float; - -uniform mat4 mat_projection; -uniform mat4 mat_model_view; -uniform mat3 mat_normal; - -in vec4 pos; -in vec3 normal; - -out vec3 frag_normal; - -void main() { - gl_Position = mat_projection * mat_model_view * pos; - frag_normal = normalize(mat_normal * normal); -} diff --git a/src/core/rendering/webgl/pos_tex.frag b/src/core/rendering/webgl/pos_tex.frag deleted file mode 100644 index b87590cd..00000000 --- a/src/core/rendering/webgl/pos_tex.frag +++ /dev/null @@ -1,13 +0,0 @@ -#version 300 es - -precision mediump float; - -uniform sampler2D tex_sampler; - -in vec2 f_tex; - -out vec4 frag_color; - -void main() { - frag_color = texture(tex_sampler, f_tex); -} diff --git a/src/core/rendering/webgl/pos_tex.vert b/src/core/rendering/webgl/pos_tex.vert deleted file mode 100644 index b563b1b3..00000000 --- a/src/core/rendering/webgl/pos_tex.vert +++ /dev/null @@ -1,16 +0,0 @@ -#version 300 es - -precision mediump float; - -uniform mat4 mat_projection; -uniform mat4 mat_model_view; - -in vec4 pos; -in vec2 tex; - -out vec2 f_tex; - -void main() { - gl_Position = mat_projection * mat_model_view * pos; - f_tex = tex; -} diff --git a/src/core/rendering/webgl/shader_sources.ts b/src/core/rendering/webgl/shader_sources.ts deleted file mode 100644 index 43d87ec7..00000000 --- a/src/core/rendering/webgl/shader_sources.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const POS_VERTEX_SHADER_SOURCE = ` -`; - -export const POS_FRAG_SHADER_SOURCE = ``; - -export const POS_TEX_VERTEX_SHADER_SOURCE = ``; - -export const POS_TEX_FRAG_SHADER_SOURCE = ``; diff --git a/src/core/rendering/webgpu/ShaderLoader.ts b/src/core/rendering/webgpu/ShaderLoader.ts deleted file mode 100644 index ecba9a8a..00000000 --- a/src/core/rendering/webgpu/ShaderLoader.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { HttpClient } from "../../HttpClient"; - -export class ShaderLoader { - constructor(private readonly http_client: HttpClient) {} - - async load(name: string): Promise { - return new Uint32Array(await this.http_client.get(`/shaders/${name}.spv`).array_buffer()); - } -} diff --git a/src/core/rendering/webgpu/WebgpuGfx.ts b/src/core/rendering/webgpu/WebgpuGfx.ts deleted file mode 100644 index 2a172cc3..00000000 --- a/src/core/rendering/webgpu/WebgpuGfx.ts +++ /dev/null @@ -1,187 +0,0 @@ -import { Gfx } from "../Gfx"; -import { Texture, TextureFormat } from "../Texture"; -import { VERTEX_FORMATS, VertexFormatType } from "../VertexFormat"; -import { assert } from "../../util"; - -export type WebgpuMesh = { - readonly uniform_buffer: GPUBuffer; - readonly bind_group: GPUBindGroup; - readonly vertex_buffer: GPUBuffer; - readonly index_buffer: GPUBuffer; -}; - -export class WebgpuGfx implements Gfx { - constructor( - private readonly device: GPUDevice, - private readonly bind_group_layouts: readonly GPUBindGroupLayout[], - ) {} - - create_gfx_mesh( - format_type: VertexFormatType, - vertex_data: ArrayBuffer, - index_data: ArrayBuffer, - texture?: Texture, - ): WebgpuMesh { - const format = VERTEX_FORMATS[format_type]; - - const uniform_buffer = this.device.createBuffer({ - size: format.uniform_buffer_size, - usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.UNIFORM, // eslint-disable-line no-undef - }); - - const bind_group_entries: GPUBindGroupEntry[] = [ - { - binding: 0, - resource: { - buffer: uniform_buffer, - }, - }, - ]; - - if (format.tex_offset != undefined) { - assert( - texture, - () => `Vertex format ${VertexFormatType[format_type]} requires a texture.`, - ); - - bind_group_entries.push( - { - binding: 1, - resource: this.device.createSampler({ - magFilter: "linear", - minFilter: "linear", - }), - }, - { - binding: 2, - resource: (texture.gfx_texture as GPUTexture).createView(), - }, - ); - } - - const bind_group = this.device.createBindGroup({ - layout: this.bind_group_layouts[format_type], - entries: bind_group_entries, - }); - - const [vertex_buffer, vertex_array_buffer] = this.device.createBufferMapped({ - size: vertex_data.byteLength, - usage: GPUBufferUsage.VERTEX, // eslint-disable-line no-undef - }); - new Uint8Array(vertex_array_buffer).set(new Uint8Array(vertex_data)); - vertex_buffer.unmap(); - - const [index_buffer, index_array_buffer] = this.device.createBufferMapped({ - size: index_data.byteLength, - usage: GPUBufferUsage.INDEX, // eslint-disable-line no-undef - }); - new Uint8Array(index_array_buffer).set(new Uint8Array(index_data)); - index_buffer.unmap(); - - return { - uniform_buffer, - bind_group, - vertex_buffer, - index_buffer, - }; - } - - destroy_gfx_mesh(gfx_mesh?: WebgpuMesh): void { - if (gfx_mesh) { - gfx_mesh.uniform_buffer.destroy(); - gfx_mesh.vertex_buffer.destroy(); - gfx_mesh.index_buffer.destroy(); - } - } - - create_texture( - format: TextureFormat, - width: number, - height: number, - data: ArrayBuffer, - ): GPUTexture { - let texture_format: string; - let bytes_per_pixel: number; - - switch (format) { - case TextureFormat.RGBA_S3TC_DXT1: - texture_format = "bc1-rgba-unorm"; - bytes_per_pixel = 2; - break; - - case TextureFormat.RGBA_S3TC_DXT3: - texture_format = "bc2-rgba-unorm"; - bytes_per_pixel = 4; - break; - } - - const texture = this.device.createTexture({ - size: { - width, - height, - depth: 1, - }, - format: (texture_format as any) as GPUTextureFormat, - usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.SAMPLED, // eslint-disable-line no-undef - }); - - // Bytes per row must be a multiple of 256. - const bytes_per_row = Math.ceil((4 * width) / 256) * 256; - const data_size = bytes_per_row * height; - - let buffer_data: Uint8Array; - - if (data_size === data.byteLength) { - buffer_data = new Uint8Array(data); - } else { - buffer_data = new Uint8Array(data_size); - const orig_data = new Uint8Array(data); - let orig_idx = 0; - - for (let y = 0; y < height; y++) { - for (let x = 0; x < width; x++) { - const idx = bytes_per_pixel * x + bytes_per_row * y; - - for (let i = 0; i < bytes_per_pixel; i++) { - buffer_data[idx + i] = orig_data[orig_idx + i]; - } - - orig_idx += bytes_per_pixel; - } - } - } - - const [buffer, array_buffer] = this.device.createBufferMapped({ - size: data_size, - usage: GPUBufferUsage.COPY_SRC, // eslint-disable-line no-undef - }); - new Uint8Array(array_buffer).set(buffer_data); - buffer.unmap(); - - const command_encoder = this.device.createCommandEncoder(); - command_encoder.copyBufferToTexture( - { - buffer, - bytesPerRow: bytes_per_row, - rowsPerImage: 0, - }, - { - texture, - }, - { - width, - height, - depth: 1, - }, - ); - this.device.defaultQueue.submit([command_encoder.finish()]); - - buffer.destroy(); - - return texture; - } - - destroy_texture(texture?: GPUTexture): void { - texture?.destroy(); - } -} diff --git a/src/core/rendering/webgpu/WebgpuRenderer.ts b/src/core/rendering/webgpu/WebgpuRenderer.ts deleted file mode 100644 index 5c41d4fa..00000000 --- a/src/core/rendering/webgpu/WebgpuRenderer.ts +++ /dev/null @@ -1,326 +0,0 @@ -import { - VERTEX_FORMATS, - VERTEX_NORMAL_LOC, - VERTEX_POS_LOC, - VERTEX_TEX_LOC, - VertexFormat, - VertexFormatType, -} from "../VertexFormat"; -import { GfxRenderer } from "../GfxRenderer"; -import { Mat4, mat4_multiply } from "../../math/linear_algebra"; -import { WebgpuGfx, WebgpuMesh } from "./WebgpuGfx"; -import { ShaderLoader } from "./ShaderLoader"; -import { HttpClient } from "../../HttpClient"; -import { Projection } from "../Camera"; -import { Mesh } from "../Mesh"; - -type PipelineDetails = { - readonly pipeline: GPURenderPipeline; - readonly bind_group_layout: GPUBindGroupLayout; -}; - -export async function create_webgpu_renderer( - projection: Projection, - http_client: HttpClient, -): Promise { - if (window.navigator.gpu == undefined) { - throw new Error("WebGPU not supported on this device."); - } - - const canvas_element = document.createElement("canvas"); - const context = canvas_element.getContext("gpupresent") as GPUCanvasContext | null; - - if (context == null) { - throw new Error("Failed to initialize gpupresent context."); - } - - const adapter = await window.navigator.gpu.requestAdapter(); - const device = await adapter.requestDevice({ - extensions: (["textureCompressionBC"] as any) as GPUExtensionName[], - }); - const shader_loader = new ShaderLoader(http_client); - - const texture_format = "bgra8unorm"; - - const swap_chain = context.configureSwapChain({ - device, - format: texture_format, - }); - - const pipelines: PipelineDetails[] = await Promise.all( - VERTEX_FORMATS.map(format => - create_pipeline(format, device, texture_format, shader_loader), - ), - ); - - return new WebgpuRenderer(canvas_element, projection, device, swap_chain, pipelines); -} - -async function create_pipeline( - format: VertexFormat, - device: GPUDevice, - texture_format: GPUTextureFormat, - shader_loader: ShaderLoader, -): Promise { - const bind_group_layout_entries: GPUBindGroupLayoutEntry[] = [ - { - binding: 0, - visibility: GPUShaderStage.VERTEX, // eslint-disable-line no-undef - type: "uniform-buffer", - }, - ]; - - if (format.tex_offset != undefined) { - bind_group_layout_entries.push( - { - binding: 1, - visibility: GPUShaderStage.FRAGMENT, // eslint-disable-line no-undef - type: "sampler", - }, - { - binding: 2, - visibility: GPUShaderStage.FRAGMENT, // eslint-disable-line no-undef - type: "sampled-texture", - }, - ); - } - - const bind_group_layout = device.createBindGroupLayout({ - entries: bind_group_layout_entries, - }); - - let shader_name: string; - - switch (format.type) { - case VertexFormatType.PosNorm: - shader_name = "pos_norm"; - break; - case VertexFormatType.PosTex: - shader_name = "pos_tex"; - break; - case VertexFormatType.PosNormTex: - shader_name = "pos_norm_tex"; - break; - } - - const vertex_shader_source = await shader_loader.load(`${shader_name}.vert`); - const fragment_shader_source = await shader_loader.load(`${shader_name}.frag`); - - const vertex_attributes: GPUVertexAttributeDescriptor[] = [ - { - format: "float3", - offset: 0, - shaderLocation: VERTEX_POS_LOC, - }, - ]; - - if (format.normal_offset != undefined) { - vertex_attributes.push({ - format: "float3", - offset: format.normal_offset, - shaderLocation: VERTEX_NORMAL_LOC, - }); - } - - if (format.tex_offset != undefined) { - vertex_attributes.push({ - format: "ushort2norm", - offset: format.tex_offset, - shaderLocation: VERTEX_TEX_LOC, - }); - } - - const pipeline = device.createRenderPipeline({ - layout: device.createPipelineLayout({ bindGroupLayouts: [bind_group_layout] }), - vertexStage: { - module: device.createShaderModule({ - code: vertex_shader_source, - }), - entryPoint: "main", - }, - fragmentStage: { - module: device.createShaderModule({ - code: fragment_shader_source, - }), - entryPoint: "main", - }, - primitiveTopology: "triangle-list", - colorStates: [{ format: texture_format }], - depthStencilState: { - format: "depth24plus", - depthWriteEnabled: true, - depthCompare: "less", - }, - vertexState: { - indexFormat: "uint16", - vertexBuffers: [ - { - arrayStride: format.size, - stepMode: "vertex", - attributes: vertex_attributes, - }, - ], - }, - }); - - return { pipeline, bind_group_layout }; -} - -/** - * Uses the experimental WebGPU API for rendering. - */ -export class WebgpuRenderer extends GfxRenderer { - private disposed: boolean = false; - private depth_texture!: GPUTexture; - - readonly gfx: WebgpuGfx; - - constructor( - canvas_element: HTMLCanvasElement, - projection: Projection, - private readonly device: GPUDevice, - private readonly swap_chain: GPUSwapChain, - private readonly pipelines: readonly { - pipeline: GPURenderPipeline; - bind_group_layout: GPUBindGroupLayout; - }[], - ) { - super(canvas_element, projection); - - this.gfx = new WebgpuGfx( - device, - pipelines.map(p => p.bind_group_layout), - ); - - this.set_size(this.width, this.height); - } - - dispose(): void { - this.disposed = true; - - this.depth_texture.destroy(); - - super.dispose(); - } - - set_size(width: number, height: number): void { - this.canvas_element.width = width; - this.canvas_element.height = height; - - this.depth_texture?.destroy(); - this.depth_texture = this.device.createTexture({ - size: { - width, - height, - depth: 1, - }, - format: "depth24plus", - usage: GPUTextureUsage.OUTPUT_ATTACHMENT | GPUTextureUsage.COPY_SRC, // eslint-disable-line no-undef - }); - - super.set_size(width, height); - } - - protected render(): void { - const command_encoder = this.device.createCommandEncoder(); - - // Traverse the scene graph and sort the meshes into vertex format-specific buckets. - const draw_data: { mesh: Mesh; mvp_mat: Mat4 }[][] = VERTEX_FORMATS.map(() => []); - - const camera_project_mat = mat4_multiply( - this.camera.projection_matrix, - this.camera.view_matrix, - ); - - let uniform_buffer_size = 0; - - this.scene.traverse((node, parent_mat) => { - const mat = mat4_multiply(parent_mat, node.transform); - - if (node.mesh) { - uniform_buffer_size += VERTEX_FORMATS[node.mesh.format].uniform_buffer_size; - - draw_data[node.mesh.format].push({ - mesh: node.mesh, - mvp_mat: mat, - }); - } - - return mat; - }, camera_project_mat); - - let uniform_buffer: GPUBuffer | undefined; - - // Upload uniform data. - if (uniform_buffer_size > 0) { - let uniform_array_buffer: ArrayBuffer; - - [uniform_buffer, uniform_array_buffer] = this.device.createBufferMapped({ - size: uniform_buffer_size, - usage: GPUBufferUsage.COPY_SRC, // eslint-disable-line no-undef - }); - - const uniform_array = new Float32Array(uniform_array_buffer); - let uniform_buffer_pos = 0; - - for (const vertex_format of VERTEX_FORMATS) { - for (const { mesh, mvp_mat } of draw_data[vertex_format.type]) { - const copy_pos = 4 * uniform_buffer_pos; - uniform_array.set(mvp_mat.data, uniform_buffer_pos); - uniform_buffer_pos += mvp_mat.data.length; - - if (vertex_format.normal_offset != undefined) { - const normal_mat = mvp_mat.normal_mat3(); - uniform_array.set(normal_mat.data, uniform_buffer_pos); - uniform_buffer_pos += normal_mat.data.length; - } - - command_encoder.copyBufferToBuffer( - uniform_buffer, - copy_pos, - (mesh.gfx_mesh as WebgpuMesh).uniform_buffer, - 0, - vertex_format.uniform_buffer_size, - ); - } - } - - uniform_buffer.unmap(); - } - - const texture_view = this.swap_chain.getCurrentTexture().createView(); - const pass_encoder = command_encoder.beginRenderPass({ - colorAttachments: [ - { - attachment: texture_view, - loadValue: { r: 0.1, g: 0.1, b: 0.1, a: 1 }, - }, - ], - depthStencilAttachment: { - attachment: this.depth_texture.createView(), - depthLoadValue: 1, - depthStoreOp: "store", - stencilLoadValue: "load", - stencilStoreOp: "store", - }, - }); - - // Render all meshes per vertex format. - for (const vertex_format of VERTEX_FORMATS) { - pass_encoder.setPipeline(this.pipelines[vertex_format.type].pipeline); - - for (const { mesh } of draw_data[vertex_format.type]) { - const gfx_mesh = mesh.gfx_mesh as WebgpuMesh; - pass_encoder.setBindGroup(0, gfx_mesh.bind_group); - pass_encoder.setVertexBuffer(0, gfx_mesh.vertex_buffer); - pass_encoder.setIndexBuffer(gfx_mesh.index_buffer); - pass_encoder.drawIndexed(mesh.index_count, 1, 0, 0, 0); - } - } - - pass_encoder.endPass(); - this.device.defaultQueue.submit([command_encoder.finish()]); - - uniform_buffer?.destroy(); - } -} diff --git a/src/index.ts b/src/index.ts index 47c6ca12..625bbf1d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ import "@fortawesome/fontawesome-free/js/brands"; import { initialize_application } from "./application"; import { FetchClient } from "./core/HttpClient"; import { WebGLRenderer } from "three"; -import { DisposableThreeRenderer } from "./core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer } from "./core/rendering/Renderer"; import { Random } from "./core/Random"; import { DateClock } from "./core/Clock"; diff --git a/src/quest_editor/gui/QuestEditorRendererView.ts b/src/quest_editor/gui/QuestEditorRendererView.ts index 56b28186..80a6f39e 100644 --- a/src/quest_editor/gui/QuestEditorRendererView.ts +++ b/src/quest_editor/gui/QuestEditorRendererView.ts @@ -5,7 +5,7 @@ import { QuestRendererView } from "./QuestRendererView"; import { QuestEntityControls } from "../rendering/QuestEntityControls"; import { AreaAssetLoader } from "../loading/AreaAssetLoader"; import { EntityAssetLoader } from "../loading/EntityAssetLoader"; -import { DisposableThreeRenderer } from "../../core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer } from "../../core/rendering/Renderer"; export class QuestEditorRendererView extends QuestRendererView { private readonly entity_controls: QuestEntityControls; diff --git a/src/quest_editor/gui/QuestRunnerRendererView.ts b/src/quest_editor/gui/QuestRunnerRendererView.ts index 7e6d4278..3ee158c8 100644 --- a/src/quest_editor/gui/QuestRunnerRendererView.ts +++ b/src/quest_editor/gui/QuestRunnerRendererView.ts @@ -4,7 +4,7 @@ import { QuestRendererView } from "./QuestRendererView"; import { QuestEditorStore } from "../stores/QuestEditorStore"; import { AreaAssetLoader } from "../loading/AreaAssetLoader"; import { EntityAssetLoader } from "../loading/EntityAssetLoader"; -import { DisposableThreeRenderer } from "../../core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer } from "../../core/rendering/Renderer"; export class QuestRunnerRendererView extends QuestRendererView { constructor( diff --git a/src/quest_editor/index.ts b/src/quest_editor/index.ts index 160b584f..9079b2c1 100644 --- a/src/quest_editor/index.ts +++ b/src/quest_editor/index.ts @@ -7,7 +7,7 @@ import { AreaAssetLoader } from "./loading/AreaAssetLoader"; import { HttpClient } from "../core/HttpClient"; import { EntityImageRenderer } from "./rendering/EntityImageRenderer"; import { EntityAssetLoader } from "./loading/EntityAssetLoader"; -import { DisposableThreeRenderer } from "../core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer } from "../core/rendering/Renderer"; import { QuestEditorUiPersister } from "./persistence/QuestEditorUiPersister"; import { QuestEditorToolBarView } from "./gui/QuestEditorToolBarView"; import { QuestEditorToolBarController } from "./controllers/QuestEditorToolBarController"; diff --git a/src/quest_editor/loading/EntityAssetLoader.ts b/src/quest_editor/loading/EntityAssetLoader.ts index 4bb4effe..3cf50eba 100644 --- a/src/quest_editor/loading/EntityAssetLoader.ts +++ b/src/quest_editor/loading/EntityAssetLoader.ts @@ -2,7 +2,7 @@ import { BufferGeometry, CylinderBufferGeometry, Texture } from "three"; import { LoadingCache } from "./LoadingCache"; import { Endianness } from "../../core/data_formats/Endianness"; import { ArrayBufferCursor } from "../../core/data_formats/cursor/ArrayBufferCursor"; -import { ninja_object_to_buffer_geometry } from "../../core/rendering/conversion/ninja_three_geometry"; +import { ninja_object_to_buffer_geometry } from "../../core/rendering/conversion/ninja_geometry"; import { NjObject, parse_nj, parse_xj } from "../../core/data_formats/parsing/ninja"; import { parse_xvm } from "../../core/data_formats/parsing/ninja/texture"; import { xvm_to_three_textures } from "../../core/rendering/conversion/ninja_textures"; diff --git a/src/quest_editor/rendering/EntityImageRenderer.ts b/src/quest_editor/rendering/EntityImageRenderer.ts index 5929c0d7..d8de6ba6 100644 --- a/src/quest_editor/rendering/EntityImageRenderer.ts +++ b/src/quest_editor/rendering/EntityImageRenderer.ts @@ -11,7 +11,7 @@ import { create_entity_type_mesh } from "./conversion/entities"; import { sequential } from "../../core/sequential"; import { EntityAssetLoader } from "../loading/EntityAssetLoader"; import { Disposable } from "../../core/observable/Disposable"; -import { DisposableThreeRenderer } from "../../core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer } from "../../core/rendering/Renderer"; import { LoadingCache } from "../loading/LoadingCache"; import { DisposablePromise } from "../../core/DisposablePromise"; diff --git a/src/quest_editor/rendering/QuestRenderer.ts b/src/quest_editor/rendering/QuestRenderer.ts index 47264de4..e18c3047 100644 --- a/src/quest_editor/rendering/QuestRenderer.ts +++ b/src/quest_editor/rendering/QuestRenderer.ts @@ -1,4 +1,4 @@ -import { DisposableThreeRenderer, ThreeRenderer } from "../../core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer, Renderer } from "../../core/rendering/Renderer"; import { Group, Mesh, MeshLambertMaterial, Object3D, PerspectiveCamera } from "three"; import { QuestEntityModel } from "../model/QuestEntityModel"; import { Quest3DModelManager } from "./Quest3DModelManager"; @@ -6,7 +6,7 @@ import { Disposer } from "../../core/observable/Disposer"; import { ColorType, EntityUserData, NPC_COLORS, OBJECT_COLORS } from "./conversion/entities"; import { QuestNpcModel } from "../model/QuestNpcModel"; -export class QuestRenderer extends ThreeRenderer { +export class QuestRenderer extends Renderer { private _collision_geometry = new Object3D(); private _render_geometry = new Object3D(); private _entity_models = new Object3D(); diff --git a/src/quest_editor/rendering/conversion/areas.ts b/src/quest_editor/rendering/conversion/areas.ts index 1b69ca99..9c4f30f3 100644 --- a/src/quest_editor/rendering/conversion/areas.ts +++ b/src/quest_editor/rendering/conversion/areas.ts @@ -13,7 +13,7 @@ import { import { CollisionObject } from "../../../core/data_formats/parsing/area_collision_geometry"; import { RenderObject } from "../../../core/data_formats/parsing/area_geometry"; import { GeometryBuilder } from "../../../core/rendering/conversion/GeometryBuilder"; -import { ninja_object_to_geometry_builder } from "../../../core/rendering/conversion/ninja_three_geometry"; +import { ninja_object_to_geometry_builder } from "../../../core/rendering/conversion/ninja_geometry"; import { SectionModel } from "../../model/SectionModel"; import { AreaVariantModel } from "../../model/AreaVariantModel"; import { vec3_to_threejs } from "../../../core/rendering/conversion"; diff --git a/src/viewer/gui/model/ModelView.test.ts b/src/viewer/gui/model/ModelView.test.ts index 1c07c56d..36dec4df 100644 --- a/src/viewer/gui/model/ModelView.test.ts +++ b/src/viewer/gui/model/ModelView.test.ts @@ -4,7 +4,7 @@ import { CharacterClassAssetLoader } from "../../loading/CharacterClassAssetLoad import { FileSystemHttpClient } from "../../../../test/src/core/FileSystemHttpClient"; import { ModelView } from "./ModelView"; import { ModelRenderer } from "../../rendering/ModelRenderer"; -import { STUB_THREE_RENDERER } from "../../../../test/src/core/rendering/StubThreeRenderer"; +import { STUB_RENDERER } from "../../../../test/src/core/rendering/StubRenderer"; import { Random } from "../../../core/Random"; import { ModelStore } from "../../stores/ModelStore"; import { ModelToolBarView } from "./ModelToolBarView"; @@ -26,7 +26,7 @@ test("Renders correctly.", () => disposer.add(new ModelController(store)), new ModelToolBarView(disposer.add(new ModelToolBarController(store))), new CharacterClassOptionsView(disposer.add(new CharacterClassOptionsController(store))), - new ModelRenderer(store, STUB_THREE_RENDERER), + new ModelRenderer(store, STUB_RENDERER), ); expect(view.element).toMatchSnapshot(); diff --git a/src/viewer/gui/texture/TextureView.test.ts b/src/viewer/gui/texture/TextureView.test.ts index eb71a674..c261559e 100644 --- a/src/viewer/gui/texture/TextureView.test.ts +++ b/src/viewer/gui/texture/TextureView.test.ts @@ -2,14 +2,12 @@ import { TextureView } from "./TextureView"; import { with_disposer } from "../../../../test/src/core/observables/disposable_helpers"; import { TextureController } from "../../controllers/texture/TextureController"; import { TextureRenderer } from "../../rendering/TextureRenderer"; -import { StubGfxRenderer } from "../../../../test/src/core/rendering/StubGfxRenderer"; +import { STUB_RENDERER } from "../../../../test/src/core/rendering/StubRenderer"; test("Renders correctly without textures.", () => with_disposer(disposer => { const ctrl = disposer.add(new TextureController()); - const view = disposer.add( - new TextureView(ctrl, new TextureRenderer(ctrl, new StubGfxRenderer())), - ); + const view = disposer.add(new TextureView(ctrl, new TextureRenderer(ctrl, STUB_RENDERER))); expect(view.element).toMatchSnapshot("Should render a toolbar and a renderer widget."); })); diff --git a/src/viewer/gui/texture/__snapshots__/TextureView.test.ts.snap b/src/viewer/gui/texture/__snapshots__/TextureView.test.ts.snap index 1e0ba2d9..747f8830 100644 --- a/src/viewer/gui/texture/__snapshots__/TextureView.test.ts.snap +++ b/src/viewer/gui/texture/__snapshots__/TextureView.test.ts.snap @@ -35,8 +35,8 @@ exports[`Renders correctly without textures.: Should render a toolbar and a rend class="core_RendererWidget" > diff --git a/src/viewer/index.ts b/src/viewer/index.ts index 5da676e2..99cc8f38 100644 --- a/src/viewer/index.ts +++ b/src/viewer/index.ts @@ -4,9 +4,7 @@ import { HttpClient } from "../core/HttpClient"; import { Disposable } from "../core/observable/Disposable"; import { Disposer } from "../core/observable/Disposer"; import { Random } from "../core/Random"; -import { Renderer } from "../core/rendering/Renderer"; -import { DisposableThreeRenderer } from "../core/rendering/ThreeRenderer"; -import { Projection } from "../core/rendering/Camera"; +import { DisposableThreeRenderer } from "../core/rendering/Renderer"; export function initialize_viewer( http_client: HttpClient, @@ -36,35 +34,14 @@ export function initialize_viewer( const { CharacterClassOptionsController } = await import( "./controllers/model/CharacterClassOptionsController" ); + const { ModelRenderer } = await import("./rendering/ModelRenderer"); const asset_loader = disposer.add(new CharacterClassAssetLoader(http_client)); const store = disposer.add(new ModelStore(gui_store, asset_loader, random)); const model_controller = new ModelController(store); const model_tool_bar_controller = new ModelToolBarController(store); const character_class_options_controller = new CharacterClassOptionsController(store); - - let renderer: Renderer; - - if (gui_store.feature_active("webgpu")) { - const { create_webgpu_renderer } = await import( - "../core/rendering/webgpu/WebgpuRenderer" - ); - const { ModelGfxRenderer } = await import("./rendering/ModelGfxRenderer"); - - renderer = new ModelGfxRenderer( - store, - await create_webgpu_renderer(Projection.Perspective, http_client), - ); - } else if (gui_store.feature_active("webgl")) { - const { WebglRenderer } = await import("../core/rendering/webgl/WebglRenderer"); - const { ModelGfxRenderer } = await import("./rendering/ModelGfxRenderer"); - - renderer = new ModelGfxRenderer(store, new WebglRenderer(Projection.Perspective)); - } else { - const { ModelRenderer } = await import("./rendering/ModelRenderer"); - - renderer = new ModelRenderer(store, create_three_renderer()); - } + const renderer = new ModelRenderer(store, create_three_renderer()); return new ModelView( model_controller, @@ -80,24 +57,7 @@ export function initialize_viewer( const { TextureRenderer } = await import("./rendering/TextureRenderer"); const controller = disposer.add(new TextureController()); - - let renderer: Renderer; - - if (gui_store.feature_active("webgpu")) { - const { create_webgpu_renderer } = await import( - "../core/rendering/webgpu/WebgpuRenderer" - ); - renderer = new TextureRenderer( - controller, - await create_webgpu_renderer(Projection.Orthographic, http_client), - ); - } else { - const { WebglRenderer } = await import("../core/rendering/webgl/WebglRenderer"); - renderer = new TextureRenderer( - controller, - new WebglRenderer(Projection.Orthographic), - ); - } + const renderer = new TextureRenderer(controller, create_three_renderer()); return new TextureView(controller, renderer); }, diff --git a/src/viewer/rendering/ModelGfxRenderer.ts b/src/viewer/rendering/ModelGfxRenderer.ts deleted file mode 100644 index 9c482d28..00000000 --- a/src/viewer/rendering/ModelGfxRenderer.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ModelStore } from "../stores/ModelStore"; -import { Disposer } from "../../core/observable/Disposer"; -import { Renderer } from "../../core/rendering/Renderer"; -import { GfxRenderer } from "../../core/rendering/GfxRenderer"; -import { ninja_object_to_mesh } from "../../core/rendering/conversion/ninja_geometry"; -import { SceneNode } from "../../core/rendering/Scene"; -import { Mat4 } from "../../core/math/linear_algebra"; - -export class ModelGfxRenderer implements Renderer { - private readonly disposer = new Disposer(); - - readonly canvas_element: HTMLCanvasElement; - - constructor(private readonly store: ModelStore, private readonly renderer: GfxRenderer) { - this.canvas_element = renderer.canvas_element; - - renderer.camera.pan(0, 0, 50); - - this.disposer.add_all(store.current_nj_object.observe(this.nj_object_or_xvm_changed)); - - // TODO: remove - // const cube = cube_mesh(); - // cube.upload(this.renderer.gfx); - // - // this.renderer.scene.root_node.add_child( - // new SceneNode( - // undefined, - // Mat4.identity(), - // new SceneNode( - // cube, - // Mat4.compose( - // new Vec3(-3, 0, 0), - // quat_product( - // Quat.euler_angles(Math.PI / 6, 0, 0, EulerOrder.ZYX), - // Quat.euler_angles(0, -Math.PI / 6, 0, EulerOrder.ZYX), - // ), - // new Vec3(1, 1, 1), - // ), - // ), - // new SceneNode( - // cube, - // Mat4.compose( - // new Vec3(3, 0, 0), - // quat_product( - // Quat.euler_angles(-Math.PI / 6, 0, 0, EulerOrder.ZYX), - // Quat.euler_angles(0, Math.PI / 6, 0, EulerOrder.ZYX), - // ), - // new Vec3(1, 1, 1), - // ), - // ), - // ), - // ); - } - - dispose(): void { - this.disposer.dispose(); - } - - start_rendering(): void { - this.renderer.start_rendering(); - } - - stop_rendering(): void { - this.renderer.stop_rendering(); - } - - set_size(width: number, height: number): void { - this.renderer.set_size(width, height); - } - - private nj_object_or_xvm_changed = (): void => { - this.renderer.destroy_scene(); - - const nj_object = this.store.current_nj_object.val; - - if (nj_object) { - // Convert textures and geometry. - const node = new SceneNode(ninja_object_to_mesh(nj_object), Mat4.identity()); - this.renderer.scene.root_node.add_child(node); - - this.renderer.scene.traverse(node => { - node.mesh?.upload(this.renderer.gfx); - }, undefined); - } - - this.renderer.schedule_render(); - }; -} diff --git a/src/viewer/rendering/ModelRenderer.ts b/src/viewer/rendering/ModelRenderer.ts index 11d5805e..c53447a6 100644 --- a/src/viewer/rendering/ModelRenderer.ts +++ b/src/viewer/rendering/ModelRenderer.ts @@ -14,12 +14,12 @@ import { Disposable } from "../../core/observable/Disposable"; import { NjMotion } from "../../core/data_formats/parsing/ninja/motion"; import { xvr_texture_to_three_texture } from "../../core/rendering/conversion/ninja_textures"; import { create_mesh } from "../../core/rendering/conversion/create_mesh"; -import { ninja_object_to_buffer_geometry } from "../../core/rendering/conversion/ninja_three_geometry"; +import { ninja_object_to_buffer_geometry } from "../../core/rendering/conversion/ninja_geometry"; import { create_animation_clip, PSO_FRAME_RATE, } from "../../core/rendering/conversion/ninja_animation"; -import { DisposableThreeRenderer, ThreeRenderer } from "../../core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer, Renderer } from "../../core/rendering/Renderer"; import { Disposer } from "../../core/observable/Disposer"; import { ChangeEvent } from "../../core/observable/Observable"; import { LogManager } from "../../core/Logger"; @@ -40,7 +40,7 @@ const DEFAULT_SKINNED_MATERIAL = new MeshLambertMaterial({ const CAMERA_POSITION = Object.freeze(new Vector3(0, 10, 20)); const CAMERA_LOOK_AT = Object.freeze(new Vector3(0, 0, 0)); -export class ModelRenderer extends ThreeRenderer implements Disposable { +export class ModelRenderer extends Renderer implements Disposable { private readonly disposer = new Disposer(); private readonly clock = new Clock(); private character_class_active: boolean; diff --git a/src/viewer/rendering/TextureRenderer.ts b/src/viewer/rendering/TextureRenderer.ts index d883850f..7fb10a3d 100644 --- a/src/viewer/rendering/TextureRenderer.ts +++ b/src/viewer/rendering/TextureRenderer.ts @@ -2,51 +2,61 @@ import { Disposer } from "../../core/observable/Disposer"; import { LogManager } from "../../core/Logger"; import { TextureController } from "../controllers/texture/TextureController"; import { XvrTexture } from "../../core/data_formats/parsing/ninja/texture"; -import { VertexFormatType } from "../../core/rendering/VertexFormat"; -import { Mesh } from "../../core/rendering/Mesh"; -import { GfxRenderer } from "../../core/rendering/GfxRenderer"; -import { Renderer } from "../../core/rendering/Renderer"; -import { xvr_texture_to_texture } from "../../core/rendering/conversion/ninja_textures"; -import { Mat4, Vec2, Vec3 } from "../../core/math/linear_algebra"; -import { SceneNode } from "../../core/rendering/Scene"; +import { xvr_texture_to_three_texture } from "../../core/rendering/conversion/ninja_textures"; +import { + Mesh, + MeshBasicMaterial, + OrthographicCamera, + PlaneGeometry, + Texture, + Vector2, + Vector3, +} from "three"; +import { DisposableThreeRenderer, Renderer } from "../../core/rendering/Renderer"; +import { Disposable } from "../../core/observable/Disposable"; const logger = LogManager.get("viewer/rendering/TextureRenderer"); -export class TextureRenderer implements Renderer { +const CAMERA_POSITION = Object.freeze(new Vector3(0, 0, 5)); +const CAMERA_LOOK_AT = Object.freeze(new Vector3(0, 0, 0)); + +export class TextureRenderer extends Renderer implements Disposable { private readonly disposer = new Disposer(); + private readonly quad_meshes: Mesh[] = []; - readonly canvas_element: HTMLCanvasElement; + readonly camera = new OrthographicCamera(-400, 400, 300, -300, 1, 10); - constructor(ctrl: TextureController, private readonly renderer: GfxRenderer) { - this.canvas_element = renderer.canvas_element; - - renderer.camera.pan(0, 0, 10); + constructor(ctrl: TextureController, three_renderer: DisposableThreeRenderer) { + super(three_renderer); this.disposer.add_all( ctrl.textures.observe(({ value: textures }) => { - renderer.destroy_scene(); - renderer.camera.reset(); + this.scene.remove(...this.quad_meshes); + this.create_quads(textures); - renderer.schedule_render(); + + this.reset_camera(CAMERA_POSITION, CAMERA_LOOK_AT); + this.schedule_render(); }), ); - } - dispose(): void { - this.renderer.dispose(); - this.disposer.dispose(); - } - - start_rendering(): void { - this.renderer.start_rendering(); - } - - stop_rendering(): void { - this.renderer.stop_rendering(); + this.init_camera_controls(); + this.controls.azimuthRotateSpeed = 0; + this.controls.polarRotateSpeed = 0; } set_size(width: number, height: number): void { - this.renderer.set_size(width, height); + this.camera.left = -Math.floor(width / 2); + this.camera.right = Math.ceil(width / 2); + this.camera.top = Math.floor(height / 2); + this.camera.bottom = -Math.ceil(height / 2); + this.camera.updateProjectionMatrix(); + super.set_size(width, height); + } + + dispose(): void { + super.dispose(); + this.disposer.dispose(); } private create_quads(textures: readonly XvrTexture[]): void { @@ -62,37 +72,47 @@ export class TextureRenderer implements Renderer { const y = -Math.floor(total_height / 2); for (const tex of textures) { - try { - const quad_mesh = this.create_quad(tex); - quad_mesh.upload(this.renderer.gfx); + let texture: Texture | undefined = undefined; - this.renderer.scene.root_node.add_child( - new SceneNode( - quad_mesh, - Mat4.translation(x, y + (total_height - tex.height) / 2, 0), - ), - ); + try { + texture = xvr_texture_to_three_texture(tex); } catch (e) { - logger.error("Couldn't create quad for texture.", e); + logger.error("Couldn't convert XVR texture.", e); } + const quad_mesh = new Mesh( + this.create_quad( + x, + y + Math.floor((total_height - tex.height) / 2), + tex.width, + tex.height, + ), + texture + ? new MeshBasicMaterial({ + map: texture, + transparent: true, + }) + : new MeshBasicMaterial({ + color: 0xff00ff, + }), + ); + + this.quad_meshes.push(quad_mesh); + this.scene.add(quad_mesh); + x += 10 + tex.width; } } - private create_quad(tex: XvrTexture): Mesh { - return Mesh.builder(VertexFormatType.PosTex) - - .vertex(new Vec3(0, 0, 0), new Vec2(0, 1)) - .vertex(new Vec3(tex.width, 0, 0), new Vec2(1, 1)) - .vertex(new Vec3(tex.width, tex.height, 0), new Vec2(1, 0)) - .vertex(new Vec3(0, tex.height, 0), new Vec2(0, 0)) - - .triangle(0, 1, 2) - .triangle(2, 3, 0) - - .texture(xvr_texture_to_texture(this.renderer.gfx, tex)) - - .build(); + private create_quad(x: number, y: number, width: number, height: number): PlaneGeometry { + const quad = new PlaneGeometry(width, height, 1, 1); + quad.faceVertexUvs = [ + [ + [new Vector2(0, 0), new Vector2(0, 1), new Vector2(1, 0)], + [new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0)], + ], + ]; + quad.translate(x + width / 2, y + height / 2, -5); + return quad; } } diff --git a/test/src/core/rendering/StubGfxRenderer.ts b/test/src/core/rendering/StubGfxRenderer.ts deleted file mode 100644 index e2bf7710..00000000 --- a/test/src/core/rendering/StubGfxRenderer.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { GfxRenderer } from "../../../../src/core/rendering/GfxRenderer"; -import { Gfx } from "../../../../src/core/rendering/Gfx"; -import { Projection } from "../../../../src/core/rendering/Camera"; - -export class StubGfxRenderer extends GfxRenderer { - get gfx(): Gfx { - throw new Error("gfx is not implemented."); - } - - constructor() { - super(document.createElement("canvas"), Projection.Orthographic); - } - - protected render(): void {} // eslint-disable-line -} diff --git a/test/src/core/rendering/StubThreeRenderer.ts b/test/src/core/rendering/StubRenderer.ts similarity index 75% rename from test/src/core/rendering/StubThreeRenderer.ts rename to test/src/core/rendering/StubRenderer.ts index fbf73c84..fa08567d 100644 --- a/test/src/core/rendering/StubThreeRenderer.ts +++ b/test/src/core/rendering/StubRenderer.ts @@ -1,6 +1,6 @@ -import { DisposableThreeRenderer } from "../../../../src/core/rendering/ThreeRenderer"; +import { DisposableThreeRenderer } from "../../../../src/core/rendering/Renderer"; -export const STUB_THREE_RENDERER: DisposableThreeRenderer = { +export const STUB_RENDERER: DisposableThreeRenderer = { domElement: document.createElement("canvas"), dispose(): void {}, // eslint-disable-line diff --git a/webpack.common.js b/webpack.common.js index d893c4e4..83c63e21 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -17,10 +17,6 @@ module.exports = { test: /\.(gif|jpg|png|svg|ttf)$/, loader: "file-loader", }, - { - test: /\.(vert|frag)$/, - loader: "raw-loader", - }, ], }, plugins: [ diff --git a/yarn.lock b/yarn.lock index 5acafaeb..2feb641f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1065,16 +1065,6 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" -"@webgpu/glslang@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@webgpu/glslang/-/glslang-0.0.15.tgz#f5ccaf6015241e6175f4b90906b053f88483d1f2" - integrity sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q== - -"@webgpu/types@^0.0.27": - version "0.0.27" - resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.0.27.tgz#ce3b39f496109fc22dc22786ca5724f52c68d9e0" - integrity sha512-z1laHQvErLFM9nQSxfRuRetMk8iahidOdVQEdHWG9OjMKvXhHk6WnC98En0Bk0pWLQBvhiP1SGg3oHVlnKRucw== - "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -6870,14 +6860,6 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -raw-loader@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.1.tgz#14e1f726a359b68437e183d5a5b7d33a3eba6933" - integrity sha512-baolhQBSi3iNh1cglJjA0mYzga+wePk7vdEX//1dTFd+v4TsQlQE0jitJSNF1OIP82rdYulH7otaVmdlDaJ64A== - dependencies: - loader-utils "^2.0.0" - schema-utils "^2.6.5" - react-is@^16.12.0: version "16.12.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"