From 32ff6ce2d75e898cd350172916751ec13226d5f8 Mon Sep 17 00:00:00 2001 From: LLLL Colonq Date: Wed, 9 Apr 2025 03:13:42 -0400 Subject: Reorganize into workspace --- .gitmodules | 9 - Cargo.lock | 5 + Cargo.toml | 60 +-- crates/teleia/Cargo.toml | 48 ++ crates/teleia/src/assets/fonts/font1.png | Bin 0 -> 1508 bytes crates/teleia/src/assets/fonts/font2.png | Bin 0 -> 1883 bytes crates/teleia/src/assets/fonts/simple.png | Bin 0 -> 1042 bytes crates/teleia/src/assets/shaders/bitmap/frag.glsl | 17 + crates/teleia/src/assets/shaders/bitmap/vert.glsl | 17 + crates/teleia/src/assets/shaders/common/frag.glsl | 159 +++++++ crates/teleia/src/assets/shaders/common/vert.glsl | 30 ++ crates/teleia/src/assets/shaders/scale/frag.glsl | 12 + crates/teleia/src/assets/shaders/scale/vert.glsl | 22 + crates/teleia/src/assets/shaders/scene/frag.glsl | 11 + crates/teleia/src/assets/shaders/scene/vert.glsl | 18 + crates/teleia/src/assets/shaders/test/frag.glsl | 23 + crates/teleia/src/assets/shaders/test/vert.glsl | 4 + crates/teleia/src/assets/shaders/tiled/frag.glsl | 12 + crates/teleia/src/assets/shaders/tiled/vert.glsl | 14 + .../teleia/src/assets/shaders/truetype/frag.glsl | 16 + .../teleia/src/assets/shaders/truetype/vert.glsl | 17 + crates/teleia/src/audio.rs | 209 +++++++++ crates/teleia/src/context.rs | 228 +++++++++ crates/teleia/src/font.rs | 363 ++++++++++++++ crates/teleia/src/framebuffer.rs | 145 ++++++ crates/teleia/src/helpers.js | 13 + crates/teleia/src/js/module.js | 7 + crates/teleia/src/level2d.rs | 1 + crates/teleia/src/level2d/tiled.rs | 298 ++++++++++++ crates/teleia/src/lib.rs | 328 +++++++++++++ crates/teleia/src/mesh.rs | 132 ++++++ crates/teleia/src/net.rs | 1 + crates/teleia/src/net/client.rs | 2 + crates/teleia/src/net/client/wasm.rs | 67 +++ crates/teleia/src/physics.rs | 0 crates/teleia/src/save.rs | 43 ++ crates/teleia/src/scene.rs | 398 ++++++++++++++++ crates/teleia/src/shader.rs | 253 ++++++++++ crates/teleia/src/shadow.rs | 127 +++++ crates/teleia/src/state.rs | 520 +++++++++++++++++++++ crates/teleia/src/texture.rs | 83 ++++ crates/teleia/src/ui.rs | 153 ++++++ crates/teleia/src/utils.rs | 131 ++++++ deps/directories-rs | 1 - deps/glfw-rs | 1 - deps/gltf | 1 - src/assets/fonts/font1.png | Bin 1508 -> 0 bytes src/assets/fonts/font2.png | Bin 1883 -> 0 bytes src/assets/fonts/simple.png | Bin 1042 -> 0 bytes src/assets/shaders/bitmap/frag.glsl | 17 - src/assets/shaders/bitmap/vert.glsl | 17 - src/assets/shaders/common/frag.glsl | 159 ------- src/assets/shaders/common/vert.glsl | 30 -- src/assets/shaders/scale/frag.glsl | 12 - src/assets/shaders/scale/vert.glsl | 22 - src/assets/shaders/scene/frag.glsl | 11 - src/assets/shaders/scene/vert.glsl | 18 - src/assets/shaders/test/frag.glsl | 23 - src/assets/shaders/test/vert.glsl | 4 - src/assets/shaders/tiled/frag.glsl | 12 - src/assets/shaders/tiled/vert.glsl | 14 - src/assets/shaders/truetype/frag.glsl | 16 - src/assets/shaders/truetype/vert.glsl | 17 - src/audio.rs | 209 --------- src/context.rs | 228 --------- src/font.rs | 363 -------------- src/framebuffer.rs | 145 ------ src/helpers.js | 13 - src/js/module.js | 7 - src/level2d.rs | 1 - src/level2d/tiled.rs | 298 ------------ src/lib.rs | 328 ------------- src/mesh.rs | 132 ------ src/net.rs | 1 - src/net/client.rs | 2 - src/net/client/wasm.rs | 67 --- src/physics.rs | 0 src/save.rs | 43 -- src/scene.rs | 398 ---------------- src/shader.rs | 253 ---------- src/shadow.rs | 127 ----- src/state.rs | 514 -------------------- src/texture.rs | 83 ---- src/ui.rs | 153 ------ src/utils.rs | 131 ------ 85 files changed, 3936 insertions(+), 3931 deletions(-) create mode 100644 crates/teleia/Cargo.toml create mode 100644 crates/teleia/src/assets/fonts/font1.png create mode 100644 crates/teleia/src/assets/fonts/font2.png create mode 100644 crates/teleia/src/assets/fonts/simple.png create mode 100644 crates/teleia/src/assets/shaders/bitmap/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/bitmap/vert.glsl create mode 100644 crates/teleia/src/assets/shaders/common/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/common/vert.glsl create mode 100644 crates/teleia/src/assets/shaders/scale/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/scale/vert.glsl create mode 100644 crates/teleia/src/assets/shaders/scene/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/scene/vert.glsl create mode 100644 crates/teleia/src/assets/shaders/test/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/test/vert.glsl create mode 100644 crates/teleia/src/assets/shaders/tiled/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/tiled/vert.glsl create mode 100644 crates/teleia/src/assets/shaders/truetype/frag.glsl create mode 100644 crates/teleia/src/assets/shaders/truetype/vert.glsl create mode 100644 crates/teleia/src/audio.rs create mode 100644 crates/teleia/src/context.rs create mode 100644 crates/teleia/src/font.rs create mode 100644 crates/teleia/src/framebuffer.rs create mode 100644 crates/teleia/src/helpers.js create mode 100644 crates/teleia/src/js/module.js create mode 100644 crates/teleia/src/level2d.rs create mode 100644 crates/teleia/src/level2d/tiled.rs create mode 100644 crates/teleia/src/lib.rs create mode 100644 crates/teleia/src/mesh.rs create mode 100644 crates/teleia/src/net.rs create mode 100644 crates/teleia/src/net/client.rs create mode 100644 crates/teleia/src/net/client/wasm.rs create mode 100644 crates/teleia/src/physics.rs create mode 100644 crates/teleia/src/save.rs create mode 100644 crates/teleia/src/scene.rs create mode 100644 crates/teleia/src/shader.rs create mode 100644 crates/teleia/src/shadow.rs create mode 100644 crates/teleia/src/state.rs create mode 100644 crates/teleia/src/texture.rs create mode 100644 crates/teleia/src/ui.rs create mode 100644 crates/teleia/src/utils.rs delete mode 160000 deps/directories-rs delete mode 160000 deps/glfw-rs delete mode 160000 deps/gltf delete mode 100644 src/assets/fonts/font1.png delete mode 100644 src/assets/fonts/font2.png delete mode 100644 src/assets/fonts/simple.png delete mode 100644 src/assets/shaders/bitmap/frag.glsl delete mode 100644 src/assets/shaders/bitmap/vert.glsl delete mode 100644 src/assets/shaders/common/frag.glsl delete mode 100644 src/assets/shaders/common/vert.glsl delete mode 100644 src/assets/shaders/scale/frag.glsl delete mode 100644 src/assets/shaders/scale/vert.glsl delete mode 100644 src/assets/shaders/scene/frag.glsl delete mode 100644 src/assets/shaders/scene/vert.glsl delete mode 100644 src/assets/shaders/test/frag.glsl delete mode 100644 src/assets/shaders/test/vert.glsl delete mode 100644 src/assets/shaders/tiled/frag.glsl delete mode 100644 src/assets/shaders/tiled/vert.glsl delete mode 100644 src/assets/shaders/truetype/frag.glsl delete mode 100644 src/assets/shaders/truetype/vert.glsl delete mode 100644 src/audio.rs delete mode 100644 src/context.rs delete mode 100644 src/font.rs delete mode 100644 src/framebuffer.rs delete mode 100644 src/helpers.js delete mode 100644 src/js/module.js delete mode 100644 src/level2d.rs delete mode 100644 src/level2d/tiled.rs delete mode 100644 src/lib.rs delete mode 100644 src/mesh.rs delete mode 100644 src/net.rs delete mode 100644 src/net/client.rs delete mode 100644 src/net/client/wasm.rs delete mode 100644 src/physics.rs delete mode 100644 src/save.rs delete mode 100644 src/scene.rs delete mode 100644 src/shader.rs delete mode 100644 src/shadow.rs delete mode 100644 src/state.rs delete mode 100644 src/texture.rs delete mode 100644 src/ui.rs delete mode 100644 src/utils.rs diff --git a/.gitmodules b/.gitmodules index 2317b8f..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +0,0 @@ -[submodule "deps/gltf"] - path = deps/gltf - url = https://github.com/lcolonq/gltf -[submodule "deps/glfw-rs"] - path = deps/glfw-rs - url = git@github.com:lcolonq/glfw-rs.git -[submodule "deps/directories-rs"] - path = deps/directories-rs - url = git@github.com:lcolonq/directories-rs.git diff --git a/Cargo.lock b/Cargo.lock index 80d345a..18f2017 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -635,6 +635,7 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] name = "directories" version = "6.0.0" +source = "git+https://github.com/lcolonq/directories-rs#164536daab2c07265167715934081cb1942387b0" dependencies = [ "dirs-sys", ] @@ -949,6 +950,7 @@ dependencies = [ [[package]] name = "glfw" version = "0.59.0" +source = "git+https://github.com/lcolonq/glfw-rs#1ce13a2bcf3ef3bf6184bcb4c2a90d9034956841" dependencies = [ "bitflags 1.3.2", "objc2 0.5.2", @@ -978,6 +980,7 @@ dependencies = [ [[package]] name = "gltf" version = "1.4.1" +source = "git+https://github.com/lcolonq/gltf#9763a5abe8c168eb1282a037ff16668542fe2a6d" dependencies = [ "base64 0.13.1", "byteorder", @@ -991,6 +994,7 @@ dependencies = [ [[package]] name = "gltf-derive" version = "1.4.1" +source = "git+https://github.com/lcolonq/gltf#9763a5abe8c168eb1282a037ff16668542fe2a6d" dependencies = [ "inflections", "proc-macro2", @@ -1001,6 +1005,7 @@ dependencies = [ [[package]] name = "gltf-json" version = "1.4.1" +source = "git+https://github.com/lcolonq/gltf#9763a5abe8c168eb1282a037ff16668542fe2a6d" dependencies = [ "gltf-derive", "serde", diff --git a/Cargo.toml b/Cargo.toml index 38c2063..98dfb24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,56 +1,14 @@ -[package] -name = "teleia" +[workspace] +resolver = "2" +members = ["crates/*"] + +[workspace.package] version = "0.1.0" -authors = ["LLLL Colonq "] edition = "2021" +authors = ["LLLL Colonq "] -[lib] -crate-type = ["cdylib", "rlib"] - -[profile.release] -opt-level = 2 -codegen-units = 1 +[workspace.metadata.crane] +name = "teleia" [profile.dev.package."*"] -opt-level = 2 - -[dependencies] -strum = {version = "*", features = ["derive"]} # utility macros for enums -glow = {version = "=0.13.1", features = []} # rendering -tobj = "*" # loader for .obj meshes loader -gltf = {path = "deps/gltf", features = ["extras", "import", "names", "utils"]} # loader for .gltf scenes -# gltf = {version = "*", features = ["extras", "names"]} # model loader -image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } # texture loader -fontdue = "*" # truetype fonts -glam = "0.29" # linear algebra -log = "*" # logging -rand = {version = "=0.8.5", features = ["small_rng"]} # rng -serde = {version = "*", features = ["derive"]} # serialization -serde_json = "*" # serialize JSON -bincode = {version = "*", features = ["serde"]} # binary serialization -enum-map = "*" # fast maps with enums as keys -bimap = "*" # bijective maps -reqwest = "*" # http requests -bytes = "*" # bytes for http responses -bitflags = "*" # bitwise flags -color-eyre = { version = "*", default-features = false } # error reporting and formatting -rapier3d = "*" # rigid-body physics -parry3d = "*" # collision detection -nalgebra = {version = "0.33.2", features = ["convert-glam029"]} # linear algebra library for rapier3d - -[target.'cfg(target_arch = "wasm32")'.dependencies] -winit = {version = "=0.29.15", features = ["serde"]} # windowing and events -getrandom = {version = "*", features = ["js"]} # rng in the browser -console_log = "*" # log to browser console -console_error_panic_hook = "*" # log to browser console on panic -tracing-wasm = "*" # trace performance in browser -wasm-bindgen = "=0.2.100" # interface with javascript -wasm-bindgen-futures = "*" # interface with async javascript -js-sys = "*" # browser APIs to interact with JS runtime (e.g. run WASM) -web-sys = { version = "*", features = ["Document", "Window", "Element", "HtmlCanvasElement", "WebGl2RenderingContext", "Headers", "Request", "RequestInit", "RequestMode", "Response", "Performance", "PerformanceTiming", "AudioContext", "AudioNode", "AudioDestinationNode", "AudioBuffer", "AudioBufferSourceNode", "BinaryType", "Blob", "CloseEvent", "ErrorEvent", "FileReader", "MessageEvent", "ProgressEvent", "WebSocket", "Storage"] } - -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -env_logger = "*" # configurable logging to stdout -glfw = { path = "deps/glfw-rs", features = ["serde"] } # window management -kira = { version = "=0.9.6", default-features = false, features = ["cpal", "ogg", "wav"] } # audio -directories = { path = "deps/directories-rs" } # standard system directories \ No newline at end of file +opt-level = 2 \ No newline at end of file diff --git a/crates/teleia/Cargo.toml b/crates/teleia/Cargo.toml new file mode 100644 index 0000000..849973f --- /dev/null +++ b/crates/teleia/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "teleia" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +strum = {version = "*", features = ["derive"]} # utility macros for enums +glow = {version = "=0.13.1", features = []} # rendering +tobj = "*" # loader for .obj meshes loader +gltf = {git = "https://github.com/lcolonq/gltf", features = ["extras", "import", "names", "utils"]} # loader for .gltf scenes +image = { version = "0.25", default-features = false, features = ["jpeg", "png"] } # texture loader +fontdue = "*" # truetype fonts +glam = "0.29" # linear algebra +log = "*" # logging +rand = {version = "=0.8.5", features = ["small_rng"]} # rng +serde = {version = "*", features = ["derive"]} # serialization +serde_json = "*" # serialize JSON +bincode = {version = "*", features = ["serde"]} # binary serialization +enum-map = "*" # fast maps with enums as keys +bimap = "*" # bijective maps +reqwest = "*" # http requests +bytes = "*" # bytes for http responses +bitflags = "*" # bitwise flags +color-eyre = { version = "*", default-features = false } # error reporting and formatting +rapier3d = "*" # rigid-body physics +parry3d = "*" # collision detection +nalgebra = {version = "0.33.2", features = ["convert-glam029"]} # linear algebra library for rapier3d + +[target.'cfg(target_arch = "wasm32")'.dependencies] +winit = {version = "=0.29.15", features = ["serde"]} # windowing and events +getrandom = {version = "*", features = ["js"]} # rng in the browser +console_log = "*" # log to browser console +console_error_panic_hook = "*" # log to browser console on panic +tracing-wasm = "*" # trace performance in browser +wasm-bindgen = "=0.2.100" # interface with javascript +wasm-bindgen-futures = "*" # interface with async javascript +js-sys = "*" # browser APIs to interact with JS runtime (e.g. run WASM) +web-sys = { version = "*", features = ["Document", "Window", "Element", "HtmlCanvasElement", "WebGl2RenderingContext", "Headers", "Request", "RequestInit", "RequestMode", "Response", "Performance", "PerformanceTiming", "AudioContext", "AudioNode", "AudioDestinationNode", "AudioBuffer", "AudioBufferSourceNode", "BinaryType", "Blob", "CloseEvent", "ErrorEvent", "FileReader", "MessageEvent", "ProgressEvent", "WebSocket", "Storage"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +env_logger = "*" # configurable logging to stdout +glfw = { git = "https://github.com/lcolonq/glfw-rs", features = ["serde"] } # window management +kira = { version = "=0.9.6", default-features = false, features = ["cpal", "ogg", "wav"] } # audio +directories = { git = "https://github.com/lcolonq/directories-rs" } # standard system directories \ No newline at end of file diff --git a/crates/teleia/src/assets/fonts/font1.png b/crates/teleia/src/assets/fonts/font1.png new file mode 100644 index 0000000..ec06424 Binary files /dev/null and b/crates/teleia/src/assets/fonts/font1.png differ diff --git a/crates/teleia/src/assets/fonts/font2.png b/crates/teleia/src/assets/fonts/font2.png new file mode 100644 index 0000000..8435cad Binary files /dev/null and b/crates/teleia/src/assets/fonts/font2.png differ diff --git a/crates/teleia/src/assets/fonts/simple.png b/crates/teleia/src/assets/fonts/simple.png new file mode 100644 index 0000000..7b1d2a3 Binary files /dev/null and b/crates/teleia/src/assets/fonts/simple.png differ diff --git a/crates/teleia/src/assets/shaders/bitmap/frag.glsl b/crates/teleia/src/assets/shaders/bitmap/frag.glsl new file mode 100644 index 0000000..7df9a5c --- /dev/null +++ b/crates/teleia/src/assets/shaders/bitmap/frag.glsl @@ -0,0 +1,17 @@ +#version 300 es +precision highp float; + +uniform sampler2D texture_data; + +in vec2 vertex_texcoord; +in vec3 vertex_color; +out vec4 frag_color; + +void main() { + vec4 texel = texture(texture_data, vertex_texcoord); + if (texel.rgb == vec3(0.0, 0.0, 0.0)) discard; + texel.r = vertex_color.r; + texel.g = vertex_color.g; + texel.b = vertex_color.b; + frag_color = texel; +} diff --git a/crates/teleia/src/assets/shaders/bitmap/vert.glsl b/crates/teleia/src/assets/shaders/bitmap/vert.glsl new file mode 100644 index 0000000..192d4b0 --- /dev/null +++ b/crates/teleia/src/assets/shaders/bitmap/vert.glsl @@ -0,0 +1,17 @@ +#version 300 es +precision highp float; + +in vec2 vertex; +in vec2 texcoord; +in vec3 color; + +uniform mat4 transform; + +out vec2 vertex_texcoord; +out vec3 vertex_color; + +void main() { + vertex_texcoord = texcoord; + vertex_color = color; + gl_Position = transform * vec4(vertex, 0.0, 1.0); +} diff --git a/crates/teleia/src/assets/shaders/common/frag.glsl b/crates/teleia/src/assets/shaders/common/frag.glsl new file mode 100644 index 0000000..26d63fa --- /dev/null +++ b/crates/teleia/src/assets/shaders/common/frag.glsl @@ -0,0 +1,159 @@ +#version 300 es +precision highp float; + +uniform vec3 camera_pos; +uniform float time; + +uniform vec3 light_ambient_color; +uniform vec3 light_dir; +uniform vec3 light_dir_color; +uniform int light_count; +uniform vec3 light_pos[5]; +uniform vec3 light_color[5]; +uniform vec2 light_attenuation[5]; +uniform highp sampler2DShadow light_shadowbuffer_dir; +uniform samplerCube light_shadowbuffer_point[5]; + +uniform int has_point_shadows; + +in vec2 vertex_texcoord; +in vec3 vertex_normal; +in vec3 vertex_fragpos; +in vec4 vertex_fragpos_shadow_dir; +in vec3 vertex_view_vector; + +out vec4 frag_color; + +mat3 compute_tbn() { + vec3 p = -vertex_view_vector; + vec3 normal = normalize(vertex_normal); + vec3 dpx = dFdx(p); + vec3 dpy = dFdy(p); + vec2 duvx = dFdx(vertex_texcoord); + vec2 duvy = dFdy(vertex_texcoord); + vec3 dpyperp = cross(dpy, normal); + vec3 dpxperp = cross(normal, dpx); + vec3 tangent = dpyperp * duvx.x + dpxperp * duvy.x; + vec3 bitangent = dpyperp * duvx.y + dpxperp * duvy.y; + float invmax = inversesqrt(max(dot(bitangent, bitangent), dot(bitangent, bitangent))); + return mat3(-tangent * invmax, -bitangent * invmax, normal); +} + +vec4 normal_as_color(vec3 n) { + float r = (128.0 + 127.0 * n.r) / 255.0; + float g = (128.0 + 127.0 * n.g) / 255.0; + float b = (128.0 + 127.0 * n.b) / 255.0; + return vec4(r, g, b, 1.0); +} + +vec3 dir_light(vec3 normal) { + return max(dot(normal, -normalize(light_dir)), 0.0) * light_dir_color; +} + +float dir_shadow(vec3 normal) { + vec3 proj = vertex_fragpos_shadow_dir.xyz / vertex_fragpos_shadow_dir.w; + float bias = 0.002; + // float current_depth = proj.z; + // float bias = max(0.05 * (1.0 - dot(normal, -normalize(light_dir))), 0.005); + proj.z -= bias; + proj *= 0.5; proj += 0.5; + if (proj.z > 1.0) return 0.0; + return 1.0 - texture(light_shadowbuffer_dir, proj.xyz); +} + +float point_shadow(vec3 normal, vec3 shadow_vector, float closest_depth) { + closest_depth *= 25.0; + float current_depth = length(shadow_vector); + float bias = max(0.1 * (1.0 - dot(normal, normalize(shadow_vector))), 0.005); + bias = min(bias + current_depth * 0.01, 0.2); + float shadow = current_depth - bias > closest_depth ? 1.0 : 0.0; + return shadow; +} + +vec3 point_light(vec3 normal, const int idx) { + vec3 pos = light_pos[idx]; + vec3 color = light_color[idx]; + float linear = light_attenuation[idx].x; + float quadratic = light_attenuation[idx].y; + vec3 light_vector = pos - vertex_fragpos; + float distance = length(light_vector); + float attenuation = 1.0 / (1.0 + distance * linear + distance * distance * quadratic); + + float directional = max(dot(normal.xyz, normalize(light_vector)), 0.0); + vec3 directional_light = color * directional; + + vec3 view_dir = normalize(camera_pos - vertex_fragpos); + vec3 reflect_dir = reflect(-normalize(light_vector), normalize(normal.xyz)); + float specular = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0); + vec3 specular_light = 0.5 * specular * color; + // return (directional_light + specular_light) * attenuation; + return directional_light * attenuation; +} + +vec3 point_light_billboard(const int idx) { + vec3 pos = light_pos[idx]; + vec3 color = light_color[idx]; + float linear = light_attenuation[idx].x; + float quadratic = light_attenuation[idx].y; + vec3 light_vector = pos - vertex_fragpos; + float distance = length(light_vector); + float attenuation = 1.0 / (1.0 + distance * linear + distance * distance * quadratic); + + return color * attenuation; +} + +vec3 compute_lighting(vec3 normal) { + vec3 ambient_light = light_ambient_color; + + vec3 from_dir = dir_light(normal) * (1.0 - dir_shadow(normal)); + + vec3 shadow_vector[5]; + for (int i = 0; i < light_count; ++i) { + shadow_vector[i] = vertex_fragpos - light_pos[i]; + shadow_vector[i].x *= -1.0; + } + + // cannot only index array of samplers with a constant, hence the weird setup + #define SAMPLE_SHADOW(n) n < light_count ? texture(light_shadowbuffer_point[n], shadow_vector[n]).r : 1.0 + float shadow_depth[5]; + shadow_depth[0] = SAMPLE_SHADOW(0); + shadow_depth[1] = SAMPLE_SHADOW(1); + shadow_depth[2] = SAMPLE_SHADOW(2); + shadow_depth[3] = SAMPLE_SHADOW(3); + shadow_depth[4] = SAMPLE_SHADOW(4); + + vec3 from_points = vec3(0.0, 0.0, 0.0); + for (int i = 0; i < light_count; ++i) { + from_points += has_point_shadows != 0 + ? point_light(normal, i) * (1.0 - point_shadow(normal, shadow_vector[i], shadow_depth[i])) + : point_light(normal, i); + } + + return (ambient_light + from_dir + from_points); +} + +vec3 compute_lighting_noshadow(vec3 normal) { + vec3 ambient_light = light_ambient_color; + + vec3 from_dir = dir_light(normal); + + vec3 from_points = vec3(0.0, 0.0, 0.0); + for (int i = 0; i < light_count; ++i) { + from_points += point_light(normal, i); + } + + return (ambient_light + from_dir + from_points); +} + +vec3 compute_lighting_billboard(vec3 normal) { + vec3 ambient_light = light_ambient_color; + + vec3 from_dir = light_dir_color / 2.0; + + vec3 from_points = vec3(0.0, 0.0, 0.0); + for (int i = 0; i < light_count; ++i) { + from_points += point_light_billboard(i); + } + + return (ambient_light + from_dir + from_points); +} diff --git a/crates/teleia/src/assets/shaders/common/vert.glsl b/crates/teleia/src/assets/shaders/common/vert.glsl new file mode 100644 index 0000000..b8e11c5 --- /dev/null +++ b/crates/teleia/src/assets/shaders/common/vert.glsl @@ -0,0 +1,30 @@ +#version 300 es +precision highp float; + +in vec3 vertex; +in vec3 normal; +in vec2 texcoord; + +uniform mat4 view; +uniform mat4 position; +uniform mat4 projection; +uniform mat4 normal_matrix; +uniform mat4 lightspace_matrix; +uniform vec3 camera_pos; + +out vec2 vertex_texcoord; +out vec3 vertex_normal; +out vec3 vertex_fragpos; +out vec4 vertex_fragpos_shadow_dir; +out vec3 vertex_view_vector; + +void default_main() +{ + vertex_texcoord = texcoord; + vertex_normal = (normal_matrix * vec4(normal, 1.0)).xyz; + vec3 pos = (position * vec4(vertex, 1.0)).xyz; + vertex_fragpos = pos; + vertex_fragpos_shadow_dir = lightspace_matrix * vec4(pos, 1.0); + vertex_view_vector = camera_pos - pos; + gl_Position = projection * view * vec4(pos, 1.0); +} diff --git a/crates/teleia/src/assets/shaders/scale/frag.glsl b/crates/teleia/src/assets/shaders/scale/frag.glsl new file mode 100644 index 0000000..5fce547 --- /dev/null +++ b/crates/teleia/src/assets/shaders/scale/frag.glsl @@ -0,0 +1,12 @@ +#version 300 es +precision highp float; + +uniform sampler2D texture_data; + +in vec2 vertex_texcoord; +out vec4 frag_color; + +void main() { + vec4 texel = texture(texture_data, vertex_texcoord); + frag_color = texel; +} diff --git a/crates/teleia/src/assets/shaders/scale/vert.glsl b/crates/teleia/src/assets/shaders/scale/vert.glsl new file mode 100644 index 0000000..e05bbb6 --- /dev/null +++ b/crates/teleia/src/assets/shaders/scale/vert.glsl @@ -0,0 +1,22 @@ +#version 300 es +precision highp float; + +out vec2 vertex_texcoord; + +void main() { + const vec2 positions[4] = vec2[]( + vec2(-1, -1), + vec2(+1, -1), + vec2(-1, +1), + vec2(+1, +1) + ); + const vec2 coords[4] = vec2[]( + vec2(0, 0), + vec2(1, 0), + vec2(0, 1), + vec2(1, 1) + ); + + vertex_texcoord = coords[gl_VertexID]; + gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); +} diff --git a/crates/teleia/src/assets/shaders/scene/frag.glsl b/crates/teleia/src/assets/shaders/scene/frag.glsl new file mode 100644 index 0000000..81e08b9 --- /dev/null +++ b/crates/teleia/src/assets/shaders/scene/frag.glsl @@ -0,0 +1,11 @@ +uniform sampler2D texture_data; + +void main() +{ + vec4 texel = texture(texture_data, vertex_texcoord); + if (texel.a != 1.0) { + discard; + } + + frag_color = vec4(texel.rgb, texel.a); +} diff --git a/crates/teleia/src/assets/shaders/scene/vert.glsl b/crates/teleia/src/assets/shaders/scene/vert.glsl new file mode 100644 index 0000000..64f400c --- /dev/null +++ b/crates/teleia/src/assets/shaders/scene/vert.glsl @@ -0,0 +1,18 @@ +in vec4 joint; +in vec4 weight; + +uniform mat4 joint_matrices[128]; + +void main() +{ + vertex_texcoord = texcoord; + vertex_normal = (normal_matrix * vec4(normal, 1.0)).xyz; + mat4 skin + = weight.x * joint_matrices[int(joint.x)] + + weight.y * joint_matrices[int(joint.y)] + + weight.z * joint_matrices[int(joint.z)] + + weight.w * joint_matrices[int(joint.w)]; + vec3 pos = (position * skin * vec4(vertex, 1.0)).xyz; + vertex_view_vector = camera_pos - pos; + gl_Position = projection * view * vec4(pos, 1.0); +} diff --git a/crates/teleia/src/assets/shaders/test/frag.glsl b/crates/teleia/src/assets/shaders/test/frag.glsl new file mode 100644 index 0000000..a52aa15 --- /dev/null +++ b/crates/teleia/src/assets/shaders/test/frag.glsl @@ -0,0 +1,23 @@ +// uniform int has_normal_map; +// uniform sampler2D normal_map; + +uniform sampler2D texture_data; + +void main() +{ + vec2 inverted_texcoord = vec2(vertex_texcoord.x, 1.0 - vertex_texcoord.y); + vec4 texel = texture(texture_data, inverted_texcoord); + if (texel.a != 1.0) { + discard; + } + + // mat3 tbn = compute_tbn(); + // vec3 normal = has_normal_map != 0 + // ? normalize(tbn * (texture(normal_map, inverted_texcoord).xyz * 2.0 - 1.0)) + // : normalize(vertex_normal); + vec3 normal = normalize(vertex_normal); + + vec3 lighting = compute_lighting_noshadow(normal); + + frag_color = vec4(texel.rgb * lighting, texel.a); +} diff --git a/crates/teleia/src/assets/shaders/test/vert.glsl b/crates/teleia/src/assets/shaders/test/vert.glsl new file mode 100644 index 0000000..e324f7e --- /dev/null +++ b/crates/teleia/src/assets/shaders/test/vert.glsl @@ -0,0 +1,4 @@ +void main() +{ + default_main(); +} \ No newline at end of file diff --git a/crates/teleia/src/assets/shaders/tiled/frag.glsl b/crates/teleia/src/assets/shaders/tiled/frag.glsl new file mode 100644 index 0000000..5fce547 --- /dev/null +++ b/crates/teleia/src/assets/shaders/tiled/frag.glsl @@ -0,0 +1,12 @@ +#version 300 es +precision highp float; + +uniform sampler2D texture_data; + +in vec2 vertex_texcoord; +out vec4 frag_color; + +void main() { + vec4 texel = texture(texture_data, vertex_texcoord); + frag_color = texel; +} diff --git a/crates/teleia/src/assets/shaders/tiled/vert.glsl b/crates/teleia/src/assets/shaders/tiled/vert.glsl new file mode 100644 index 0000000..4ff9865 --- /dev/null +++ b/crates/teleia/src/assets/shaders/tiled/vert.glsl @@ -0,0 +1,14 @@ +#version 300 es +precision highp float; + +in vec2 vertex; +in vec2 texcoord; + +uniform mat4 transform; + +out vec2 vertex_texcoord; + +void main() { + vertex_texcoord = texcoord; + gl_Position = transform * vec4(vertex, 0.0, 1.0); +} diff --git a/crates/teleia/src/assets/shaders/truetype/frag.glsl b/crates/teleia/src/assets/shaders/truetype/frag.glsl new file mode 100644 index 0000000..3f62e01 --- /dev/null +++ b/crates/teleia/src/assets/shaders/truetype/frag.glsl @@ -0,0 +1,16 @@ +#version 300 es +precision highp float; + +uniform sampler2D texture_data; + +in vec2 vertex_texcoord; +in vec3 vertex_color; +out vec4 frag_color; + +void main() +{ + float val = texture(texture_data, vertex_texcoord).r; + if (val == 0.0) discard; + vec4 texel = vec4(vertex_color, val); + frag_color = texel; +} diff --git a/crates/teleia/src/assets/shaders/truetype/vert.glsl b/crates/teleia/src/assets/shaders/truetype/vert.glsl new file mode 100644 index 0000000..192d4b0 --- /dev/null +++ b/crates/teleia/src/assets/shaders/truetype/vert.glsl @@ -0,0 +1,17 @@ +#version 300 es +precision highp float; + +in vec2 vertex; +in vec2 texcoord; +in vec3 color; + +uniform mat4 transform; + +out vec2 vertex_texcoord; +out vec3 vertex_color; + +void main() { + vertex_texcoord = texcoord; + vertex_color = color; + gl_Position = transform * vec4(vertex, 0.0, 1.0); +} diff --git a/crates/teleia/src/audio.rs b/crates/teleia/src/audio.rs new file mode 100644 index 0000000..2b4226c --- /dev/null +++ b/crates/teleia/src/audio.rs @@ -0,0 +1,209 @@ +use std::collections::HashMap; + +#[cfg(target_arch = "wasm32")] +use std::cell::RefCell; + +#[cfg(target_arch = "wasm32")] +pub struct Context { + pub audio: web_sys::AudioContext, +} + +#[cfg(target_arch = "wasm32")] +impl Context { + pub fn new() -> Self { + let audio = web_sys::AudioContext::new() + .expect("failed to create audio context"); + Self { + audio, + } + } +} + +#[cfg(target_arch = "wasm32")] +pub struct Audio { + pub buffer: &'static RefCell>, + //pub source: &'static web_sys::AudioBufferSourceNode, +} + +#[cfg(target_arch = "wasm32")] +impl Audio { + pub fn new(ctx: &Context, bytes: &[u8]) -> Self { + let sbuffer: &_ = Box::leak(Box::new(RefCell::new(None))); + let sclone: &'static RefCell> = + <&_>::clone(&sbuffer); + let ret = Audio { + buffer: sclone, + }; + let jsp = ctx.audio.decode_audio_data(&js_sys::Uint8Array::from(bytes).buffer()).expect("failed to decode audio"); + let promise = wasm_bindgen_futures::JsFuture::from(jsp); + wasm_bindgen_futures::spawn_local(async { + if let Some(data) = promise.await.ok() { + *sbuffer.borrow_mut() = Some(web_sys::AudioBuffer::from(data)); + } + () + }); + ret + } + + pub fn play(&self, ctx: &Context, looping: Option<(Option, Option)>) -> Option { + let source = ctx.audio.create_buffer_source().ok()?; + source.set_buffer((&*self.buffer.borrow()).as_ref()); + if let Some((ms, me)) = looping { + source.set_loop(true); + if let Some(s) = ms { source.set_loop_start(s) } + if let Some(e) = me { source.set_loop_end(e) } + } + source.connect_with_audio_node(&ctx.audio.destination()).ok()?; + source.start().ok()?; + Some(source) + } +} + +#[cfg(target_arch = "wasm32")] +pub struct Assets { + pub ctx: Context, + + pub audio: HashMap, + + pub music_node: Option, +} + +#[cfg(target_arch = "wasm32")] +impl Assets { + pub fn new(f : F) -> Self where F: Fn(&Context) -> HashMap { + let ctx = Context::new(); + + let audio = f(&ctx); + + Self { + ctx, + audio, + music_node: None, + } + } + + pub fn play_sfx(&mut self, name: &str) { + if let Some(a) = self.audio.get(name) { + a.play(&self.ctx, None); + } + } + + pub fn is_music_playing(&self) -> bool { + if let Some(ms) = &self.music_node { + ms.buffer().is_some() + } else { false } + } + + pub fn play_music(&mut self, name: &str, start: Option, end: Option) { + if let Some(s) = &self.music_node { + let _ = s.stop(); + } + if let Some(a) = self.audio.get(name) { + self.music_node = a.play(&self.ctx, Some((start, end))); + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct Context { + manager: kira::manager::AudioManager, +} + +#[cfg(not(target_arch = "wasm32"))] +impl Context { + pub fn new() -> Self { + Self { + manager: kira::manager::AudioManager::new(kira::manager::AudioManagerSettings::default()) + .expect("failed to create audio manager"), + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct Audio { + data: kira::sound::static_sound::StaticSoundData, +} + +#[cfg(not(target_arch = "wasm32"))] +impl Audio { + pub fn new(_ctx: &Context, bytes: &'static [u8]) -> Self { + Self { + data: kira::sound::static_sound::StaticSoundData::from_cursor(std::io::Cursor::new(bytes)) + .expect("failed to decode audio"), + } + } + + pub fn play( + &self, + ctx: &mut Context, + looping: Option<(Option, Option)> + ) -> Result + { + let sd = if let Some((ss, se)) = looping { + let start = if let Some(s) = ss { s } else { 0.0 }; + if let Some(e) = se { + self.data.loop_region(start..e) + } else { + self.data.loop_region(start..) + } + } else { + self.data.clone() + }; + match ctx.manager.play(sd) { + Ok(h) => Ok(h), + Err(e) => Err(e.to_string()), + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct Assets { + pub ctx: Context, + pub audio: HashMap, + pub music_handle: Option, +} + +#[cfg(not(target_arch = "wasm32"))] +impl Assets { + pub fn new(f : F) -> Self where F: Fn(&Context) -> HashMap { + let ctx = Context::new(); + + let audio = f(&ctx); + + Self { + ctx, + audio, + music_handle: None, + } + } + + pub fn play_sfx(&mut self, name: &str) { + if let Some(a) = self.audio.get(name) { + if let Err(e) = a.play(&mut self.ctx, None) { + log::warn!("failed to play sound {}: {}", name, e); + } + } + } + + pub fn is_music_playing(&self) -> bool { + if let Some(mh) = &self.music_handle { + mh.state() == kira::sound::PlaybackState::Playing + } else { false } + } + + pub fn play_music(&mut self, name: &str, start: Option, end: Option) { + if let Some(s) = &mut self.music_handle { + let _ = s.stop(kira::tween::Tween::default()); + } + if let Some(a) = self.audio.get(name) { + match a.play(&mut self.ctx, Some((start, end))) { + Ok(h) => { + self.music_handle = Some(h); + }, + Err(e) => { + log::warn!("failed to play music {}: {}", name, e); + } + } + } + } +} diff --git a/crates/teleia/src/context.rs b/crates/teleia/src/context.rs new file mode 100644 index 0000000..9f0d4ad --- /dev/null +++ b/crates/teleia/src/context.rs @@ -0,0 +1,228 @@ +use glow::HasContext; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; + +#[cfg(target_arch = "wasm32")] +use winit::platform::web::WindowExtWebSys; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(module = "/src/helpers.js")] +extern { + fn js_track_resized_setup(); + fn js_poll_resized() -> bool; +} + +#[cfg(not(target_arch = "wasm32"))] +pub struct Context { + pub render_width: f32, + pub render_height: f32, + pub resize: bool, + pub glfw: std::cell::RefCell, + pub window: std::cell::RefCell, + pub gl: glow::Context, + pub emptyvao: glow::VertexArray, + pub start_instant: std::time::Instant, +} + + +#[cfg(target_arch = "wasm32")] +pub struct Context { + pub render_width: f32, + pub render_height: f32, + pub resize: bool, + pub window: winit::window::Window, + pub gl: glow::Context, + pub emptyvao: glow::VertexArray, + pub performance: web_sys::Performance, +} + +impl Context { + pub fn compute_upscale(&self, windoww: u32, windowh: u32) -> u32 { + let mut ratio = 1; + loop { + if (self.render_width as u32) * ratio > windoww + || (self.render_height as u32) * ratio > windowh + { + break; + } + ratio += 1; + } + (ratio - 1).max(1) + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn new(glfw: std::cell::RefCell, window: std::cell::RefCell, gl: glow::Context, render_width: f32, render_height: f32, resize: bool) -> Self { + let emptyvao = unsafe { + gl.create_vertex_array().expect("failed to initialize vao") + }; + let ret = Self { + render_width, render_height, + resize, + glfw, window, + gl, + emptyvao, + start_instant: std::time::Instant::now(), + }; + ret.init(); + ret + } + + #[cfg(target_arch = "wasm32")] + pub fn new(window: winit::window::Window, gl: glow::Context, render_width: f32, render_height: f32, resize: bool) -> Self { + let emptyvao = unsafe { + gl.create_vertex_array().expect("failed to initialize vao") + }; + + #[cfg(target_arch = "wasm32")] + js_track_resized_setup(); + + let ret = Self { + render_width, render_height, + resize, + window, + gl, + emptyvao, + + #[cfg(target_arch = "wasm32")] + performance: web_sys::window().expect("failed to find window") + .performance().expect("failed to get performance"), + }; + ret.init(); + ret + } + + pub fn init(&self) { + unsafe { + self.gl.clear_color(0.1, 0.1, 0.1, 1.0); + self.gl.clear_depth_f32(1.0); + + self.gl.enable(glow::DEPTH_TEST); + self.gl.depth_func(glow::LEQUAL); + + self.gl.enable(glow::BLEND); + self.gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + + self.gl.enable(glow::STENCIL_TEST); + + self.gl.cull_face(glow::FRONT); + } + } + + #[cfg(target_arch = "wasm32")] + pub fn maximize_canvas(&self) { + if self.resize { + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + let inner_size = { + let browser_window = doc.default_view() + .or_else(web_sys::window) + .unwrap(); + winit::dpi::PhysicalSize::new( + browser_window.inner_width().unwrap().as_f64().unwrap(), + browser_window.inner_height().unwrap().as_f64().unwrap(), + ) + }; + self.window.canvas().unwrap().set_width(inner_size.width as _); + self.window.canvas().unwrap().set_height(inner_size.height as _); + let _ = self.window.request_inner_size(inner_size); + Some(()) + }) + .expect("failed to resize canvas"); + } + } + + #[cfg(target_arch = "wasm32")] + pub fn resize_necessary(&self) -> bool { + js_poll_resized() + } + + #[cfg(not(target_arch = "wasm32"))] + pub fn resize_necessary(&self) -> bool { + false + } + + pub fn clear_color(&self, color: glam::Vec4) { + unsafe { + self.gl.clear_color(color.x, color.y, color.z, color.w); + } + } + + pub fn clear_depth(&self) { + unsafe { + self.gl.clear(glow::DEPTH_BUFFER_BIT); + } + } + + pub fn clear(&self) { + unsafe { + self.gl.stencil_mask(0xff); + self.gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT | glow::STENCIL_BUFFER_BIT); + } + } + + pub fn begin_stencil(&self) { + unsafe { + self.gl.stencil_func(glow::ALWAYS, 1, 0xff); + self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::REPLACE); + } + } + + pub fn use_stencil(&self) { + unsafe { + self.gl.stencil_func(glow::EQUAL, 1, 0xff); + self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); + } + } + + pub fn end_stencil(&self) { + unsafe { + self.gl.stencil_func(glow::ALWAYS, 1, 0xff); + self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); + } + } + + pub fn render_no_geometry(&self) { + unsafe { + self.gl.bind_vertex_array(Some(self.emptyvao)); + self.gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); + } + } + + pub fn reset_blend(&self) { + unsafe { + self.gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); + } + } + + pub fn inverse_blend(&self) { + unsafe { + self.gl.blend_func( + glow::ONE_MINUS_DST_COLOR, + glow::ZERO, + ); + } + } + + pub fn enable_culling(&self) { + unsafe { + self.gl.enable(glow::CULL_FACE); + } + } + + pub fn disable_culling(&self) { + unsafe { + self.gl.disable(glow::CULL_FACE); + } + } + + pub fn check_error(&self) { + unsafe { + let err = self.gl.get_error(); + if err != 0 { + log::warn!("gl error: {}", err); + } + } + } +} diff --git a/crates/teleia/src/font.rs b/crates/teleia/src/font.rs new file mode 100644 index 0000000..e431896 --- /dev/null +++ b/crates/teleia/src/font.rs @@ -0,0 +1,363 @@ +use std::collections::HashMap; + +use crate::{context, mesh, shader, texture}; +use glow::HasContext; + +pub struct Bitmap { + pub char_width: i32, + pub char_height: i32, + pub font_width: i32, + pub font_height: i32, + pub shader: shader::Shader, + pub font: texture::Texture, + pub vao: glow::VertexArray, + pub vertex_buf: glow::Buffer, + pub texcoords_buf: glow::Buffer, + pub colors_buf: glow::Buffer, + pub index_buf: glow::Buffer, +} + +impl Bitmap { + pub fn from_image( + ctx: &context::Context, + char_width: i32, char_height: i32, + font_width: i32, font_height: i32, + data: &[u8], + ) -> Self { + let shader = shader::Shader::new_nolib( + &ctx, + include_str!("assets/shaders/bitmap/vert.glsl"), + include_str!("assets/shaders/bitmap/frag.glsl"), + ); + let font = texture::Texture::new(ctx, data); + unsafe { + let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); + ctx.gl.bind_vertex_array(Some(vao)); + let vertex_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 2, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); + let texcoords_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); + let colors_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(colors_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_COLOR, 3, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_COLOR); + let index_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buf)); + Self { + char_width, char_height, + font_width, font_height, + shader, + font, + vao, + vertex_buf, + texcoords_buf, + colors_buf, + index_buf, + } + } + } + + pub fn new(ctx: &context::Context) -> Self { + Self::from_image(ctx, 7, 9, 112, 54, include_bytes!("assets/fonts/simple.png")) + } + + pub fn render_text_helper(&self, ctx: &context::Context, pos: &glam::Vec2, text: &str, color: &[glam::Vec3]) { + let mut cur = glam::Vec2::new(0.0, 0.0); + let mut vertices = Vec::new(); + let mut texcoords = Vec::new(); + let mut colors = Vec::new(); + let mut indices = Vec::new(); + let cwidth = self.char_width as f32 / self.font_width as f32; + let cheight = self.char_height as f32 / self.font_height as f32; + let row_len = self.font_width as u32 / self.char_width as u32; + for (i, c) in text.chars().enumerate() { + if c == '\n' { + cur.x = 0.0; + cur.y -= self.char_height as f32; + } else { + let idx = vertices.len() as u32; + vertices.push(cur); + vertices.push(cur + glam::Vec2::new(self.char_width as f32, 0.0)); + vertices.push(cur + glam::Vec2::new(self.char_width as f32, self.char_height as f32)); + vertices.push(cur + glam::Vec2::new(0.0, self.char_height as f32)); + let cidx = c as u32 - ' ' as u32; + let col = cidx % row_len; + let row = cidx / row_len; + let tcbase = glam::Vec2::new(col as f32 * cwidth, row as f32 * cheight); + texcoords.push(tcbase + glam::Vec2::new(0.0, cheight)); + texcoords.push(tcbase + glam::Vec2::new(cwidth, cheight)); + texcoords.push(tcbase + glam::Vec2::new(cwidth, 0.0)); + texcoords.push(tcbase); + let c = if let Some(c) = color.get(if color.len() == 0 { 0 } else { i % color.len() }) { + *c + } else { + glam::Vec3::new(1.0, 1.0, 1.0) + }; + colors.push(c); colors.push(c); colors.push(c); colors.push(c); + indices.push(idx + 0); indices.push(idx + 1); indices.push(idx + 2); + indices.push(idx + 0); indices.push(idx + 3); indices.push(idx + 2); + cur.x += self.char_width as f32; + } + } + let index_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); + self.shader.bind(ctx); + self.font.bind(ctx); + let scale = glam::Vec2::new(2.0 / ctx.render_width, 2.0 / ctx.render_height); + let offset = glam::Vec2::new( + -ctx.render_width / 2.0, + ctx.render_height / 2.0 - self.char_height as f32, + ); + let npos = (glam::Vec2::new(pos.x, -pos.y) + offset) * scale; + self.shader.set_mat4( + ctx, "transform", + &glam::Mat4::from_scale_rotation_translation( + glam::Vec3::new(scale.x, scale.y, 1.0), + glam::Quat::IDENTITY, + glam::Vec3::new(npos.x, npos.y, 0.0), + ), + ); + unsafe { + ctx.gl.bind_vertex_array(Some(self.vao)); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + vertices.as_ptr() as _, + vertices.len() * std::mem::size_of::() * 2, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.texcoords_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + texcoords.as_ptr() as _, + texcoords.len() * std::mem::size_of::() * 2, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.colors_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + colors.as_ptr() as _, + colors.len() * std::mem::size_of::() * 3, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + &index_bytes, + glow::STATIC_DRAW, + ); + ctx.gl.draw_elements(glow::TRIANGLES, indices.len() as _, glow::UNSIGNED_INT, 0); + } + } + + pub fn render_text(&self, ctx: &context::Context, pos: &glam::Vec2, text: &str) { + self.render_text_helper(ctx, pos, text, &[]); + } +} + +pub struct AtlasInfo { + pub pos: usize, +} + +pub struct TrueType { + pub shader: shader::Shader, + pub font: fontdue::Font, + pub atlas: texture::Texture, + pub atlaswidth: usize, + pub cellwidth: usize, + pub cellheight: usize, + pub info: HashMap, + pub vao: glow::VertexArray, + pub vertex_buf: glow::Buffer, + pub texcoords_buf: glow::Buffer, + pub colors_buf: glow::Buffer, + pub index_buf: glow::Buffer, +} + +impl TrueType { + pub fn new(ctx: &context::Context, size: f32, data: &[u8]) -> Self { + let shader = shader::Shader::new_nolib( + &ctx, + include_str!("assets/shaders/truetype/vert.glsl"), + include_str!("assets/shaders/truetype/frag.glsl"), + ); + let font = fontdue::Font::from_bytes(data, fontdue::FontSettings::default()) + .expect("failed to load font"); + let mut chardata = HashMap::new(); + for ci in 0..128 { + if let Some(c) = char::from_u32(ci) { + if !c.is_ascii_graphic() { continue; } + let res = font.rasterize(c, size); + chardata.insert(c, res); + } + } + let mut cellwidth = 0; + let mut cellbase = 0; + let mut cellextra = 0; + for (_, (m, _)) in &chardata { + if m.width > cellwidth { cellwidth = m.width } + if m.height > cellbase { cellbase = m.height } + let extra = (-m.ymin.min(0)) as usize; + if extra > cellextra { cellextra = extra } + } + let mut cellheight = cellbase + cellextra; + cellwidth = cellwidth.next_power_of_two(); + cellheight = cellheight.next_power_of_two(); + let atlaswidth = (chardata.len() * cellwidth).next_power_of_two(); + let mut info = HashMap::new(); + let mut atlas_bmp: Vec = vec![0; atlaswidth * cellheight]; + for (i, (c, (m, bmp))) in chardata.iter().enumerate() { + let by = ((cellbase as i32) - (m.height as i32) - m.ymin) as usize; + let bx = cellwidth * i; + info.insert(*c, AtlasInfo { + pos: cellwidth * i, + }); + for x in 0..m.width { + for y in 0..m.height { + atlas_bmp[bx + x + (by + y) * atlaswidth] = bmp[x + y * m.width]; + } + } + } + let atlas = texture::Texture::new_empty(ctx); + unsafe { + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(atlas.tex)); + ctx.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); + ctx.gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::R8 as i32, + atlaswidth as i32, + cellheight as i32, + 0, + glow::RED, + glow::UNSIGNED_BYTE, + Some(&atlas_bmp), + ); + ctx.gl.generate_mipmap(glow::TEXTURE_2D); + let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); + ctx.gl.bind_vertex_array(Some(vao)); + let vertex_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 2, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); + let texcoords_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); + let colors_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(colors_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_COLOR, 3, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_COLOR); + let index_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buf)); + Self { + shader, font, atlas, atlaswidth, cellwidth, cellheight, info, + vao, vertex_buf, texcoords_buf, colors_buf, index_buf, + } + } + } + + pub fn render_text_helper(&self, ctx: &context::Context, pos: &glam::Vec2, spacing: &glam::Vec2, text: &str, color: &[glam::Vec3]) { + let mut cur = glam::Vec2::new(0.0, 0.0); + let mut vertices = Vec::new(); + let mut texcoords = Vec::new(); + let mut colors = Vec::new(); + let mut indices = Vec::new(); + let cellwidth = self.cellwidth as f32; + let cellheight = self.cellheight as f32; + let cwidth = cellwidth / self.atlaswidth as f32; + let cheight = 1.0; + for (i, c) in text.chars().enumerate() { + if c == '\n' { + cur.x = 0.0; + cur.y -= spacing.y; + } else { + let idx = vertices.len() as u32; + if let Some(off) = self.info.get(&c) { + vertices.push(cur); + vertices.push(cur + glam::Vec2::new(cellwidth, 0.0)); + vertices.push(cur + glam::Vec2::new(cellwidth, cellheight)); + vertices.push(cur + glam::Vec2::new(0.0, cellheight)); + let tcbase = glam::Vec2::new(off.pos as f32 / self.atlaswidth as f32, 0.0); + texcoords.push(tcbase + glam::Vec2::new(0.0, cheight)); + texcoords.push(tcbase + glam::Vec2::new(cwidth, cheight)); + texcoords.push(tcbase + glam::Vec2::new(cwidth, 0.0)); + texcoords.push(tcbase); + let c = if let Some(c) = color.get(i) { + *c + } else { + glam::Vec3::new(1.0, 1.0, 1.0) + }; + colors.push(c); colors.push(c); colors.push(c); colors.push(c); + indices.push(idx + 0); indices.push(idx + 1); indices.push(idx + 2); + indices.push(idx + 0); indices.push(idx + 3); indices.push(idx + 2); + } + cur.x += spacing.x; + } + } + let index_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); + let scale = glam::Vec2::new(2.0 / ctx.render_width, 2.0 / ctx.render_height); + let offset = glam::Vec2::new( + -ctx.render_width / 2.0, + ctx.render_height / 2.0 - cellheight as f32, + ); + let npos = (glam::Vec2::new(pos.x, -pos.y) + offset) * scale; + self.shader.bind(ctx); + self.shader.set_mat4( + ctx, "transform", + &glam::Mat4::from_scale_rotation_translation( + glam::Vec3::new(scale.x, scale.y, 1.0), + glam::Quat::IDENTITY, + glam::Vec3::new(npos.x, npos.y, 0.0), + ), + ); + unsafe { + ctx.gl.active_texture(glow::TEXTURE0); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.atlas.tex)); + ctx.gl.bind_vertex_array(Some(self.vao)); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + vertices.as_ptr() as _, + vertices.len() * std::mem::size_of::() * 2, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.texcoords_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + texcoords.as_ptr() as _, + texcoords.len() * std::mem::size_of::() * 2, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.colors_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + colors.as_ptr() as _, + colors.len() * std::mem::size_of::() * 3, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + &index_bytes, + glow::STATIC_DRAW, + ); + ctx.gl.draw_elements(glow::TRIANGLES, indices.len() as _, glow::UNSIGNED_INT, 0); + } + } +} diff --git a/crates/teleia/src/framebuffer.rs b/crates/teleia/src/framebuffer.rs new file mode 100644 index 0000000..db808e4 --- /dev/null +++ b/crates/teleia/src/framebuffer.rs @@ -0,0 +1,145 @@ +use glow::HasContext; + +use crate::context; + +pub struct Framebuffer { + pub tex: Option, + pub fbo: Option, + pub dims: glam::Vec2, + pub offsets: glam::Vec2, +} + +impl Framebuffer { + pub fn screen(ctx: &context::Context) -> Self { + #[cfg(target_arch = "wasm32")] + let (windoww, windowh): (f32, f32) = if ctx.resize { + ctx.window.inner_size().into() + } else { + (ctx.render_width, ctx.render_height) + }; + #[cfg(not(target_arch = "wasm32"))] + let (windoww, windowh) = { + let (w, h) = ctx.window.borrow().get_size(); + (w as f32, h as f32) + }; + let ratio = ctx.compute_upscale(windoww as _, windowh as _) as f32; + let upscalew = ctx.render_width * ratio; + let upscaleh = ctx.render_height * ratio; + let offsetx = (windoww - upscalew) / 2.0; + let offsety = (windowh - upscaleh) / 2.0; + log::info!("resize window: {:?}, upscale: {:?}, offset: {:?}", (windoww, windowh), (upscalew, upscaleh), (offsetx, offsety)); + Self { + tex: None, + fbo: None, + dims: glam::Vec2::new(upscalew, upscaleh), + offsets: glam::Vec2::new(offsetx, offsety), + } + } + + pub fn new(ctx: &context::Context, dims: &glam::Vec2, offsets: &glam::Vec2) -> Self { + unsafe { + let fbo = ctx.gl.create_framebuffer() + .expect("failed to create framebuffer"); + ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); + + let depth_buffer = ctx.gl.create_renderbuffer() + .expect("failed to create depth buffer"); + ctx.gl.bind_renderbuffer(glow::RENDERBUFFER, Some(depth_buffer)); + ctx.gl.renderbuffer_storage(glow::RENDERBUFFER, glow::DEPTH_COMPONENT32F, dims.x as _, dims.y as _); + ctx.gl.framebuffer_renderbuffer(glow::FRAMEBUFFER, glow::DEPTH_ATTACHMENT, glow::RENDERBUFFER, Some(depth_buffer)); + + let stencil_buffer = ctx.gl.create_renderbuffer() + .expect("failed to create stencil buffer"); + ctx.gl.bind_renderbuffer(glow::RENDERBUFFER, Some(stencil_buffer)); + ctx.gl.renderbuffer_storage(glow::RENDERBUFFER, glow::DEPTH_STENCIL, dims.x as _, dims.y as _); + ctx.gl.framebuffer_renderbuffer(glow::FRAMEBUFFER, glow::DEPTH_STENCIL_ATTACHMENT, glow::RENDERBUFFER, Some(stencil_buffer)); + + let tex = ctx.gl.create_texture() + .expect("failed to create framebuffer texture"); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); + ctx.gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::RGBA as _, + dims.x as _, + dims.y as _, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + None, + ); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as _); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as _); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as _); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as _); + ctx.gl.framebuffer_texture_2d(glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::TEXTURE_2D, Some(tex), 0); + ctx.gl.draw_buffer(glow::COLOR_ATTACHMENT0); + + let status = ctx.gl.check_framebuffer_status(glow::FRAMEBUFFER); + if status != glow::FRAMEBUFFER_COMPLETE { + panic!("error initializing framebuffer:\n{}", status); + } + + Self { + tex: Some(tex), + fbo: Some(fbo), + dims: dims.clone(), + offsets: offsets.clone(), + } + } + } + + pub fn bind_texture(&self, ctx: &context::Context) { + unsafe { + ctx.gl.active_texture(glow::TEXTURE0); + ctx.gl.bind_texture(glow::TEXTURE_2D, self.tex); + } + } + + pub fn bind(&self, ctx: &context::Context) { + unsafe { + ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, self.fbo); + ctx.gl.viewport( + self.offsets.x as _, + self.offsets.y as _, + self.dims.x as _, + self.dims.y as _, + ); + } + } + + pub fn blit(&self, ctx: &context::Context, dest: &Self, pos: &glam::Vec2, scale: &glam::Vec2) { + unsafe { + ctx.gl.bind_framebuffer(glow::READ_FRAMEBUFFER, self.fbo); + ctx.gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, dest.fbo); + ctx.gl.blit_framebuffer( + 0, 0, self.dims.x as _, self.dims.y as _, + pos.x as _, pos.y as _, (pos.x + scale.x) as _, (pos.y + scale.y) as _, + glow::COLOR_BUFFER_BIT, glow::NEAREST + ); + } + } + + pub fn get_pixels(&self, ctx: &context::Context, buf: &mut [glam::Vec3]) { + let w = self.dims.x as usize; + let h = self.dims.y as usize; + let tmp = vec![glam::Vec3::default(); w * h]; + unsafe { + ctx.gl.bind_texture(glow::TEXTURE_2D, self.tex); + ctx.gl.get_tex_image( + glow::TEXTURE_2D, 0, glow::RGB, glow::FLOAT, + glow::PixelPackData::Slice( + std::slice::from_raw_parts_mut( + tmp.as_ptr() as *mut u8, + tmp.len() * std::mem::size_of::() + ) + ), + ); + } + for y in 0..h { + for x in 0..w { + buf[x + (h - 1 - y) * w] = tmp[x + y * w]; + } + } + } +} diff --git a/crates/teleia/src/helpers.js b/crates/teleia/src/helpers.js new file mode 100644 index 0000000..aaaafa1 --- /dev/null +++ b/crates/teleia/src/helpers.js @@ -0,0 +1,13 @@ +let resized = false; + +export async function js_track_resized_setup() { + window.addEventListener("resize", () => { + resized = true; + }); +} + +export function js_poll_resized() { + let ret = resized; + resized = false; + return ret; +} diff --git a/crates/teleia/src/js/module.js b/crates/teleia/src/js/module.js new file mode 100644 index 0000000..f7bff39 --- /dev/null +++ b/crates/teleia/src/js/module.js @@ -0,0 +1,7 @@ +export function js_build_interface() { + return { + env: { + log_info: window.wasmBindings.log_info, + }, + }; +} diff --git a/crates/teleia/src/level2d.rs b/crates/teleia/src/level2d.rs new file mode 100644 index 0000000..8b033ff --- /dev/null +++ b/crates/teleia/src/level2d.rs @@ -0,0 +1 @@ +pub mod tiled; diff --git a/crates/teleia/src/level2d/tiled.rs b/crates/teleia/src/level2d/tiled.rs new file mode 100644 index 0000000..466260a --- /dev/null +++ b/crates/teleia/src/level2d/tiled.rs @@ -0,0 +1,298 @@ +use std::collections::HashMap; +use serde::Deserialize; +use glow::HasContext; + +use crate::{context, erm, mesh, shader, texture, Erm}; + +#[derive(Debug)] +pub enum Err { + LayerIndexOutOfBounds, + LayerDataTooSmall, + GIDNotFound(u32), + AssetNotFound(String), + GL(String), +} +impl std::error::Error for Err {} +impl std::fmt::Display for Err { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::LayerIndexOutOfBounds => write!(f, "layer index out of bounds"), + Self::LayerDataTooSmall => write!(f, "layer data too small for dimensions"), + Self::GIDNotFound(gid) => write!(f, "GID not found: {}", gid), + Self::AssetNotFound(ass) => write!(f, "asset not found: {}", ass), + Self::GL(msg) => write!(f, "GL error: {msg:}"), + } + } +} + +#[derive(Debug, Deserialize)] +pub enum LayerType { + #[serde(rename = "tilelayer")] + Tile, + #[serde(rename = "imagelayer")] + Image, + #[serde(rename = "objectgroup")] + ObjectGroup, + #[serde(rename = "group")] + Group, +} + +#[derive(Debug, Deserialize)] +pub struct Layer { + name: String, + id: i32, + #[serde(rename = "type")] ty: LayerType, + width: i32, height: i32, + x: i32, y: i32, + opacity: f32, + visible: bool, + data: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct LevelTileset { + firstgid: i32, + source: String, +} + +#[derive(Debug, Deserialize)] +pub struct Level { + width: i32, height: i32, + tilewidth: i32, tileheight: i32, + layers: Vec, + tilesets: Vec, +} +impl Level { + pub fn new(bytes: &str) -> Erm { + Ok(serde_json::from_str(bytes)?) + } +} + +#[derive(Debug, Deserialize)] +pub struct Tileset { + name: String, + imagewidth: i32, imageheight: i32, + tilewidth: i32, tileheight: i32, + margin: i32, spacing: i32, +} +impl Tileset { + pub fn new(bytes: &str) -> Erm { + Ok(serde_json::from_str(bytes)?) + } +} + +// TODO +pub enum Flip { + None, +} + +pub struct Asset { + tileset: Tileset, + texture: texture::Texture, +} +pub struct Assets { + entries: HashMap, +} +impl Assets { + pub fn new() -> Self { Self { entries: HashMap::new() } } + pub fn load(&mut self, ctx: &context::Context, nm: &str, ts: &str, img: &[u8]) -> Erm<()> { + let ass = Asset { + tileset: Tileset::new(ts)?, + texture: texture::Texture::new(ctx, img), + }; + if self.entries.insert(nm.to_string(), ass).is_some() { + log::warn!("duplicate tileset entry named: {}", nm); + } + Ok(()) + } + pub fn lookup_gid(&self, level: &Level, gid: u32) -> Erm<(i32, &Asset, Flip)> { + let offset = (gid & 0x0fffffff) as i32; + for lts in level.tilesets.iter().rev() { + if lts.firstgid <= offset { + return Ok(( + offset - lts.firstgid, + self.entries.get(<s.source).ok_or(Err::AssetNotFound(lts.source.clone()))?, + Flip::None + )) + } + } + return erm(Err::GIDNotFound(gid)); + } +} + +pub struct LayerRenderer { + pub vao: glow::VertexArray, + pub vertex_buf: glow::Buffer, + pub texcoords_buf: glow::Buffer, + pub index_buf: glow::Buffer, + pub index_count: usize, +} +impl LayerRenderer { + pub fn new(ctx: &context::Context) -> Erm { + unsafe { + let vao = ctx.gl.create_vertex_array().map_err(Err::GL)?; + ctx.gl.bind_vertex_array(Some(vao)); + let vertex_buf = ctx.gl.create_buffer().map_err(Err::GL)?; + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 2, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); + let texcoords_buf = ctx.gl.create_buffer().map_err(Err::GL)?; + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_buf)); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); + let index_buf = ctx.gl.create_buffer().map_err(Err::GL)?; + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buf)); + Ok(Self { + vao, + vertex_buf, + texcoords_buf, + index_buf, + index_count: 0, + }) + } + } +} +pub struct LevelRenderer { + pub layers: Vec, + pub shader: shader::Shader, +} +impl LevelRenderer { + pub fn new(ctx: &context::Context, level: &Level) -> Erm { + let mut layers = Vec::new(); + for _ in level.layers.iter() { + layers.push(LayerRenderer::new(ctx)?); + } + let shader = shader::Shader::new_nolib( + &ctx, + include_str!("../assets/shaders/tiled/vert.glsl"), + include_str!("../assets/shaders/tiled/frag.glsl"), + ); + Ok(Self { + layers, + shader, + }) + } + pub fn populate_layer( + &mut self, + ctx: &context::Context, + assets: &Assets, + level: &Level, + lidx: usize + ) -> Erm<()> { + let lr = self.layers.get_mut(lidx).ok_or(Err::LayerIndexOutOfBounds)?; + let layer = level.layers.get(lidx).ok_or(Err::LayerIndexOutOfBounds)?; + let mut vertices = Vec::new(); + let mut texcoords = Vec::new(); + let mut indices = Vec::new(); + for y in 0..layer.height { + for x in 0..layer.width { + let idx = x as usize + (y * layer.width) as usize; + let gid = *layer.data.get(idx).ok_or(Err::LayerDataTooSmall)?; + if gid == 0 { continue; } + let (lid, ass, _) = assets.lookup_gid(level, gid)?; + let cols = ass.tileset.imagewidth / ass.tileset.tilewidth; + let rows = ass.tileset.imageheight / ass.tileset.tileheight; + let col = lid % cols; + let row = lid / cols; + let twidth = 1.0 / cols as f32; + let theight = 1.0 / rows as f32; + + let i = vertices.len() as u32; + let v = glam::Vec2::new(x as _, y as _); + vertices.push(v); + vertices.push(v + glam::Vec2::new(1.0, 0.0)); + vertices.push(v + glam::Vec2::new(1.0, 1.0)); + vertices.push(v + glam::Vec2::new(0.0, 1.0)); + let uvbase = glam::Vec2::new(col as f32 / cols as f32, row as f32 / rows as f32); + texcoords.push(uvbase + glam::Vec2::new(0.0, theight)); + texcoords.push(uvbase + glam::Vec2::new(twidth, theight)); + texcoords.push(uvbase + glam::Vec2::new(twidth, 0.0)); + texcoords.push(uvbase); + indices.push(i + 0); indices.push(i + 1); indices.push(i + 2); + indices.push(i + 0); indices.push(i + 3); indices.push(i + 2); + } + } + let index_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); + unsafe { + ctx.gl.bind_vertex_array(Some(lr.vao)); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(lr.vertex_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + vertices.as_ptr() as _, + vertices.len() * std::mem::size_of::() * 2, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(lr.texcoords_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + texcoords.as_ptr() as _, + texcoords.len() * std::mem::size_of::() * 2, + ), + glow::STATIC_DRAW, + ); + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(lr.index_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + &index_bytes, + glow::STATIC_DRAW, + ); + lr.index_count = indices.len(); + } + Ok(()) + } + pub fn render_layer( + &self, + ctx: &context::Context, + assets: &Assets, + level: &Level, + lidx: usize, + ) -> Erm<()> { + let layer = level.layers.get(lidx).ok_or(Err::LayerIndexOutOfBounds)?; + let lr = self.layers.get(lidx).ok_or(Err::LayerIndexOutOfBounds)?; + // TODO: handle layers with multiple textures + let gid = *layer.data.iter().find(|g| **g > 0).unwrap(); + let (_, ass, _) = assets.lookup_gid(level, gid)?; + ass.texture.bind(ctx); + unsafe { + ctx.gl.bind_vertex_array(Some(lr.vao)); + ctx.gl.draw_elements(glow::TRIANGLES, lr.index_count as _, glow::UNSIGNED_INT, 0); + } + Ok(()) + } + pub fn populate( + &mut self, + ctx: &context::Context, + assets: &Assets, + level: &Level, + ) -> Erm<()> { + for lidx in 0..level.layers.len() { + self.populate_layer(ctx, assets, level, lidx)?; + } + Ok(()) + } + pub fn render( + &self, + ctx: &context::Context, + assets: &Assets, + level: &Level, + ) -> Erm<()> { + self.shader.bind(ctx); + let sx = 2.0 * level.tilewidth as f32 / ctx.render_width; + let sy = 2.0 * level.tileheight as f32 / ctx.render_height; + self.shader.set_mat4( + ctx, "transform", + &glam::Mat4::from_scale_rotation_translation( + glam::Vec3::new(sx, -sy, 1.0), + glam::Quat::IDENTITY, + glam::Vec3::new(0.0, 0.0, 0.0), + ), + ); + for lidx in 0..level.layers.len() { + self.render_layer(ctx, assets, level, lidx)?; + } + Ok(()) + } +} diff --git a/crates/teleia/src/lib.rs b/crates/teleia/src/lib.rs new file mode 100644 index 0000000..916a405 --- /dev/null +++ b/crates/teleia/src/lib.rs @@ -0,0 +1,328 @@ +pub mod utils; +pub mod ui; +pub mod context; +pub mod state; +pub mod framebuffer; +pub mod shader; +pub mod mesh; +pub mod texture; +pub mod scene; +pub mod font; +pub mod shadow; +pub mod audio; +pub mod net; +pub mod physics; +pub mod save; +pub mod level2d; + +pub use utils::{erm, install_error_handler, Erm}; +pub use color_eyre::eyre::WrapErr; + +#[cfg(target_arch = "wasm32")] +use winit::platform::web::EventLoopExtWebSys; + +#[cfg(target_arch = "wasm32")] +use winit::platform::web::WindowExtWebSys; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsCast; + +#[cfg(not(target_arch = "wasm32"))] +use glfw::Context; + +use bitflags::bitflags; +bitflags! { + pub struct Options: u32 { + const OVERLAY = 0b00000001; + const HIDDEN = 0b00000010; + const NORESIZE = 0b00000100; + } +} + +static mut CTX: Option<*const context::Context> = None; +static mut ST: Option<*mut state::State> = None; +static mut G: Option<*mut std::ffi::c_void> = None; + +pub fn contextualize(mut f: F) -> X +where + G: state::Game + 'static, + F: FnMut(&context::Context, &mut state::State, &mut G) -> X { + unsafe { + match (CTX, ST, G) { + (Some(c), Some(s), Some(g)) => f(&*c, &mut*s, &mut*(g as *mut G)), + _ => panic!("context not set"), + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn run<'a, F, G>(title: &str, w: u32, h: u32, options: Options, gnew: F) -> Erm<()> +where + G: state::Game + 'static, + F: (Fn(&'a context::Context) -> G), +{ + env_logger::Builder::new() + .filter(None, log::LevelFilter::Info) + .init(); + install_error_handler(); + + log::info!("hello computer, starting up..."); + + let resize = !options.contains(Options::NORESIZE); + let (rglfw, rwindow, gl, events) = { + use glfw::fail_on_errors; + let mut glfw = glfw::init(glfw::fail_on_errors!()).expect("failed to initialize GLFW"); + // let gl_attr = video.gl_attr(); + // gl_attr.set_context_profile(sdl2::video::GLProfile::Core); + // gl_attr.set_context_version(3, 0); + let (mut window, events) = glfw.with_primary_monitor(|glfw, primary| { + if options.contains(Options::HIDDEN) { + glfw.window_hint(glfw::WindowHint::Visible(false)); + glfw.create_window(w as _, h as _, title, glfw::WindowMode::Windowed) + .expect("failed to create window") + } else if options.contains(Options::OVERLAY) { + let mon = primary.expect("failed to get monitor"); + let mode = mon.get_video_mode().expect("failed to get video mode"); + glfw.window_hint(glfw::WindowHint::RedBits(Some(mode.red_bits))); + glfw.window_hint(glfw::WindowHint::GreenBits(Some(mode.green_bits))); + glfw.window_hint(glfw::WindowHint::BlueBits(Some(mode.blue_bits))); + glfw.window_hint(glfw::WindowHint::RefreshRate(Some(mode.refresh_rate))); + glfw.window_hint(glfw::WindowHint::Resizable(false)); + glfw.window_hint(glfw::WindowHint::Decorated(false)); + glfw.window_hint(glfw::WindowHint::Floating(true)); + glfw.window_hint(glfw::WindowHint::TransparentFramebuffer(true)); + unsafe { + // glfw.window_hint(glfw::WindowHint::MousePassthrough(true)); + glfw::ffi::glfwWindowHint(0x0002000D, 1); // mouse passthrough + } + glfw.create_window(mode.width, mode.height, title, glfw::WindowMode::FullScreen(mon)) + .expect("failed to create window") + } else { + glfw.create_window(w as _, h as _, title, glfw::WindowMode::Windowed) + .expect("failed to create window") + } + }); + window.make_current(); + window.set_key_polling(true); + window.set_mouse_button_polling(true); + window.set_size_polling(true); + window.set_focus_polling(true); + window.set_cursor_pos_polling(true); + let gl = unsafe { + glow::Context::from_loader_function(|s| window.get_proc_address(s) as *const _) + }; + glfw.set_swap_interval(glfw::SwapInterval::Sync(1)); + (glfw, window, gl, events) + }; + let glfw = std::cell::RefCell::new(rglfw); + let window = std::cell::RefCell::new(rwindow); + + let ctx = Box::leak(Box::new(context::Context::new( + glfw, window, gl, + w as f32, h as f32, resize, + ))); + let game = Box::leak(Box::new(gnew(ctx))); + let st = Box::leak(Box::new(state::State::new(&ctx))); + + unsafe { + CTX = Some(ctx as _); + ST = Some(st as _); + G = Some(game as *mut G as *mut std::ffi::c_void); + } + + game.initialize(ctx, st)?; + 'running: loop { + if ctx.window.borrow().should_close() { + game.finalize(ctx, st)?; + log::info!("bye!"); + break 'running; + } + ctx.glfw.borrow_mut().poll_events(); + for (_, event) in glfw::flush_messages(&events) { + match event { + glfw::WindowEvent::Size(_, _) => st.handle_resize(&ctx), + glfw::WindowEvent::Focus(false) => { + st.keys = state::Keys::new(); + }, + glfw::WindowEvent::CursorPos(x, y) => { + st.mouse_moved(&ctx, x as f32, y as f32, game); + } + glfw::WindowEvent::MouseButton(_, glfw::Action::Press, _) => { + st.mouse_pressed(&ctx, game) + }, + glfw::WindowEvent::MouseButton(_, glfw::Action::Release, _) => { + st.mouse_released(&ctx) + }, + glfw::WindowEvent::Key(key, _, glfw::Action::Press, _) => { + st.key_pressed(&ctx, state::Keycode::new(key)) + }, + glfw::WindowEvent::Key(key, _, glfw::Action::Release, _) => { + st.key_released(&ctx, state::Keycode::new(key)) + }, + _ => {}, + } + } + if ctx.resize_necessary() { + st.handle_resize(&ctx); + } + if let Some(f) = &mut st.request { + match std::future::Future::poll(f.as_mut(), &mut st.waker_ctx) { + std::task::Poll::Pending => {}, + std::task::Poll::Ready(res) => { + st.request = None; + match res { + Ok(r) => st.request_returned(&ctx, game, r), + Err(e) => log::warn!("error during HTTP request: {}", e), + } + }, + } + } + st.run_update(&ctx, game)?; + st.run_render(&ctx, game)?; + ctx.window.borrow_mut().swap_buffers(); + } + Ok(()) +} + +#[cfg(target_arch = "wasm32")] +pub fn run<'a, F, G>(w: u32, h: u32, options: Options, gnew: F) +where + G: state::Game + 'static, + F: (Fn(&'a context::Context) -> G), +{ + console_log::init_with_level(log::Level::Debug).unwrap(); + console_error_panic_hook::set_once(); + tracing_wasm::set_as_global_default(); + // install_error_handler(); + + log::info!("hello computer, starting up..."); + + let event_loop = winit::event_loop::EventLoop::new() + .expect("failed to initialize event loop"); + + let resize = !options.contains(Options::NORESIZE); + let (window, gl) = { + let window = winit::window::WindowBuilder::new() + .with_maximized(resize) + .with_decorations(false) + .build(&event_loop) + .expect("failed to initialize window"); + let gl = web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + let dst = doc.get_element_by_id("teleia-parent")?; + let canvas = web_sys::Element::from(window.canvas().expect("failed to find canvas")); + dst.append_child(&canvas).ok()?; + let c = canvas.dyn_into::().ok()?; + let webgl2_context = c.get_context("webgl2").ok()?? + .dyn_into::().ok()?; + Some(glow::Context::from_webgl2_context(webgl2_context)) + }) + .expect("couldn't add canvas to document"); + (window, gl) + }; + + let ctx = Box::leak(Box::new(context::Context::new(window, gl, w as f32, h as f32, resize))); + ctx.maximize_canvas(); + let game = Box::leak(Box::new(gnew(ctx))); + let st = Box::leak(Box::new(state::State::new(&ctx))); + + unsafe { + CTX = Some(ctx as _); + ST = Some(st as _); + G = Some(game as *mut G as *mut std::ffi::c_void); + } + + let _ = game.initialize(ctx, st); + let res = std::rc::Rc::new(std::cell::RefCell::new(Ok(()))); + let result = res.clone(); + event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait); + event_loop.spawn(move |event, elwt| { + let res: Erm<()> = contextualize(|ctx, st, game: &mut G| { + match &event { + winit::event::Event::WindowEvent { + event: wev, + window_id, + .. + } => match wev { + winit::event::WindowEvent::CloseRequested + if *window_id == ctx.window.id() => elwt.exit(), + winit::event::WindowEvent::Resized{..} => { + #[cfg(target_arch = "wasm32")] + ctx.maximize_canvas(); + st.handle_resize(&ctx); + }, + winit::event::WindowEvent::Focused(false) => { + st.keys = state::Keys::new(); + }, + winit::event::WindowEvent::CursorMoved { position, ..} => { + st.mouse_moved(&ctx, position.x as f32, position.y as f32, game); + }, + winit::event::WindowEvent::MouseInput { + state, + .. + } => match state { + winit::event::ElementState::Pressed => { + st.mouse_pressed(&ctx, game) + }, + winit::event::ElementState::Released => { + st.mouse_released(&ctx) + }, + } + winit::event::WindowEvent::KeyboardInput { + event: winit::event::KeyEvent { + physical_key: winit::keyboard::PhysicalKey::Code(key), + state, + repeat: false, + .. + }, + .. + } => match state { + winit::event::ElementState::Pressed => { + st.key_pressed(&ctx, *key) + }, + winit::event::ElementState::Released => { + st.key_released(&ctx, *key) + }, + } + _ => {}, + }, + + winit::event::Event::AboutToWait => { + if ctx.resize_necessary() { + #[cfg(target_arch = "wasm32")] + ctx.maximize_canvas(); + st.handle_resize(&ctx); + } + if let Some(f) = &mut st.request { + match std::future::Future::poll(f.as_mut(), &mut st.waker_ctx) { + std::task::Poll::Pending => {}, + std::task::Poll::Ready(res) => { + st.request = None; + match res { + Ok(r) => st.request_returned(&ctx, game, r), + Err(e) => log::warn!("error during HTTP request: {}", e), + } + }, + } + // f.poll(); + } + st.run_update(&ctx, game)?; + st.run_render(&ctx, game)?; + ctx.window.request_redraw(); + }, + + _ => {}, + } + Ok(()) + }); + if let Err(e) = res { + *result.borrow_mut() = Err(e); + elwt.exit(); + } + }); + let _ = game.finalize(ctx, st); + if let Err(e) = res.replace(Ok(())) { + panic!("{}", e); + } +} diff --git a/crates/teleia/src/mesh.rs b/crates/teleia/src/mesh.rs new file mode 100644 index 0000000..7209de1 --- /dev/null +++ b/crates/teleia/src/mesh.rs @@ -0,0 +1,132 @@ +use glow::HasContext; + +use crate::context; + +pub const ATTRIB_VERTEX: u32 = 0; +pub const ATTRIB_NORMAL: u32 = 1; +pub const ATTRIB_TEXCOORD: u32 = 2; +pub const ATTRIB_JOINT: u32 = 3; +pub const ATTRIB_WEIGHT: u32 = 4; +pub const ATTRIB_COLOR: u32 = 5; + +pub struct Mesh { + pub vao: glow::VertexArray, + pub mode: u32, // glow::TRIANGLES, etc. + pub index_count: usize, + pub index_type: u32, // glow::BYTE, glow::FLOAT, etc. + pub index_offset: i32, +} + +impl Mesh { + pub fn build( + ctx: &context::Context, + vertices: &Vec, + indices: &Vec, + snormals: &Option>, + stexcoords: &Option>, + ) -> Self { + unsafe { + let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); + ctx.gl.bind_vertex_array(Some(vao)); + + let buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + vertices.as_ptr() as _, + vertices.len() * std::mem::size_of::(), + ), + glow::STATIC_DRAW, + ); + ctx.gl.vertex_attrib_pointer_f32(ATTRIB_VERTEX, 3, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(ATTRIB_VERTEX); + + let indices_vbo = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(indices_vbo)); + ctx.gl.buffer_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + std::slice::from_raw_parts( + indices.as_ptr() as _, + indices.len() * std::mem::size_of::(), + ), + glow::STATIC_DRAW, + ); + + if let Some(normals) = snormals { + let normals_vbo = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(normals_vbo)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + normals.as_ptr() as _, + normals.len() * std::mem::size_of::(), + ), + glow::STATIC_DRAW, + ); + ctx.gl.vertex_attrib_pointer_f32(ATTRIB_NORMAL, 3, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(ATTRIB_NORMAL); + } + + if let Some(texcoords) = stexcoords { + let texcoords_vbo = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_vbo)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + texcoords.as_ptr() as _, + texcoords.len() * std::mem::size_of::(), + ), + glow::STATIC_DRAW, + ); + ctx.gl.vertex_attrib_pointer_f32(ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); + ctx.gl.enable_vertex_attrib_array(ATTRIB_TEXCOORD); + } + + Self { + vao, + mode: glow::TRIANGLES, + index_count: indices.len(), + index_type: glow::UNSIGNED_INT, + index_offset: 0, + } + } + } + + pub fn from_obj(ctx: &context::Context, mut bytes: &[u8]) -> Self { + let lopts = tobj::LoadOptions { + triangulate: true, + single_index: true, + ..Default::default() + }; + let (meshes, _materials) = tobj::load_obj_buf( + &mut bytes, + &lopts, + |_| Err(tobj::LoadError::GenericFailure) + ).expect("failed to load mesh"); + let mesh = meshes.into_iter().next() + .expect("failed to load mesh") + .mesh; + Self::build( + ctx, + &mesh.positions, + &mesh.indices, + &Some(mesh.normals), + &Some(mesh.texcoords), + ) + } + + pub fn render(&self, ctx: &context::Context) { + unsafe { + ctx.gl.bind_vertex_array(Some(self.vao)); + ctx.gl.draw_elements(self.mode, self.index_count as _, self.index_type, self.index_offset); + } + } + + pub fn render_instanced(&self, ctx: &context::Context, count: u64) { + unsafe { + ctx.gl.bind_vertex_array(Some(self.vao)); + ctx.gl.draw_elements_instanced(self.mode, self.index_count as _, self.index_type, self.index_offset, count as _); + } + } +} diff --git a/crates/teleia/src/net.rs b/crates/teleia/src/net.rs new file mode 100644 index 0000000..b9babe5 --- /dev/null +++ b/crates/teleia/src/net.rs @@ -0,0 +1 @@ +pub mod client; diff --git a/crates/teleia/src/net/client.rs b/crates/teleia/src/net/client.rs new file mode 100644 index 0000000..fcd51b8 --- /dev/null +++ b/crates/teleia/src/net/client.rs @@ -0,0 +1,2 @@ +#[cfg(target_arch = "wasm32")] +pub mod wasm; diff --git a/crates/teleia/src/net/client/wasm.rs b/crates/teleia/src/net/client/wasm.rs new file mode 100644 index 0000000..d9936e5 --- /dev/null +++ b/crates/teleia/src/net/client/wasm.rs @@ -0,0 +1,67 @@ +use std::{collections::VecDeque, sync::{Arc, Mutex}}; + +use wasm_bindgen::prelude::*; + +#[derive(Debug)] +pub enum Message { + Binary(Vec), + Text(String), +} + +impl Message { + pub fn from_messageevent(e: web_sys::MessageEvent) -> Self { + if let Ok(abuf) = e.data().dyn_into::() { + let array = js_sys::Uint8Array::new(&abuf).to_vec(); + Message::Binary(array) + } else if let Ok(txt) = e.data().dyn_into::() { + Message::Text(txt.into()) + } else { + panic!("received weird websocked message: {:?}", e); + } + } +} + +pub struct Client { + pub ws: Option, + pub messages: Arc>>, +} + +impl Client { + pub fn new() -> Self { + Self { + ws: None, + messages: Arc::new(Mutex::new(VecDeque::new())), + } + } + pub fn connect(&mut self, url: &str) { + let ws = web_sys::WebSocket::new(url).expect("failed to open websocket"); + let messages_ref = self.messages.clone(); + let cb: Closure = Closure::new(move |e: web_sys::MessageEvent| { + let msg = Message::from_messageevent(e); + log::info!("incoming: {:?}", msg); + messages_ref.lock().unwrap().push_back(msg); + }); + ws.set_onmessage(Some(cb.as_ref().unchecked_ref())); + cb.forget(); + self.ws = Some(ws); + } + pub fn poll(&mut self) -> Option { + self.messages.lock().unwrap().pop_front() + } + pub fn send(&self, msg: Message) { + if let Some(ws) = &self.ws { + match msg { + Message::Text(txt) => { + if let Err(e) = ws.send_with_str(&txt) { + log::warn!("failed to send string: {:?}", e); + } + }, + Message::Binary(bytes) => { + if let Err(e) = ws.send_with_u8_array(&bytes) { + log::warn!("failed to send bytes: {:?}", e); + } + }, + } + } + } +} diff --git a/crates/teleia/src/physics.rs b/crates/teleia/src/physics.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/teleia/src/save.rs b/crates/teleia/src/save.rs new file mode 100644 index 0000000..3f244cc --- /dev/null +++ b/crates/teleia/src/save.rs @@ -0,0 +1,43 @@ +#[cfg(target_arch = "wasm32")] +pub fn save(id: &str, data: &W) where W: serde::Serialize { + let window = web_sys::window().expect("failed to get window object"); + let storage = window.local_storage() + .expect("failed to get local storage") + .expect("local storage not present"); + let key = format!("{}_save", id); + let val = serde_json::to_string(data).expect("failed to serialize save"); + storage.set_item(&key, &val).expect("failed to set save"); +} + +#[cfg(target_arch = "wasm32")] +pub fn load(id: &str) -> Option where W: serde::de::DeserializeOwned { + let window = web_sys::window().expect("failed to get window object"); + let storage = window.local_storage() + .expect("failed to get local storage") + .expect("local storage not present"); + let key = format!("{}_save", id); + let s = storage.get_item(&key).expect("failed to get save").expect("save not present"); + let mut cur = std::io::Cursor::new(s); + serde_json::from_reader(&mut cur).ok() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn save(id: &str, data: &W) where W: serde::Serialize { + let pd = directories::ProjectDirs::from("", "milkfat", id).expect("failed to get save directory"); + let _ = std::fs::create_dir_all(pd.data_dir()); + let path = pd.data_dir().join("teleia.save"); + let mut file = std::fs::File::create(&path).expect("failed to open save file"); + // serde_json::to_writer(file, data).expect("failed to write save file"); + bincode::serde::encode_into_std_write(data, &mut file, bincode::config::standard()) + .expect("failed to write save file"); +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn load(id: &str) -> Option where W: serde::de::DeserializeOwned { + let pd = directories::ProjectDirs::from("", "milkfat", id).expect("failed to get save directory"); + let _ = std::fs::create_dir_all(pd.data_dir()); + let path = pd.data_dir().join("teleia.save"); + let mut file = std::fs::File::open(&path).ok()?; + // serde_json::from_reader(file).ok() + bincode::serde::decode_from_std_read(&mut file, bincode::config::standard()).ok() +} diff --git a/crates/teleia/src/scene.rs b/crates/teleia/src/scene.rs new file mode 100644 index 0000000..3146b5d --- /dev/null +++ b/crates/teleia/src/scene.rs @@ -0,0 +1,398 @@ +use std::{collections::{HashMap, VecDeque}, mem::offset_of}; + +use glow::HasContext; +use image::EncodableLayout; + +use crate::{context, mesh, shader, texture}; + +pub type Index = usize; + +pub struct Primitive { + pub mesh: mesh::Mesh, + pub material: Index, +} + +pub struct Object { + pub primitives: Vec, +} + +pub struct Material { + pub base_color_factor: glam::Vec4, + pub base_color_texture: Option, + + pub metallic_factor: f32, + pub roughness_factor: f32, + pub metallic_roughness_texture: Option, + + pub normal_texture: Option, + + pub occlusion_texture: Option, + + pub emissive_factor: glam::Vec3, + pub emissive_texture: Option, +} + +pub struct Skin { + pub inverse_bind_matrices: Vec, + pub joints: Vec, +} + +pub enum ChannelValues { + Translation(Vec), + Rotation(Vec), + Scale(Vec), +} + +pub enum Interpolation { + Linear, + Step, + CubicSpline, +} + +pub struct Channel { + pub target: Index, + pub interpolation: Interpolation, + pub keyframes: Vec, + pub values: ChannelValues, +} + +pub struct Animation { + pub channels: Vec, +} + +pub struct Node { + pub children: Vec, + pub object: Option, + pub skin: Option, + pub transform: glam::Mat4, +} + +pub struct Scene { + pub objects: Vec, + pub textures: Vec, + pub materials: Vec, + pub skins: Vec, + pub animations: HashMap, + pub nodes: Vec, + pub nodes_by_name: HashMap, + pub scene_nodes: Vec, +} + +impl Scene { + pub fn load_default_shader(ctx: &context::Context) -> shader::Shader { + shader::Shader::new( + ctx, + include_str!("assets/shaders/scene/vert.glsl"), + include_str!("assets/shaders/scene/frag.glsl") + ) + } + pub fn from_gltf(ctx: &context::Context, bytes: &[u8]) -> Self { + let (gltf, buffers, images) = gltf::import_slice(bytes).expect("failed to parse GLTF"); + let get_buffer_data = |b: gltf::Buffer| { + buffers.get(b.index()).map(|gltf::buffer::Data(bytes)| bytes.as_slice()) + }; + let objects = gltf.meshes().map(|m| { + let primitives = m.primitives().filter_map(|p| { + let mode = match p.mode() { + gltf::mesh::Mode::Points => glow::POINTS, + gltf::mesh::Mode::Lines => glow::LINES, + gltf::mesh::Mode::LineLoop => glow::LINE_LOOP, + gltf::mesh::Mode::LineStrip => glow::LINE_STRIP, + gltf::mesh::Mode::Triangles => glow::TRIANGLES, + gltf::mesh::Mode::TriangleStrip => glow::TRIANGLE_STRIP, + gltf::mesh::Mode::TriangleFan => glow::TRIANGLE_FAN, + }; + unsafe { + let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); + ctx.gl.bind_vertex_array(Some(vao)); + + // in the past, I've been lazy and just uploaded whole buffers to the GPU. + // this is certainly not the right thing to do in general. + // perhaps I am misunderstanding, but it feels like GLTF makes it pretty difficult to do + // that in general, on account of things like sparse accessors. + // instead, we'll use the gltf crate's handy "reader" abstraction to iterate over all of the + // data in the buffers, assemble it ourselves, and then upload that. + let reader = p.reader(get_buffer_data); + + // on to the actual vertex data. + // this is the layout of a single vertex in the buffer we send to the GPU. + struct Vertex { + pos: glam::Vec3, + normal: glam::Vec3, + texcoord: glam::Vec2, + joints: glam::Vec4, + weights: glam::Vec4, + } + + // vertices always have positions + let mut vertices = Vec::new(); + for pos in reader.read_positions().expect("primitive has no positions") { + vertices.push(Vertex { + pos: glam::Vec3::from_array(pos), + normal: glam::Vec3::default(), + texcoord: glam::Vec2::default(), + joints: glam::Vec4::default(), + weights: glam::Vec4::default(), + }); + } + + // if we find indices, use those. otherwise generate indices + let indices: Vec = if let Some(ri) = reader.read_indices() { + ri.into_u32().collect() + } else { + vertices.iter().enumerate().map(|(i, _)| i as u32).collect() + }; + let indices_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); + let indices_buf = ctx.gl.create_buffer().expect("failed to create index buffer object"); + ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(indices_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ELEMENT_ARRAY_BUFFER, + &indices_bytes, + glow::STATIC_DRAW, + ); + + // optionally, we might have some other vertex attributes too + if let Some(iter) = reader.read_normals() { + for (i, n) in iter.enumerate() { + vertices[i].normal = glam::Vec3::from_array(n) + } + } + if let Some(iter) = reader.read_tex_coords(0) { + for (i, uv) in iter.into_f32().enumerate() { + vertices[i].texcoord = glam::Vec2::from_array(uv) + } + } + if let Some(iter) = reader.read_joints(0) { + for (i, j) in iter.into_u16().enumerate() { + vertices[i].joints = glam::Vec4::from_slice(&j.into_iter().map(|x| x as f32).collect::>()) + } + } + if let Some(iter) = reader.read_weights(0) { + for (i, w) in iter.into_f32().enumerate() { + vertices[i].weights = glam::Vec4::from_array(w) + } + } + + let vertex_size = std::mem::size_of::() as i32; + let vertices_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); + ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertices_buf)); + ctx.gl.buffer_data_u8_slice( + glow::ARRAY_BUFFER, + std::slice::from_raw_parts( + vertices.as_ptr() as _, + vertices.len() * (vertex_size as usize), + ), + glow::STATIC_DRAW, + ); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 3, glow::FLOAT, false, vertex_size, offset_of!(Vertex, pos) as _); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_NORMAL); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_NORMAL, 3, glow::FLOAT, false, vertex_size, offset_of!(Vertex, normal) as _); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, vertex_size, offset_of!(Vertex, texcoord) as _); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_JOINT); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_JOINT, 4, glow::FLOAT, false, vertex_size, offset_of!(Vertex, joints) as _); + ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_WEIGHT); + ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_WEIGHT, 4, glow::FLOAT, false, vertex_size, offset_of!(Vertex, weights) as _); + + + Some(Primitive { + mesh: mesh::Mesh { + vao, + mode, + index_count: indices.len(), + index_type: glow::UNSIGNED_INT, + index_offset: 0, + }, + material: p.material().index().unwrap(), + }) + } + }).collect(); + Object { + primitives, + } + }).collect(); + let textures: Vec = images.into_iter().map(|bi| { + unsafe { + let i = bi.image.into_rgba8(); + let tex = ctx.gl.create_texture().expect("failed to create texture"); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); + ctx.gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::RGBA as i32, + i.width() as i32, + i.height() as i32, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + Some(&i.as_bytes()), + ); + ctx.gl.generate_mipmap(glow::TEXTURE_2D); + texture::Texture { tex } + } + }).collect(); + let materials: Vec = gltf.materials().map(|m| { + let pbr = m.pbr_metallic_roughness(); + let [bcr, bcg, bcb, bca] = pbr.base_color_factor(); + let [emx, emy, emz] = m.emissive_factor(); + Material { + base_color_factor: glam::Vec4::new(bcr, bcg, bcb, bca), + base_color_texture: pbr.base_color_texture().map(|tex| tex.texture().source().index()), + metallic_factor: pbr.metallic_factor(), + roughness_factor: pbr.roughness_factor(), + metallic_roughness_texture: pbr.metallic_roughness_texture().map(|tex| tex.texture().source().index()), + normal_texture: m.normal_texture().map(|tex| tex.texture().source().index()), + occlusion_texture: m.occlusion_texture().map(|tex| tex.texture().source().index()), + emissive_factor: glam::Vec3::new(emx, emy, emz), + emissive_texture: m.emissive_texture().map(|tex| tex.texture().source().index()), + } + }).collect(); + + let skins = gltf.skins().map(|s| { + let ibm = s.reader(get_buffer_data).read_inverse_bind_matrices() + .expect("missing read inverse bind matrices") + .map(|m| glam::Mat4::from_cols_array_2d(&m)).collect(); + Skin { + inverse_bind_matrices: ibm, + joints: s.joints().map(|j| j.index()).collect(), + } + }).collect(); + + let animations = HashMap::from_iter(gltf.animations().filter_map(|a| { + let channels = a.channels().map(|c| { + let read = c.reader(get_buffer_data); + Channel { + target: c.target().node().index(), + interpolation: match c.sampler().interpolation() { + gltf::animation::Interpolation::Linear => Interpolation::Linear, + gltf::animation::Interpolation::Step => Interpolation::Step, + gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline, + }, + keyframes: read.read_inputs().expect("channel has no inputs").collect(), + values: match read.read_outputs().expect("channel has no outputs") { + gltf::animation::util::ReadOutputs::Translations(ts) => + ChannelValues::Translation(ts.map(glam::Vec3::from_array).collect()), + gltf::animation::util::ReadOutputs::Rotations(ts) => + ChannelValues::Rotation(ts.into_f32().map(glam::Quat::from_array).collect()), + gltf::animation::util::ReadOutputs::Scales(ts) => + ChannelValues::Scale(ts.map(glam::Vec3::from_array).collect()), + _ => panic!("unsupport channel outputs"), + }, + } + }).collect(); + a.name().map(|nm| (nm.to_owned(), Animation { channels })) + })); + + let nodes = gltf.nodes().map(|n| { + Node { + children: n.children().map(|c| c.index()).collect(), + object: n.mesh().map(|m| m.index()), + skin: n.skin().map(|s| s.index()), + transform: glam::Mat4::from_cols_array_2d(&n.transform().matrix()), + } + }).collect(); + + let mut nodes_by_name = HashMap::new(); + for n in gltf.nodes() { + if let Some(nm) = n.name() { + nodes_by_name.insert(nm.to_owned(), n.index()); + } + } + + let scene_nodes = gltf.default_scene().unwrap().nodes().map(|n| n.index()).collect(); + + Self { + objects, + textures, + materials, + skins, + animations, + nodes, + nodes_by_name, + scene_nodes, + } + } + + pub fn compute_joint_matrices(&self, skin: &Skin) -> Vec { + let mut q: VecDeque<(Index, glam::Mat4)> = VecDeque::new(); + q.push_back((skin.joints[0], glam::Mat4::IDENTITY)); + let mut transforms = vec![glam::Mat4::IDENTITY; self.nodes.len()]; + while let Some((ni, m)) = q.pop_front() { + let n = &self.nodes[ni]; + transforms[ni] = m.mul_mat4(&n.transform); + for ci in &n.children { + q.push_back((*ci, transforms[ni])); + } + } + let mut ret = vec![glam::Mat4::IDENTITY; skin.joints.len()]; + for (idx, ni) in skin.joints.iter().enumerate() { + ret[idx] = transforms[*ni].mul_mat4(&skin.inverse_bind_matrices[idx]); + } + ret + } + + fn render_node(&self, ctx: &context::Context, shader: &shader::Shader, n: &Node) { + if let Some(o) = n.object.and_then(|i| self.objects.get(i)) { + if let Some(s) = n.skin.and_then(|i| self.skins.get(i)) { + let jms = self.compute_joint_matrices(s); + shader.set_mat4_array(ctx, "joint_matrices[0]", &jms); + } + for p in &o.primitives { + if let Some(tex) = self.materials.get(p.material) + .and_then(|m| m.base_color_texture) + .and_then(|t| self.textures.get(t)) { + tex.bind(ctx); + } + p.mesh.render(ctx); + } + } + } + + pub fn render(&self, ctx: &context::Context, shader: &shader::Shader) { + let mut q: VecDeque = VecDeque::new(); + for sn in &self.scene_nodes { + q.push_back(*sn); + } + while let Some(ni) = q.pop_front() { + let n = &self.nodes[ni]; + self.render_node(ctx, shader, n); + for ci in &n.children { + q.push_back(*ci); + } + } + } + + pub fn reflect_animation(&mut self, nm: &str, time: f32) { + if let Some(anim) = self.animations.get(nm) { + for c in &anim.channels { + let (previ, nexti) = if let Some(nexti) = c.keyframes.iter().position(|x| *x > time) { + let previ = if nexti > 0 { nexti - 1 } else { c.keyframes.len() - 1 }; + (previ, nexti) + } else { + (c.keyframes.len() - 1, 0) + }; + match &c.values { + ChannelValues::Rotation(vs) => { + let prevt = c.keyframes[previ]; + let nextt = c.keyframes[nexti]; + let prev = vs[previ]; + let next = vs[nexti]; + let new = prev.slerp(next, (time - prevt) / (nextt - prevt)); + let (scale, _, trans) = self.nodes[c.target].transform.to_scale_rotation_translation(); + self.nodes[c.target].transform = glam::Mat4::from_scale_rotation_translation( + scale, + new, + trans, + ); + }, + _ => {}, + } + } + } + } +} diff --git a/crates/teleia/src/shader.rs b/crates/teleia/src/shader.rs new file mode 100644 index 0000000..178833a --- /dev/null +++ b/crates/teleia/src/shader.rs @@ -0,0 +1,253 @@ +use std::collections::HashMap; + +use glow::HasContext; + +use crate::{context, mesh}; + +const COMMON_VERT: &'static str = include_str!("assets/shaders/common/vert.glsl"); +const COMMON_FRAG: &'static str = include_str!("assets/shaders/common/frag.glsl"); + +#[derive(Clone)] +pub struct Shader { + pub program: glow::Program, + pub uniforms: std::rc::Rc> +} + +impl Shader { + pub fn new_helper(ctx: &context::Context, vsrc: &str, fsrc: &str) -> Result { + unsafe { + let program = ctx.gl.create_program()?; + + let vert = ctx.gl.create_shader(glow::VERTEX_SHADER)?; + ctx.gl.shader_source(vert, &vsrc); + ctx.gl.compile_shader(vert); + if !ctx.gl.get_shader_compile_status(vert) { + return Err(format!( + "failed to compile vertex shader:\n{}", + ctx.gl.get_shader_info_log(vert) + )); + } + ctx.gl.attach_shader(program, vert); + + let frag = ctx.gl.create_shader(glow::FRAGMENT_SHADER)?; + ctx.gl.shader_source(frag, &fsrc); + ctx.gl.compile_shader(frag); + if !ctx.gl.get_shader_compile_status(frag) { + return Err(format!( + "failed to compile fragment shader:\n{}", + ctx.gl.get_shader_info_log(frag) + )); + } + ctx.gl.attach_shader(program, frag); + + ctx.gl.bind_attrib_location(program, mesh::ATTRIB_VERTEX, "vertex"); + ctx.gl.bind_attrib_location(program, mesh::ATTRIB_NORMAL, "normal"); + ctx.gl.bind_attrib_location(program, mesh::ATTRIB_TEXCOORD, "texcoord"); + ctx.gl.bind_attrib_location(program, mesh::ATTRIB_JOINT, "joint"); + ctx.gl.bind_attrib_location(program, mesh::ATTRIB_WEIGHT, "weight"); + ctx.gl.bind_attrib_location(program, mesh::ATTRIB_COLOR, "color"); + + ctx.gl.link_program(program); + if !ctx.gl.get_program_link_status(program) { + return Err(format!( + "failed to link shader program:\n{}", + ctx.gl.get_program_info_log(program), + )); + } + + ctx.gl.detach_shader(program, vert); + ctx.gl.delete_shader(vert); + ctx.gl.detach_shader(program, frag); + ctx.gl.delete_shader(frag); + + ctx.gl.use_program(Some(program)); + + let mut uniforms = HashMap::new(); + for index in 0..ctx.gl.get_active_uniforms(program) { + if let Some(active) = ctx.gl.get_active_uniform(program, index) { + if let Some(loc) = ctx.gl.get_uniform_location(program, &active.name) { + uniforms.insert(active.name, loc); + } else { + log::warn!("failed to get location for uniform: {}", active.name); + } + } else { + log::warn!("failed to get active uniform for index: {}", index); + } + } + + Ok(Self { + program, + uniforms: std::rc::Rc::new(uniforms), + }) + } + } + + pub fn new_nolib(ctx: &context::Context, vsrc: &str, fsrc: &str) -> Self { + Self::new_helper(ctx, vsrc, fsrc).expect("failed to load shader") + } + + pub fn new(ctx: &context::Context, vsrcstr: &str, fsrcstr: &str) -> Self { + let vsrc = format!("{}\n{}\n", COMMON_VERT, vsrcstr); + let fsrc = format!("{}\n{}\n", COMMON_FRAG, fsrcstr); + Self::new_nolib(ctx, &vsrc, &fsrc) + } + + pub fn delete(&mut self, ctx: &context::Context) { + unsafe { ctx.gl.delete_program(self.program); } + } + + pub fn replace(&mut self, ctx: &context::Context, vsrc: &str, fsrc: &str) -> Result<(), String> { + self.delete(ctx); + *self = Self::new_helper(ctx, vsrc, fsrc)?; + Ok(()) + } + + pub fn set_i32(&self, ctx: &context::Context, name: &str, val: i32) { + if let Some(loc) = self.uniforms.get(name) { + unsafe { ctx.gl.uniform_1_i32(Some(loc), val) } + } + } + + pub fn set_i32_array(&self, ctx: &context::Context, name: &str, val: &[i32]) { + if let Some(loc) = self.uniforms.get(name) { + unsafe { + ctx.gl.uniform_1_i32_slice(Some(loc), val) + } + } + } + + pub fn set_f32(&self, ctx: &context::Context, name: &str, val: f32) { + if let Some(loc) = self.uniforms.get(name) { + unsafe { ctx.gl.uniform_1_f32(Some(loc), val) } + } + } + + pub fn set_vec2(&self, ctx: &context::Context, name: &str, val: &glam::Vec2) { + if let Some(loc) = self.uniforms.get(name) { + unsafe { + ctx.gl.uniform_2_f32( + Some(loc), + val.x, + val.y, + ); + } + } + } + + pub fn set_vec2_array(&self, ctx: &context::Context, name: &str, val: &[glam::Vec2]) { + if let Some(loc) = self.uniforms.get(name) { + let vs: Vec = val.iter().flat_map(|v| [v.x, v.y]).collect(); + unsafe { + ctx.gl.uniform_2_f32_slice( + Some(loc), + &vs, + ); + } + } + } + + pub fn set_vec3(&self, ctx: &context::Context, name: &str, val: &glam::Vec3) { + if let Some(loc) = self.uniforms.get(name) { + unsafe { + ctx.gl.uniform_3_f32( + Some(loc), + val.x, + val.y, + val.z, + ); + } + } + } + + pub fn set_vec3_array(&self, ctx: &context::Context, name: &str, val: &[glam::Vec3]) { + if let Some(loc) = self.uniforms.get(name) { + let vs: Vec = val.iter().flat_map(|v| [v.x, v.y, v.z]).collect(); + unsafe { + ctx.gl.uniform_3_f32_slice( + Some(loc), + &vs, + ); + } + } + } + + pub fn set_vec4(&self, ctx: &context::Context, name: &str, val: &glam::Vec4) { + if let Some(loc) = self.uniforms.get(name) { + unsafe { + ctx.gl.uniform_4_f32( + Some(loc), + val.x, + val.y, + val.z, + val.w, + ); + } + } + } + + pub fn set_mat4(&self, ctx: &context::Context, name: &str, val: &glam::Mat4) { + if let Some(loc) = self.uniforms.get(name) { + unsafe { + ctx.gl.uniform_matrix_4_f32_slice( + Some(loc), + false, + &val.to_cols_array(), + ); + } + } + } + + pub fn set_mat4_array(&self, ctx: &context::Context, name: &str, val: &[glam::Mat4]) { + if let Some(loc) = self.uniforms.get(name) { + let vs: Vec = val.iter().flat_map(|m| m.to_cols_array()).collect(); + unsafe { + ctx.gl.uniform_matrix_4_f32_slice( + Some(loc), + false, + &vs, + ); + } + } + } + + pub fn set_position_3d(&self, ctx: &context::Context, position: &glam::Mat4) { + self.set_mat4(&ctx, "position", &position); + self.set_mat4(&ctx, "normal_matrix", &position.inverse().transpose()); + } + + pub fn set_position_2d_helper(&self, ctx: &context::Context, pos: &glam::Vec2, dims: &glam::Vec2, rot: &glam::Quat) { + let halfwidth = dims.x / 2.0; + let halfheight = dims.y / 2.0; + self.set_mat4( + &ctx, "position", + &glam::Mat4::from_scale_rotation_translation( + glam::Vec3::new(halfwidth, halfheight, 1.0), + rot.clone(), + glam::Vec3::new( + -ctx.render_width / 2.0 + pos.x + halfwidth, + ctx.render_height / 2.0 - pos.y - halfheight, + 0.0, + ), + ) + ); + } + + pub fn set_position_2d(&self, ctx: &context::Context, pos: &glam::Vec2, dims: &glam::Vec2) { + self.set_position_2d_helper(ctx, pos, dims, &glam::Quat::IDENTITY) + } + + pub fn set_texture_offset(&self, ctx: &context::Context, inc: i32, x: i32, y: i32) { + let count = inc as f32; + let ratio = 1.0 / count; + self.set_vec3( + ctx, "texture_offset", + &glam::Vec3::new((x % inc) as f32 * ratio, (y % inc) as f32 * ratio, count) + ); + } + + pub fn bind(&self, ctx: &context::Context) { + unsafe { + ctx.gl.use_program(Some(self.program)); + } + } +} diff --git a/crates/teleia/src/shadow.rs b/crates/teleia/src/shadow.rs new file mode 100644 index 0000000..f827967 --- /dev/null +++ b/crates/teleia/src/shadow.rs @@ -0,0 +1,127 @@ +use glow::HasContext; + +use crate::context; + +pub struct ShadowBuffer { + pub fbo: glow::Framebuffer, + pub depth_tex: glow::Texture, + pub width: i32, + pub height: i32, +} + +impl ShadowBuffer { + pub fn new(ctx: &context::Context, w: i32, h: i32) -> Self { + unsafe { + // generate and bind FBO + let fbo = ctx.gl.create_framebuffer().expect("failed to create framebuffer"); + ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); + + // generate and attach depth buffer + let depth_tex = ctx.gl.create_texture().expect("failed to create texture"); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(depth_tex)); + ctx.gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::DEPTH_COMPONENT as i32, + w, + h, + 0, + glow::DEPTH_COMPONENT, + glow::FLOAT, + None, + ); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_BORDER as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_BORDER as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_COMPARE_MODE, glow::COMPARE_REF_TO_TEXTURE as i32); + ctx.gl.tex_parameter_f32_slice(glow::TEXTURE_2D, glow::TEXTURE_BORDER_COLOR, &[1.0, 1.0, 1.0, 1.0]); + ctx.gl.framebuffer_texture_2d(glow::FRAMEBUFFER, glow::DEPTH_ATTACHMENT, glow::TEXTURE_2D, Some(depth_tex), 0); + ctx.gl.draw_buffer(glow::NONE); + ctx.gl.read_buffer(glow::NONE); + + let status = ctx.gl.check_framebuffer_status(glow::FRAMEBUFFER); + if status != glow::FRAMEBUFFER_COMPLETE { + panic!("error initializing framebuffer: {}", status); + } + Self { + fbo, + depth_tex, + width: w, + height: h, + } + } + } + + pub fn bind(&self, ctx: &context::Context) { + unsafe { + ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); + ctx.gl.viewport(0, 0, self.width as _, self.height as _); + ctx.gl.clear(glow::DEPTH_BUFFER_BIT); + + } + } +} + +pub struct ShadowBuffer3D { + pub fbo: glow::Framebuffer, + pub depth_cubemap: glow::Texture, + pub width: i32, + pub height: i32, +} + +impl ShadowBuffer3D { + pub fn new(ctx: &context::Context, w: i32, h: i32) -> Self { + unsafe { + // generate and bind FBO + let fbo = ctx.gl.create_framebuffer().expect("failed to create framebuffer"); + ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); + + // generate and attach depth buffer + let depth_cubemap = ctx.gl.create_texture().expect("failed to create texture"); + ctx.gl.bind_texture(glow::TEXTURE_CUBE_MAP, Some(depth_cubemap)); + for i in 0..6 { + ctx.gl.tex_image_2d( + glow::TEXTURE_CUBE_MAP_POSITIVE_X + i, + 0, + glow::DEPTH_COMPONENT as i32, + w, + h, + 0, + glow::DEPTH_COMPONENT, + glow::FLOAT, + None, + ); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_R, glow::CLAMP_TO_EDGE as i32); + } + + ctx.gl.framebuffer_texture(glow::FRAMEBUFFER, glow::DEPTH_ATTACHMENT, Some(depth_cubemap), 0); + ctx.gl.draw_buffer(glow::NONE); + ctx.gl.read_buffer(glow::NONE); + + let status = ctx.gl.check_framebuffer_status(glow::FRAMEBUFFER); + if status != glow::FRAMEBUFFER_COMPLETE { + panic!("error initializing framebuffer: {}", status); + } + Self { + fbo, + depth_cubemap, + width: w, + height: h, + } + } + } + + pub fn bind(&self, ctx: &context::Context) { + unsafe { + ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); + ctx.gl.viewport(0, 0, self.width as _, self.height as _); + ctx.gl.clear(glow::DEPTH_BUFFER_BIT); + + } + } +} diff --git a/crates/teleia/src/state.rs b/crates/teleia/src/state.rs new file mode 100644 index 0000000..78aac91 --- /dev/null +++ b/crates/teleia/src/state.rs @@ -0,0 +1,520 @@ +#![allow(dead_code, unused_variables)] +use std::{collections::HashMap, fmt::Display}; +use bimap::BiHashMap; +use enum_map::{enum_map, Enum, EnumMap}; +use serde::{Serialize, Deserialize}; + +use crate::{audio, context, framebuffer, shader, utils}; + +const DELTA_TIME: f64 = 1.0 / 60.0; + +pub struct WinitWaker {} +impl WinitWaker { + fn new() -> Self { Self {} } +} +impl std::task::Wake for WinitWaker { + fn wake(self: std::sync::Arc) {} +} + +pub struct Response { + pub url: String, + pub status: reqwest::StatusCode, + pub body: bytes::Bytes, +} + +pub trait Game { + fn initialize(&self, ctx: &context::Context, st: &State) -> utils::Erm<()> { Ok(()) } + fn finalize(&self, ctx: &context::Context, st: &State) -> utils::Erm<()> { Ok(()) } + fn initialize_audio(&self, ctx: &context::Context, st: &State, actx: &audio::Context) -> + HashMap + { + HashMap::new() + } + fn finish_title(&mut self, st: &mut State) {} + fn mouse_move(&mut self, ctx: &context::Context, st: &mut State, x: i32, y: i32) {} + fn mouse_press(&mut self, ctx: &context::Context, st: &mut State) {} + fn request_return(&mut self, ctx: &context::Context, st: &mut State, res: Response) {} + fn update(&mut self, ctx: &context::Context, st: &mut State) -> utils::Erm<()> { Ok(()) } + fn render(&mut self, ctx: &context::Context, st: &mut State) -> utils::Erm<()> { Ok(()) } +} + +#[derive(Debug, Enum, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Key { + Up, Down, Left, Right, + A, B, L, R, + Start, Select, +} +pub const KEYS: [Key; 10] = [ + Key::Up, Key::Down, Key::Left, Key::Right, + Key::A, Key::B, Key::L, Key::R, + Key::Start, Key::Select, +]; +pub struct Keys { + pub pressed: EnumMap, + pub new: EnumMap, +} +impl Keys { + pub fn new() -> Self { + Self { + pressed: enum_map! { + Key::Up => false, Key::Down => false, Key::Left => false, Key::Right => false, + Key::A => false, Key::B => false, Key::L => false, Key::R => false, + Key::Start => false, Key::Select => false, + }, + new: enum_map! { + Key::Up => false, Key::Down => false, Key::Left => false, Key::Right => false, + Key::A => false, Key::B => false, Key::L => false, Key::R => false, + Key::Start => false, Key::Select => false, + }, + } + } + pub fn up(&self) -> bool { self.pressed[Key::Up] } + pub fn down(&self) -> bool { self.pressed[Key::Down] } + pub fn left(&self) -> bool { self.pressed[Key::Left] } + pub fn right(&self) -> bool { self.pressed[Key::Right] } + pub fn a(&self) -> bool { self.pressed[Key::A] } + pub fn b(&self) -> bool { self.pressed[Key::B] } + pub fn l(&self) -> bool { self.pressed[Key::L] } + pub fn r(&self) -> bool { self.pressed[Key::R] } + pub fn start(&self) -> bool { self.pressed[Key::Start] } + pub fn select(&self) -> bool { self.pressed[Key::Select] } + pub fn new_up(&mut self) -> bool { let ret = self.new[Key::Up]; self.new[Key::Up] = false; ret } + pub fn new_down(&mut self) -> bool { let ret = self.new[Key::Down]; self.new[Key::Down] = false; ret } + pub fn new_left(&mut self) -> bool { let ret = self.new[Key::Left]; self.new[Key::Left] = false; ret } + pub fn new_right(&mut self) -> bool { let ret = self.new[Key::Right]; self.new[Key::Right] = false; ret } + pub fn new_a(&mut self) -> bool { let ret = self.new[Key::A]; self.new[Key::A] = false; ret } + pub fn new_b(&mut self) -> bool { let ret = self.new[Key::B]; self.new[Key::B] = false; ret } + pub fn new_l(&mut self) -> bool { let ret = self.new[Key::L]; self.new[Key::L] = false; ret } + pub fn new_r(&mut self) -> bool { let ret = self.new[Key::R]; self.new[Key::R] = false; ret } + pub fn new_start(&mut self) -> bool { let ret = self.new[Key::Start]; self.new[Key::Start] = false; ret } + pub fn new_select(&mut self) -> bool { let ret = self.new[Key::Select]; self.new[Key::Select] = false; ret } +} + +pub struct PointLight { + pub pos: glam::Vec3, + pub color: glam::Vec3, + pub attenuation: glam::Vec2, +} + +type Timestamp = f64; + +#[cfg(target_arch = "wasm32")] +pub type Keycode = winit::keyboard::KeyCode; + +#[cfg(not(target_arch = "wasm32"))] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Keycode { + pub kc: glfw::Key +} +#[cfg(not(target_arch = "wasm32"))] +impl Keycode { + pub fn new(kc: glfw::Key) -> Self { Self { kc } } +} +#[cfg(not(target_arch = "wasm32"))] +impl Display for Keycode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.kc) + } +} +#[cfg(not(target_arch = "wasm32"))] +impl Serialize for Keycode { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + (self.kc as i32).serialize(serializer) + } +} +#[cfg(not(target_arch = "wasm32"))] +impl<'de> Deserialize<'de> for Keycode { + fn deserialize(deserializer: D) -> Result + where D: serde::Deserializer<'de> { + i32::deserialize(deserializer) + .map(|x| unsafe { + std::mem::transmute(x) + }) + } +} + +pub struct State { + pub acc: f64, + pub last: Timestamp, + pub tick: u64, + + pub rebinding: Option, + pub keybindings: BiHashMap, + pub keys: Keys, + + pub screen: framebuffer::Framebuffer, + pub render_framebuffer: framebuffer::Framebuffer, + pub shader_upscale: shader::Shader, + pub audio: Option, + + pub projection: glam::Mat4, + pub camera: (glam::Vec3, glam::Vec3, glam::Vec3), + pub lighting: (glam::Vec3, glam::Vec3, glam::Vec3), + pub point_lights: Vec, + + pub waker_ctx: std::task::Context<'static>, + pub http_client: reqwest::Client, + pub request: Option>>>>, + + pub log: Vec<(u64, String)>, +} + +#[cfg(target_arch = "wasm32")] +pub fn now(ctx: &context::Context) -> Timestamp { + ctx.performance.now() / 1000.0 +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn now(ctx: &context::Context) -> Timestamp { + let elapsed = ctx.start_instant.elapsed(); + let ms = elapsed.as_millis(); + (ms as f64) / 1000.0 +} + +#[cfg(target_arch = "wasm32")] +pub fn default_keybindings() -> BiHashMap { + BiHashMap::from_iter(vec![ + (winit::keyboard::KeyCode::KeyW, Key::Up), + (winit::keyboard::KeyCode::KeyS, Key::Down), + (winit::keyboard::KeyCode::KeyA, Key::Left), + (winit::keyboard::KeyCode::KeyD, Key::Right), + (winit::keyboard::KeyCode::Digit1, Key::A), + (winit::keyboard::KeyCode::Digit2, Key::B), + (winit::keyboard::KeyCode::KeyQ, Key::L), + (winit::keyboard::KeyCode::KeyE, Key::R), + (winit::keyboard::KeyCode::Tab, Key::Start), + (winit::keyboard::KeyCode::Space, Key::Select), + ]) +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn default_keybindings() -> BiHashMap { + BiHashMap::from_iter(vec![ + (Keycode::new(glfw::Key::W), Key::Up), + (Keycode::new(glfw::Key::S), Key::Down), + (Keycode::new(glfw::Key::A), Key::Left), + (Keycode::new(glfw::Key::D), Key::Right), + (Keycode::new(glfw::Key::Num1), Key::A), + (Keycode::new(glfw::Key::Num2), Key::B), + (Keycode::new(glfw::Key::Q), Key::L), + (Keycode::new(glfw::Key::E), Key::R), + (Keycode::new(glfw::Key::Tab), Key::Start), + (Keycode::new(glfw::Key::Space), Key::Select), + ]) +} + +impl State { + pub fn new(ctx: &context::Context) -> Self { + let screen = framebuffer::Framebuffer::screen(ctx); + let render_framebuffer = framebuffer::Framebuffer::new( + ctx, + &glam::Vec2::new(ctx.render_width, ctx.render_height), + &glam::Vec2::new(0.0, 0.0), + ); + let shader_upscale = shader::Shader::new_nolib( + ctx, + include_str!("assets/shaders/scale/vert.glsl"), + include_str!("assets/shaders/scale/frag.glsl"), + ); + + let waker = std::sync::Arc::new(WinitWaker::new()); + let cwaker = Box::leak(Box::new(waker.into())); + let waker_ctx = std::task::Context::from_waker(cwaker); + + let acc = 0.0; + let last = now(ctx); + + Self { + acc, + last, + // we initialize the tick to 1000, which allows us to use "0" as the default time for + // various animation starts on entities without having them all play at game start + tick: 1000, + + rebinding: None, + keybindings: default_keybindings(), + keys: Keys::new(), + + screen, + render_framebuffer, + shader_upscale, + audio: None, + + projection: glam::Mat4::perspective_lh( + std::f32::consts::PI / 4.0, + ctx.render_width / ctx.render_height, + // 0.1, + 0.5, + 50.0, + ), + camera: (glam::Vec3::new(0.0, 0.0, 0.0), glam::Vec3::new(0.0, 0.0, 1.0), glam::Vec3::new(0.0, 1.0, 0.0)), + lighting: ( + glam::Vec3::new(1.0, 1.0, 1.0), + glam::Vec3::new(1.0, 1.0, 1.0), + glam::Vec3::new(1.0, -1.0, 1.0), + ), + point_lights: Vec::new(), + + waker_ctx, + http_client: reqwest::Client::new(), + request: None, + + log: Vec::new(), + } + } + + pub fn write_log(&mut self, e: &str) { + log::info!("log: {}", e.to_owned()); + self.log.push((self.tick, e.to_owned())); + } + + pub fn handle_resize(&mut self, ctx: &context::Context) { + self.screen = framebuffer::Framebuffer::screen(ctx); + } + + pub fn move_camera( + &mut self, + _ctx: &context::Context, + pos: &glam::Vec3, + dir: &glam::Vec3, + up: &glam::Vec3, + ) { + self.camera = (pos.clone(), dir.clone(), up.clone()); + } + + pub fn set_lighting( + &mut self, + _ctx: &context::Context, + ambient: &glam::Vec3, + color: &glam::Vec3, + dir: &glam::Vec3, + ) { + self.lighting = (ambient.clone(), color.clone(), dir.clone()); + } + + pub fn add_point_light( + &mut self, + _ctx: &context::Context, + pos: &glam::Vec3, + color: &glam::Vec3, + attenuation: &glam::Vec2, + ) { + self.point_lights.push( + PointLight { + pos: pos.clone(), + color: color.clone(), + attenuation: attenuation.clone(), + }, + ); + } + + pub fn clear_point_lights(&mut self, _ctx: &context::Context) { + self.point_lights.clear(); + } + + pub fn view(&self) -> glam::Mat4 { + glam::Mat4::look_to_lh( + self.camera.0, + self.camera.1, + self.camera.2, + ) + } + + pub fn bind_3d_helper(&mut self, ctx: &context::Context, shader: &shader::Shader, plc: usize) { + shader.bind(ctx); + shader.set_mat4(ctx, "projection", &self.projection); + shader.set_mat4(ctx, "view", &self.view()); + shader.set_vec3( + ctx, "light_ambient_color", + &self.lighting.0, + ); + shader.set_vec3( + ctx, "light_dir_color", + &self.lighting.1, + ); + shader.set_vec3( + ctx, "light_dir", + &self.lighting.2.normalize(), + ); + shader.set_i32( + ctx, &format!("light_count"), + plc as _, + ); + } + + pub fn bind_3d_no_point_lights(&mut self, ctx: &context::Context, shader: &shader::Shader) { + self.bind_3d_helper(ctx, shader, 0); + } + + pub fn bind_3d(&mut self, ctx: &context::Context, shader: &shader::Shader) { + let plc = self.point_lights.len().min(5); + self.bind_3d_helper(ctx, shader, plc); + if plc > 0 { + let lpos: Vec<_> = self.point_lights.iter().take(plc).map(|l| l.pos).collect(); + shader.set_vec3_array( + ctx, &format!("light_pos[0]"), + &lpos, + ); + let lcolor: Vec<_> = self.point_lights.iter().take(plc).map(|l| l.color).collect(); + shader.set_vec3_array( + ctx, &format!("light_color[0]"), + &lcolor, + ); + let lattenuation: Vec<_> = self.point_lights.iter().take(plc).map(|l| l.attenuation).collect(); + shader.set_vec2_array( + ctx, &format!("light_attenuation[0]"), + &lattenuation, + ); + } + } + + pub fn bind_2d(&mut self, ctx: &context::Context, shader: &shader::Shader) { + shader.bind(ctx); + shader.set_mat4(&ctx, "projection", &glam::Mat4::IDENTITY); + shader.set_mat4( + ctx, "view", + &glam::Mat4::from_scale( + glam::Vec3::new( + 2.0 / ctx.render_width, + 2.0 / ctx.render_height, + 1.0, + ), + ), + ); + } + + pub fn mouse_moved( + &mut self, + ctx: &context::Context, + x: f32, y: f32, + game: &mut G + ) where G: Game + { + let rx = ((x - self.screen.offsets.x) * ctx.render_width / self.screen.dims.x) as i32; + let ry = ((y - self.screen.offsets.y) * ctx.render_height / self.screen.dims.y) as i32; + if !(rx < 0 || rx >= ctx.render_width as i32 || ry < 0 || ry >= ctx.render_height as i32) { + game.mouse_move(ctx, self, rx, ry); + } + } + + pub fn mouse_pressed( + &mut self, + ctx: &context::Context, + game: &mut G + ) where G: Game { + log::info!("click"); + if self.audio.is_none() { + self.audio = Some(audio::Assets::new(|actx| { + game.initialize_audio(ctx, &self, actx) + })); + game.finish_title(self); + } + game.mouse_press(ctx, self); + } + + pub fn mouse_released( + &mut self, + _ctx: &context::Context, + ) { + } + + pub fn key_pressed( + &mut self, + _ctx: &context::Context, + key: Keycode, + ) { + #[cfg(target_arch = "wasm32")] + let rebind = key == winit::keyboard::KeyCode::F12; + #[cfg(not(target_arch = "wasm32"))] + let rebind = key.kc == glfw::Key::F12; + if rebind { + self.keybindings = default_keybindings(); + self.rebinding = None; + self.write_log("Reset keybindings!"); + } else if let Some(k) = self.rebinding { + self.keybindings.insert(key, k); + self.rebinding = None; + } else if let Some(k) = self.keybindings.get_by_left(&key) { + self.keys.pressed[*k] = true; + self.keys.new[*k] = true; + } + } + + pub fn key_released( + &mut self, + _ctx: &context::Context, + key: Keycode, + ) { + if let Some(k) = self.keybindings.get_by_left(&key) { + self.keys.pressed[*k] = false; + } + } + + /// Return the first keybinding for the given virtual key + pub fn keybinding_for(&self, k: &Key) -> Option { + if let Some(kc) = self.keybindings.get_by_right(k) { + Some(format!("{}", kc)) + } else { + None + } + } + + pub fn rebind_key(&mut self, k: &Key) { + self.rebinding = Some(*k); + } + + pub fn request(&mut self, f: F) + where F: Fn(&reqwest::Client) -> reqwest::RequestBuilder + { + let builder = f(&self.http_client); + let fut = async { + let resp = builder.send().await?; + let url = resp.url().clone().to_string(); + let status = resp.status().clone(); + let body = resp.bytes().await?; + reqwest::Result::Ok(Response { + url, + status, + body, + }) + }; + self.request = Some(Box::pin(fut)); + } + + pub fn requesting(&self) -> bool { self.request.is_some() } + + pub fn request_returned(&mut self, ctx: &context::Context, game: &mut G, res: Response) + where G: Game + { + game.request_return(ctx, self, res); + } + + pub fn run_update(&mut self, ctx: &context::Context, game: &mut G) -> utils::Erm<()> where G: Game { + let now = now(ctx); + let diff = now - self.last; + + // update, if enough time has accumulated since last update + if diff >= DELTA_TIME { + self.last = now; + self.tick += 1; + game.update(ctx, self)?; + self.acc = 0.0; + } + Ok(()) + } + + pub fn run_render(&mut self, ctx: &context::Context, game: &mut G) -> utils::Erm<()> where G: Game { + self.render_framebuffer.bind(&ctx); + + game.render(ctx, self)?; + + self.screen.bind(&ctx); + ctx.clear_color(glam::Vec4::new(0.0, 0.0, 0.0, 0.0)); + ctx.clear(); + self.shader_upscale.bind(&ctx); + self.render_framebuffer.bind_texture(&ctx); + ctx.render_no_geometry(); + Ok(()) + } +} diff --git a/crates/teleia/src/texture.rs b/crates/teleia/src/texture.rs new file mode 100644 index 0000000..68272cb --- /dev/null +++ b/crates/teleia/src/texture.rs @@ -0,0 +1,83 @@ +use glow::HasContext; +use image::EncodableLayout; + +use crate::context; + +pub struct Texture { + pub tex: glow::Texture, +} + +impl Texture { + pub fn new_empty(ctx: &context::Context) -> Self { + unsafe { + let tex = ctx.gl.create_texture().expect("failed to create texture"); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); + Self { + tex, + } + } + } + + pub fn new(ctx: &context::Context, bytes: &[u8]) -> Self { + let rgba = image::io::Reader::new(std::io::Cursor::new(bytes)) + .with_guessed_format() + .expect("failed to guess image format") + .decode() + .expect("failed to decode image") + .into_rgba8(); + let pixels = rgba.as_bytes(); + unsafe { + let tex = ctx.gl.create_texture().expect("failed to create texture"); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); + ctx.gl.tex_image_2d( + glow::TEXTURE_2D, + 0, + glow::RGBA as i32, + rgba.width() as i32, + rgba.height() as i32, + 0, + glow::RGBA, + glow::UNSIGNED_BYTE, + Some(pixels), + ); + ctx.gl.generate_mipmap(glow::TEXTURE_2D); + + Self { + tex, + } + } + } + + pub fn set_anisotropic_filtering(&self, ctx: &context::Context) { + unsafe { + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::REPEAT as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR_MIPMAP_LINEAR as i32); + ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32); + ctx.gl.tex_parameter_f32(glow::TEXTURE_2D, glow::TEXTURE_MAX_ANISOTROPY_EXT, 4.0); + } + } + + pub fn bind(&self, ctx: &context::Context) { + unsafe { + ctx.gl.active_texture(glow::TEXTURE0); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); + } + } + + pub fn bind_index(&self, ctx: &context::Context, idx: u32) { + unsafe { + ctx.gl.active_texture(glow::TEXTURE0 + idx); + ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); + } + } +} diff --git a/crates/teleia/src/ui.rs b/crates/teleia/src/ui.rs new file mode 100644 index 0000000..09afdf9 --- /dev/null +++ b/crates/teleia/src/ui.rs @@ -0,0 +1,153 @@ +use crate::utils; + +fn compute_reverse(frames: u64, tick: u64, start: u64) -> u64 { + let leftover = frames - (tick - start) + .clamp(0, frames); + tick - leftover +} + +pub enum ModeToggle { + Inactive { start: u64 }, + Active { start: u64 }, +} + +pub struct Mode { + frames: u64, + toggle: ModeToggle, + locked: bool, +} + +impl Mode { + pub fn new(frames: u64) -> Self { + Self { + frames, + toggle: ModeToggle::Inactive { start: 0 }, + locked: false, + } + } + + /// Is the current state active? + pub fn is_active(&self) -> bool { + match self.toggle { + ModeToggle::Inactive {..} => false, + ModeToggle::Active {..} => true, + } + } + + pub fn is_locked(&self) -> bool { + self.locked + } + + /// Has the current transition finished? + pub fn is_ready(&self, tick: u64) -> bool { + let started = match self.toggle { + ModeToggle::Inactive { start } => start, + ModeToggle::Active { start } => start, + }; + tick - started > self.frames + } + + pub fn progress(&self, tick: u64) -> f32 { + match self.toggle { + ModeToggle::Inactive { start } => { + 1.0 - (((tick - start) as f32) / self.frames as f32) + .clamp(0.0, 1.0) + }, + ModeToggle::Active { start } => { + (((tick - start) as f32) / self.frames as f32) + .clamp(0.0, 1.0) + } + } + } + + pub fn reset(&mut self) { + self.locked = false; + self.toggle = ModeToggle::Inactive { start: 0 }; + } + + pub fn reverse(&mut self, tick: u64) -> bool { + if !self.locked { + self.locked = true; + match self.toggle { + ModeToggle::Inactive { start } => { + self.toggle = ModeToggle::Active { + start: compute_reverse(self.frames, tick, start) + }; + }, + ModeToggle::Active { start } => { + self.toggle = ModeToggle::Inactive { + start: compute_reverse(self.frames, tick, start) + }; + }, + } + true + } else { false } + } + + pub fn lock(&mut self) { + self.locked = true; + } + + pub fn unlock(&mut self) { + self.locked = false; + } +} + +pub struct Cursor { + pub index: i32, + pub prev_index: i32, + pub change_started: u64, + pub bound: i32, + pub frames: u64, + pub locked: bool, +} + +impl Cursor { + pub fn new(bound: i32, frames: u64) -> Self { + Self { + index: 0, + prev_index: 0, + change_started: 0, + bound, + frames, + locked: false, + } + } + + pub fn animation_index(&self, tick: u64) -> f32 { + let progress = ((tick - self.change_started) as f32) + / (self.frames as f32 / 2.0); + utils::lerp( + self.prev_index as f32, + self.index as f32, + progress + ) + } + + pub fn is_ready(&self, tick: u64) -> bool { + tick - self.change_started > self.frames + } + + pub fn set(&mut self, val: i32, tick: u64) -> bool { + if self.is_ready(tick) || !self.locked { + self.change_started = tick; + self.prev_index = self.index; + self.index = val; + self.index %= self.bound; + self.locked = true; + true + } else { false } + } + + pub fn increment(&mut self, tick: u64) -> bool { + self.set(self.index + 1, tick) + } + + pub fn decrement(&mut self, tick: u64) -> bool { + self.set(self.index + self.bound - 1, tick) + } + + pub fn unlock(&mut self) { + self.locked = false; + } +} diff --git a/crates/teleia/src/utils.rs b/crates/teleia/src/utils.rs new file mode 100644 index 0000000..05e9251 --- /dev/null +++ b/crates/teleia/src/utils.rs @@ -0,0 +1,131 @@ +use std::f32::consts::PI; + +use serde::{Serialize, Deserialize}; +use strum::EnumIter; + +pub type Erm = color_eyre::Result; + +pub fn erm(e: E) -> Erm where E: std::error::Error + std::marker::Send + std::marker::Sync + 'static { + Err(e.into()) +} + +pub struct ErrorHandler; +impl color_eyre::eyre::EyreHandler for ErrorHandler { + fn debug( + &self, + error: &(dyn std::error::Error + 'static), + f: &mut core::fmt::Formatter<'_>, + ) -> core::fmt::Result { + if f.alternate() { + return core::fmt::Debug::fmt(error, f); + } + let mut first = true; + if let Some(s) = error.source() { + let errors: Vec<_> = std::iter::successors(Some(s), |e| (*e).source()).collect(); + for err in errors.iter().rev() { + writeln!(f)?; write!(f, "{}{}", if first {""} else {" - "}, err)?; + first = false; + } + } + writeln!(f)?; write!(f, "{}{}", if first {""} else {" - "}, error)?; + Ok(()) + } +} + +pub fn install_error_handler() { + let (panic_hook, _) = color_eyre::config::HookBuilder::default().into_hooks(); + panic_hook.install(); + color_eyre::eyre::set_hook(Box::new(move |_| Box::new(ErrorHandler))).expect("failed to install error handler"); +} + +#[derive(Clone, Copy, Debug, EnumIter, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum Cardinal { + North, + South, + West, + East, +} + +impl Cardinal { + pub fn to_string(&self) -> &'static str { + match self { + Self::North => "north", + Self::South => "south", + Self::West => "west", + Self::East => "east", + } + } + + pub fn turn_cw(&self) -> Self { + match self { + Self::North => Self::East, + Self::South => Self::West, + Self::West => Self::North, + Self::East => Self::South, + } + } + + pub fn turn_ccw(&self) -> Self { + match self { + Self::North => Self::West, + Self::South => Self::East, + Self::West => Self::South, + Self::East => Self::North, + } + } + + pub fn dir(&self) -> glam::Vec3 { + match self { + Self::North => glam::Vec3::new(0.0, 1.0, 0.0), + Self::South => glam::Vec3::new(0.0, -1.0, 0.0), + Self::West => glam::Vec3::new(-1.0, 0.0, 0.0), + Self::East => glam::Vec3::new(1.0, 0.0, 0.0), + } + } + + pub fn offsets(&self) -> (i32, i32) { + match self { + Self::North => (0, 1), + Self::South => (0, -1), + Self::West => (-1, 0), + Self::East => (1, 0), + } + } + + pub fn angle(&self) -> f32 { + match self { + Self::North => 0.0, + Self::South => PI, + Self::West => PI / 2.0, + Self::East => 3.0 * PI / 2.0, + } + } + + pub fn turn_by(&self, o: &Self) -> Self { + match o { + Self::North => self.clone(), + Self::South => self.turn_cw().turn_cw(), + Self::West => self.turn_cw(), + Self::East => self.turn_ccw(), + } + } + + pub fn angle_between(&self, o: &Self) -> f32 { + if o == &self.turn_cw() { -PI / 2.0 } + else if o == &self.turn_ccw() { PI / 2.0 } + else if o == &self.turn_cw().turn_cw() { PI } + else { 0.0 } + } +} + +pub fn lerp(a: f32, b: f32, t: f32) -> f32 { + a + t.clamp(0.0, 1.0) * (b - a) +} + +pub fn dir_lerp(a: &glam::Vec3, b: glam::Vec3, t: f32) -> glam::Vec3 { + let dirrotaxis = a.cross(b).normalize(); + let dirrotangle = a.angle_between(b); + let dirrotfull = glam::Quat::from_axis_angle(dirrotaxis, dirrotangle); + let dirrot = glam::Quat::IDENTITY.slerp(dirrotfull, t); + dirrot.mul_vec3(a.clone()) +} diff --git a/deps/directories-rs b/deps/directories-rs deleted file mode 160000 index 164536d..0000000 --- a/deps/directories-rs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 164536daab2c07265167715934081cb1942387b0 diff --git a/deps/glfw-rs b/deps/glfw-rs deleted file mode 160000 index 1ce13a2..0000000 --- a/deps/glfw-rs +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1ce13a2bcf3ef3bf6184bcb4c2a90d9034956841 diff --git a/deps/gltf b/deps/gltf deleted file mode 160000 index 9763a5a..0000000 --- a/deps/gltf +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9763a5abe8c168eb1282a037ff16668542fe2a6d diff --git a/src/assets/fonts/font1.png b/src/assets/fonts/font1.png deleted file mode 100644 index ec06424..0000000 Binary files a/src/assets/fonts/font1.png and /dev/null differ diff --git a/src/assets/fonts/font2.png b/src/assets/fonts/font2.png deleted file mode 100644 index 8435cad..0000000 Binary files a/src/assets/fonts/font2.png and /dev/null differ diff --git a/src/assets/fonts/simple.png b/src/assets/fonts/simple.png deleted file mode 100644 index 7b1d2a3..0000000 Binary files a/src/assets/fonts/simple.png and /dev/null differ diff --git a/src/assets/shaders/bitmap/frag.glsl b/src/assets/shaders/bitmap/frag.glsl deleted file mode 100644 index 7df9a5c..0000000 --- a/src/assets/shaders/bitmap/frag.glsl +++ /dev/null @@ -1,17 +0,0 @@ -#version 300 es -precision highp float; - -uniform sampler2D texture_data; - -in vec2 vertex_texcoord; -in vec3 vertex_color; -out vec4 frag_color; - -void main() { - vec4 texel = texture(texture_data, vertex_texcoord); - if (texel.rgb == vec3(0.0, 0.0, 0.0)) discard; - texel.r = vertex_color.r; - texel.g = vertex_color.g; - texel.b = vertex_color.b; - frag_color = texel; -} diff --git a/src/assets/shaders/bitmap/vert.glsl b/src/assets/shaders/bitmap/vert.glsl deleted file mode 100644 index 192d4b0..0000000 --- a/src/assets/shaders/bitmap/vert.glsl +++ /dev/null @@ -1,17 +0,0 @@ -#version 300 es -precision highp float; - -in vec2 vertex; -in vec2 texcoord; -in vec3 color; - -uniform mat4 transform; - -out vec2 vertex_texcoord; -out vec3 vertex_color; - -void main() { - vertex_texcoord = texcoord; - vertex_color = color; - gl_Position = transform * vec4(vertex, 0.0, 1.0); -} diff --git a/src/assets/shaders/common/frag.glsl b/src/assets/shaders/common/frag.glsl deleted file mode 100644 index 26d63fa..0000000 --- a/src/assets/shaders/common/frag.glsl +++ /dev/null @@ -1,159 +0,0 @@ -#version 300 es -precision highp float; - -uniform vec3 camera_pos; -uniform float time; - -uniform vec3 light_ambient_color; -uniform vec3 light_dir; -uniform vec3 light_dir_color; -uniform int light_count; -uniform vec3 light_pos[5]; -uniform vec3 light_color[5]; -uniform vec2 light_attenuation[5]; -uniform highp sampler2DShadow light_shadowbuffer_dir; -uniform samplerCube light_shadowbuffer_point[5]; - -uniform int has_point_shadows; - -in vec2 vertex_texcoord; -in vec3 vertex_normal; -in vec3 vertex_fragpos; -in vec4 vertex_fragpos_shadow_dir; -in vec3 vertex_view_vector; - -out vec4 frag_color; - -mat3 compute_tbn() { - vec3 p = -vertex_view_vector; - vec3 normal = normalize(vertex_normal); - vec3 dpx = dFdx(p); - vec3 dpy = dFdy(p); - vec2 duvx = dFdx(vertex_texcoord); - vec2 duvy = dFdy(vertex_texcoord); - vec3 dpyperp = cross(dpy, normal); - vec3 dpxperp = cross(normal, dpx); - vec3 tangent = dpyperp * duvx.x + dpxperp * duvy.x; - vec3 bitangent = dpyperp * duvx.y + dpxperp * duvy.y; - float invmax = inversesqrt(max(dot(bitangent, bitangent), dot(bitangent, bitangent))); - return mat3(-tangent * invmax, -bitangent * invmax, normal); -} - -vec4 normal_as_color(vec3 n) { - float r = (128.0 + 127.0 * n.r) / 255.0; - float g = (128.0 + 127.0 * n.g) / 255.0; - float b = (128.0 + 127.0 * n.b) / 255.0; - return vec4(r, g, b, 1.0); -} - -vec3 dir_light(vec3 normal) { - return max(dot(normal, -normalize(light_dir)), 0.0) * light_dir_color; -} - -float dir_shadow(vec3 normal) { - vec3 proj = vertex_fragpos_shadow_dir.xyz / vertex_fragpos_shadow_dir.w; - float bias = 0.002; - // float current_depth = proj.z; - // float bias = max(0.05 * (1.0 - dot(normal, -normalize(light_dir))), 0.005); - proj.z -= bias; - proj *= 0.5; proj += 0.5; - if (proj.z > 1.0) return 0.0; - return 1.0 - texture(light_shadowbuffer_dir, proj.xyz); -} - -float point_shadow(vec3 normal, vec3 shadow_vector, float closest_depth) { - closest_depth *= 25.0; - float current_depth = length(shadow_vector); - float bias = max(0.1 * (1.0 - dot(normal, normalize(shadow_vector))), 0.005); - bias = min(bias + current_depth * 0.01, 0.2); - float shadow = current_depth - bias > closest_depth ? 1.0 : 0.0; - return shadow; -} - -vec3 point_light(vec3 normal, const int idx) { - vec3 pos = light_pos[idx]; - vec3 color = light_color[idx]; - float linear = light_attenuation[idx].x; - float quadratic = light_attenuation[idx].y; - vec3 light_vector = pos - vertex_fragpos; - float distance = length(light_vector); - float attenuation = 1.0 / (1.0 + distance * linear + distance * distance * quadratic); - - float directional = max(dot(normal.xyz, normalize(light_vector)), 0.0); - vec3 directional_light = color * directional; - - vec3 view_dir = normalize(camera_pos - vertex_fragpos); - vec3 reflect_dir = reflect(-normalize(light_vector), normalize(normal.xyz)); - float specular = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0); - vec3 specular_light = 0.5 * specular * color; - // return (directional_light + specular_light) * attenuation; - return directional_light * attenuation; -} - -vec3 point_light_billboard(const int idx) { - vec3 pos = light_pos[idx]; - vec3 color = light_color[idx]; - float linear = light_attenuation[idx].x; - float quadratic = light_attenuation[idx].y; - vec3 light_vector = pos - vertex_fragpos; - float distance = length(light_vector); - float attenuation = 1.0 / (1.0 + distance * linear + distance * distance * quadratic); - - return color * attenuation; -} - -vec3 compute_lighting(vec3 normal) { - vec3 ambient_light = light_ambient_color; - - vec3 from_dir = dir_light(normal) * (1.0 - dir_shadow(normal)); - - vec3 shadow_vector[5]; - for (int i = 0; i < light_count; ++i) { - shadow_vector[i] = vertex_fragpos - light_pos[i]; - shadow_vector[i].x *= -1.0; - } - - // cannot only index array of samplers with a constant, hence the weird setup - #define SAMPLE_SHADOW(n) n < light_count ? texture(light_shadowbuffer_point[n], shadow_vector[n]).r : 1.0 - float shadow_depth[5]; - shadow_depth[0] = SAMPLE_SHADOW(0); - shadow_depth[1] = SAMPLE_SHADOW(1); - shadow_depth[2] = SAMPLE_SHADOW(2); - shadow_depth[3] = SAMPLE_SHADOW(3); - shadow_depth[4] = SAMPLE_SHADOW(4); - - vec3 from_points = vec3(0.0, 0.0, 0.0); - for (int i = 0; i < light_count; ++i) { - from_points += has_point_shadows != 0 - ? point_light(normal, i) * (1.0 - point_shadow(normal, shadow_vector[i], shadow_depth[i])) - : point_light(normal, i); - } - - return (ambient_light + from_dir + from_points); -} - -vec3 compute_lighting_noshadow(vec3 normal) { - vec3 ambient_light = light_ambient_color; - - vec3 from_dir = dir_light(normal); - - vec3 from_points = vec3(0.0, 0.0, 0.0); - for (int i = 0; i < light_count; ++i) { - from_points += point_light(normal, i); - } - - return (ambient_light + from_dir + from_points); -} - -vec3 compute_lighting_billboard(vec3 normal) { - vec3 ambient_light = light_ambient_color; - - vec3 from_dir = light_dir_color / 2.0; - - vec3 from_points = vec3(0.0, 0.0, 0.0); - for (int i = 0; i < light_count; ++i) { - from_points += point_light_billboard(i); - } - - return (ambient_light + from_dir + from_points); -} diff --git a/src/assets/shaders/common/vert.glsl b/src/assets/shaders/common/vert.glsl deleted file mode 100644 index b8e11c5..0000000 --- a/src/assets/shaders/common/vert.glsl +++ /dev/null @@ -1,30 +0,0 @@ -#version 300 es -precision highp float; - -in vec3 vertex; -in vec3 normal; -in vec2 texcoord; - -uniform mat4 view; -uniform mat4 position; -uniform mat4 projection; -uniform mat4 normal_matrix; -uniform mat4 lightspace_matrix; -uniform vec3 camera_pos; - -out vec2 vertex_texcoord; -out vec3 vertex_normal; -out vec3 vertex_fragpos; -out vec4 vertex_fragpos_shadow_dir; -out vec3 vertex_view_vector; - -void default_main() -{ - vertex_texcoord = texcoord; - vertex_normal = (normal_matrix * vec4(normal, 1.0)).xyz; - vec3 pos = (position * vec4(vertex, 1.0)).xyz; - vertex_fragpos = pos; - vertex_fragpos_shadow_dir = lightspace_matrix * vec4(pos, 1.0); - vertex_view_vector = camera_pos - pos; - gl_Position = projection * view * vec4(pos, 1.0); -} diff --git a/src/assets/shaders/scale/frag.glsl b/src/assets/shaders/scale/frag.glsl deleted file mode 100644 index 5fce547..0000000 --- a/src/assets/shaders/scale/frag.glsl +++ /dev/null @@ -1,12 +0,0 @@ -#version 300 es -precision highp float; - -uniform sampler2D texture_data; - -in vec2 vertex_texcoord; -out vec4 frag_color; - -void main() { - vec4 texel = texture(texture_data, vertex_texcoord); - frag_color = texel; -} diff --git a/src/assets/shaders/scale/vert.glsl b/src/assets/shaders/scale/vert.glsl deleted file mode 100644 index e05bbb6..0000000 --- a/src/assets/shaders/scale/vert.glsl +++ /dev/null @@ -1,22 +0,0 @@ -#version 300 es -precision highp float; - -out vec2 vertex_texcoord; - -void main() { - const vec2 positions[4] = vec2[]( - vec2(-1, -1), - vec2(+1, -1), - vec2(-1, +1), - vec2(+1, +1) - ); - const vec2 coords[4] = vec2[]( - vec2(0, 0), - vec2(1, 0), - vec2(0, 1), - vec2(1, 1) - ); - - vertex_texcoord = coords[gl_VertexID]; - gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); -} diff --git a/src/assets/shaders/scene/frag.glsl b/src/assets/shaders/scene/frag.glsl deleted file mode 100644 index 81e08b9..0000000 --- a/src/assets/shaders/scene/frag.glsl +++ /dev/null @@ -1,11 +0,0 @@ -uniform sampler2D texture_data; - -void main() -{ - vec4 texel = texture(texture_data, vertex_texcoord); - if (texel.a != 1.0) { - discard; - } - - frag_color = vec4(texel.rgb, texel.a); -} diff --git a/src/assets/shaders/scene/vert.glsl b/src/assets/shaders/scene/vert.glsl deleted file mode 100644 index 64f400c..0000000 --- a/src/assets/shaders/scene/vert.glsl +++ /dev/null @@ -1,18 +0,0 @@ -in vec4 joint; -in vec4 weight; - -uniform mat4 joint_matrices[128]; - -void main() -{ - vertex_texcoord = texcoord; - vertex_normal = (normal_matrix * vec4(normal, 1.0)).xyz; - mat4 skin - = weight.x * joint_matrices[int(joint.x)] - + weight.y * joint_matrices[int(joint.y)] - + weight.z * joint_matrices[int(joint.z)] - + weight.w * joint_matrices[int(joint.w)]; - vec3 pos = (position * skin * vec4(vertex, 1.0)).xyz; - vertex_view_vector = camera_pos - pos; - gl_Position = projection * view * vec4(pos, 1.0); -} diff --git a/src/assets/shaders/test/frag.glsl b/src/assets/shaders/test/frag.glsl deleted file mode 100644 index a52aa15..0000000 --- a/src/assets/shaders/test/frag.glsl +++ /dev/null @@ -1,23 +0,0 @@ -// uniform int has_normal_map; -// uniform sampler2D normal_map; - -uniform sampler2D texture_data; - -void main() -{ - vec2 inverted_texcoord = vec2(vertex_texcoord.x, 1.0 - vertex_texcoord.y); - vec4 texel = texture(texture_data, inverted_texcoord); - if (texel.a != 1.0) { - discard; - } - - // mat3 tbn = compute_tbn(); - // vec3 normal = has_normal_map != 0 - // ? normalize(tbn * (texture(normal_map, inverted_texcoord).xyz * 2.0 - 1.0)) - // : normalize(vertex_normal); - vec3 normal = normalize(vertex_normal); - - vec3 lighting = compute_lighting_noshadow(normal); - - frag_color = vec4(texel.rgb * lighting, texel.a); -} diff --git a/src/assets/shaders/test/vert.glsl b/src/assets/shaders/test/vert.glsl deleted file mode 100644 index e324f7e..0000000 --- a/src/assets/shaders/test/vert.glsl +++ /dev/null @@ -1,4 +0,0 @@ -void main() -{ - default_main(); -} \ No newline at end of file diff --git a/src/assets/shaders/tiled/frag.glsl b/src/assets/shaders/tiled/frag.glsl deleted file mode 100644 index 5fce547..0000000 --- a/src/assets/shaders/tiled/frag.glsl +++ /dev/null @@ -1,12 +0,0 @@ -#version 300 es -precision highp float; - -uniform sampler2D texture_data; - -in vec2 vertex_texcoord; -out vec4 frag_color; - -void main() { - vec4 texel = texture(texture_data, vertex_texcoord); - frag_color = texel; -} diff --git a/src/assets/shaders/tiled/vert.glsl b/src/assets/shaders/tiled/vert.glsl deleted file mode 100644 index 4ff9865..0000000 --- a/src/assets/shaders/tiled/vert.glsl +++ /dev/null @@ -1,14 +0,0 @@ -#version 300 es -precision highp float; - -in vec2 vertex; -in vec2 texcoord; - -uniform mat4 transform; - -out vec2 vertex_texcoord; - -void main() { - vertex_texcoord = texcoord; - gl_Position = transform * vec4(vertex, 0.0, 1.0); -} diff --git a/src/assets/shaders/truetype/frag.glsl b/src/assets/shaders/truetype/frag.glsl deleted file mode 100644 index 3f62e01..0000000 --- a/src/assets/shaders/truetype/frag.glsl +++ /dev/null @@ -1,16 +0,0 @@ -#version 300 es -precision highp float; - -uniform sampler2D texture_data; - -in vec2 vertex_texcoord; -in vec3 vertex_color; -out vec4 frag_color; - -void main() -{ - float val = texture(texture_data, vertex_texcoord).r; - if (val == 0.0) discard; - vec4 texel = vec4(vertex_color, val); - frag_color = texel; -} diff --git a/src/assets/shaders/truetype/vert.glsl b/src/assets/shaders/truetype/vert.glsl deleted file mode 100644 index 192d4b0..0000000 --- a/src/assets/shaders/truetype/vert.glsl +++ /dev/null @@ -1,17 +0,0 @@ -#version 300 es -precision highp float; - -in vec2 vertex; -in vec2 texcoord; -in vec3 color; - -uniform mat4 transform; - -out vec2 vertex_texcoord; -out vec3 vertex_color; - -void main() { - vertex_texcoord = texcoord; - vertex_color = color; - gl_Position = transform * vec4(vertex, 0.0, 1.0); -} diff --git a/src/audio.rs b/src/audio.rs deleted file mode 100644 index 2b4226c..0000000 --- a/src/audio.rs +++ /dev/null @@ -1,209 +0,0 @@ -use std::collections::HashMap; - -#[cfg(target_arch = "wasm32")] -use std::cell::RefCell; - -#[cfg(target_arch = "wasm32")] -pub struct Context { - pub audio: web_sys::AudioContext, -} - -#[cfg(target_arch = "wasm32")] -impl Context { - pub fn new() -> Self { - let audio = web_sys::AudioContext::new() - .expect("failed to create audio context"); - Self { - audio, - } - } -} - -#[cfg(target_arch = "wasm32")] -pub struct Audio { - pub buffer: &'static RefCell>, - //pub source: &'static web_sys::AudioBufferSourceNode, -} - -#[cfg(target_arch = "wasm32")] -impl Audio { - pub fn new(ctx: &Context, bytes: &[u8]) -> Self { - let sbuffer: &_ = Box::leak(Box::new(RefCell::new(None))); - let sclone: &'static RefCell> = - <&_>::clone(&sbuffer); - let ret = Audio { - buffer: sclone, - }; - let jsp = ctx.audio.decode_audio_data(&js_sys::Uint8Array::from(bytes).buffer()).expect("failed to decode audio"); - let promise = wasm_bindgen_futures::JsFuture::from(jsp); - wasm_bindgen_futures::spawn_local(async { - if let Some(data) = promise.await.ok() { - *sbuffer.borrow_mut() = Some(web_sys::AudioBuffer::from(data)); - } - () - }); - ret - } - - pub fn play(&self, ctx: &Context, looping: Option<(Option, Option)>) -> Option { - let source = ctx.audio.create_buffer_source().ok()?; - source.set_buffer((&*self.buffer.borrow()).as_ref()); - if let Some((ms, me)) = looping { - source.set_loop(true); - if let Some(s) = ms { source.set_loop_start(s) } - if let Some(e) = me { source.set_loop_end(e) } - } - source.connect_with_audio_node(&ctx.audio.destination()).ok()?; - source.start().ok()?; - Some(source) - } -} - -#[cfg(target_arch = "wasm32")] -pub struct Assets { - pub ctx: Context, - - pub audio: HashMap, - - pub music_node: Option, -} - -#[cfg(target_arch = "wasm32")] -impl Assets { - pub fn new(f : F) -> Self where F: Fn(&Context) -> HashMap { - let ctx = Context::new(); - - let audio = f(&ctx); - - Self { - ctx, - audio, - music_node: None, - } - } - - pub fn play_sfx(&mut self, name: &str) { - if let Some(a) = self.audio.get(name) { - a.play(&self.ctx, None); - } - } - - pub fn is_music_playing(&self) -> bool { - if let Some(ms) = &self.music_node { - ms.buffer().is_some() - } else { false } - } - - pub fn play_music(&mut self, name: &str, start: Option, end: Option) { - if let Some(s) = &self.music_node { - let _ = s.stop(); - } - if let Some(a) = self.audio.get(name) { - self.music_node = a.play(&self.ctx, Some((start, end))); - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -pub struct Context { - manager: kira::manager::AudioManager, -} - -#[cfg(not(target_arch = "wasm32"))] -impl Context { - pub fn new() -> Self { - Self { - manager: kira::manager::AudioManager::new(kira::manager::AudioManagerSettings::default()) - .expect("failed to create audio manager"), - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -pub struct Audio { - data: kira::sound::static_sound::StaticSoundData, -} - -#[cfg(not(target_arch = "wasm32"))] -impl Audio { - pub fn new(_ctx: &Context, bytes: &'static [u8]) -> Self { - Self { - data: kira::sound::static_sound::StaticSoundData::from_cursor(std::io::Cursor::new(bytes)) - .expect("failed to decode audio"), - } - } - - pub fn play( - &self, - ctx: &mut Context, - looping: Option<(Option, Option)> - ) -> Result - { - let sd = if let Some((ss, se)) = looping { - let start = if let Some(s) = ss { s } else { 0.0 }; - if let Some(e) = se { - self.data.loop_region(start..e) - } else { - self.data.loop_region(start..) - } - } else { - self.data.clone() - }; - match ctx.manager.play(sd) { - Ok(h) => Ok(h), - Err(e) => Err(e.to_string()), - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -pub struct Assets { - pub ctx: Context, - pub audio: HashMap, - pub music_handle: Option, -} - -#[cfg(not(target_arch = "wasm32"))] -impl Assets { - pub fn new(f : F) -> Self where F: Fn(&Context) -> HashMap { - let ctx = Context::new(); - - let audio = f(&ctx); - - Self { - ctx, - audio, - music_handle: None, - } - } - - pub fn play_sfx(&mut self, name: &str) { - if let Some(a) = self.audio.get(name) { - if let Err(e) = a.play(&mut self.ctx, None) { - log::warn!("failed to play sound {}: {}", name, e); - } - } - } - - pub fn is_music_playing(&self) -> bool { - if let Some(mh) = &self.music_handle { - mh.state() == kira::sound::PlaybackState::Playing - } else { false } - } - - pub fn play_music(&mut self, name: &str, start: Option, end: Option) { - if let Some(s) = &mut self.music_handle { - let _ = s.stop(kira::tween::Tween::default()); - } - if let Some(a) = self.audio.get(name) { - match a.play(&mut self.ctx, Some((start, end))) { - Ok(h) => { - self.music_handle = Some(h); - }, - Err(e) => { - log::warn!("failed to play music {}: {}", name, e); - } - } - } - } -} diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index 9f0d4ad..0000000 --- a/src/context.rs +++ /dev/null @@ -1,228 +0,0 @@ -use glow::HasContext; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::prelude::*; - -#[cfg(target_arch = "wasm32")] -use winit::platform::web::WindowExtWebSys; - -#[cfg(target_arch = "wasm32")] -#[wasm_bindgen(module = "/src/helpers.js")] -extern { - fn js_track_resized_setup(); - fn js_poll_resized() -> bool; -} - -#[cfg(not(target_arch = "wasm32"))] -pub struct Context { - pub render_width: f32, - pub render_height: f32, - pub resize: bool, - pub glfw: std::cell::RefCell, - pub window: std::cell::RefCell, - pub gl: glow::Context, - pub emptyvao: glow::VertexArray, - pub start_instant: std::time::Instant, -} - - -#[cfg(target_arch = "wasm32")] -pub struct Context { - pub render_width: f32, - pub render_height: f32, - pub resize: bool, - pub window: winit::window::Window, - pub gl: glow::Context, - pub emptyvao: glow::VertexArray, - pub performance: web_sys::Performance, -} - -impl Context { - pub fn compute_upscale(&self, windoww: u32, windowh: u32) -> u32 { - let mut ratio = 1; - loop { - if (self.render_width as u32) * ratio > windoww - || (self.render_height as u32) * ratio > windowh - { - break; - } - ratio += 1; - } - (ratio - 1).max(1) - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn new(glfw: std::cell::RefCell, window: std::cell::RefCell, gl: glow::Context, render_width: f32, render_height: f32, resize: bool) -> Self { - let emptyvao = unsafe { - gl.create_vertex_array().expect("failed to initialize vao") - }; - let ret = Self { - render_width, render_height, - resize, - glfw, window, - gl, - emptyvao, - start_instant: std::time::Instant::now(), - }; - ret.init(); - ret - } - - #[cfg(target_arch = "wasm32")] - pub fn new(window: winit::window::Window, gl: glow::Context, render_width: f32, render_height: f32, resize: bool) -> Self { - let emptyvao = unsafe { - gl.create_vertex_array().expect("failed to initialize vao") - }; - - #[cfg(target_arch = "wasm32")] - js_track_resized_setup(); - - let ret = Self { - render_width, render_height, - resize, - window, - gl, - emptyvao, - - #[cfg(target_arch = "wasm32")] - performance: web_sys::window().expect("failed to find window") - .performance().expect("failed to get performance"), - }; - ret.init(); - ret - } - - pub fn init(&self) { - unsafe { - self.gl.clear_color(0.1, 0.1, 0.1, 1.0); - self.gl.clear_depth_f32(1.0); - - self.gl.enable(glow::DEPTH_TEST); - self.gl.depth_func(glow::LEQUAL); - - self.gl.enable(glow::BLEND); - self.gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); - - self.gl.enable(glow::STENCIL_TEST); - - self.gl.cull_face(glow::FRONT); - } - } - - #[cfg(target_arch = "wasm32")] - pub fn maximize_canvas(&self) { - if self.resize { - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - let inner_size = { - let browser_window = doc.default_view() - .or_else(web_sys::window) - .unwrap(); - winit::dpi::PhysicalSize::new( - browser_window.inner_width().unwrap().as_f64().unwrap(), - browser_window.inner_height().unwrap().as_f64().unwrap(), - ) - }; - self.window.canvas().unwrap().set_width(inner_size.width as _); - self.window.canvas().unwrap().set_height(inner_size.height as _); - let _ = self.window.request_inner_size(inner_size); - Some(()) - }) - .expect("failed to resize canvas"); - } - } - - #[cfg(target_arch = "wasm32")] - pub fn resize_necessary(&self) -> bool { - js_poll_resized() - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn resize_necessary(&self) -> bool { - false - } - - pub fn clear_color(&self, color: glam::Vec4) { - unsafe { - self.gl.clear_color(color.x, color.y, color.z, color.w); - } - } - - pub fn clear_depth(&self) { - unsafe { - self.gl.clear(glow::DEPTH_BUFFER_BIT); - } - } - - pub fn clear(&self) { - unsafe { - self.gl.stencil_mask(0xff); - self.gl.clear(glow::COLOR_BUFFER_BIT | glow::DEPTH_BUFFER_BIT | glow::STENCIL_BUFFER_BIT); - } - } - - pub fn begin_stencil(&self) { - unsafe { - self.gl.stencil_func(glow::ALWAYS, 1, 0xff); - self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::REPLACE); - } - } - - pub fn use_stencil(&self) { - unsafe { - self.gl.stencil_func(glow::EQUAL, 1, 0xff); - self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); - } - } - - pub fn end_stencil(&self) { - unsafe { - self.gl.stencil_func(glow::ALWAYS, 1, 0xff); - self.gl.stencil_op(glow::KEEP, glow::KEEP, glow::KEEP); - } - } - - pub fn render_no_geometry(&self) { - unsafe { - self.gl.bind_vertex_array(Some(self.emptyvao)); - self.gl.draw_arrays(glow::TRIANGLE_STRIP, 0, 4); - } - } - - pub fn reset_blend(&self) { - unsafe { - self.gl.blend_func(glow::SRC_ALPHA, glow::ONE_MINUS_SRC_ALPHA); - } - } - - pub fn inverse_blend(&self) { - unsafe { - self.gl.blend_func( - glow::ONE_MINUS_DST_COLOR, - glow::ZERO, - ); - } - } - - pub fn enable_culling(&self) { - unsafe { - self.gl.enable(glow::CULL_FACE); - } - } - - pub fn disable_culling(&self) { - unsafe { - self.gl.disable(glow::CULL_FACE); - } - } - - pub fn check_error(&self) { - unsafe { - let err = self.gl.get_error(); - if err != 0 { - log::warn!("gl error: {}", err); - } - } - } -} diff --git a/src/font.rs b/src/font.rs deleted file mode 100644 index e431896..0000000 --- a/src/font.rs +++ /dev/null @@ -1,363 +0,0 @@ -use std::collections::HashMap; - -use crate::{context, mesh, shader, texture}; -use glow::HasContext; - -pub struct Bitmap { - pub char_width: i32, - pub char_height: i32, - pub font_width: i32, - pub font_height: i32, - pub shader: shader::Shader, - pub font: texture::Texture, - pub vao: glow::VertexArray, - pub vertex_buf: glow::Buffer, - pub texcoords_buf: glow::Buffer, - pub colors_buf: glow::Buffer, - pub index_buf: glow::Buffer, -} - -impl Bitmap { - pub fn from_image( - ctx: &context::Context, - char_width: i32, char_height: i32, - font_width: i32, font_height: i32, - data: &[u8], - ) -> Self { - let shader = shader::Shader::new_nolib( - &ctx, - include_str!("assets/shaders/bitmap/vert.glsl"), - include_str!("assets/shaders/bitmap/frag.glsl"), - ); - let font = texture::Texture::new(ctx, data); - unsafe { - let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); - ctx.gl.bind_vertex_array(Some(vao)); - let vertex_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 2, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); - let texcoords_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); - let colors_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(colors_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_COLOR, 3, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_COLOR); - let index_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buf)); - Self { - char_width, char_height, - font_width, font_height, - shader, - font, - vao, - vertex_buf, - texcoords_buf, - colors_buf, - index_buf, - } - } - } - - pub fn new(ctx: &context::Context) -> Self { - Self::from_image(ctx, 7, 9, 112, 54, include_bytes!("assets/fonts/simple.png")) - } - - pub fn render_text_helper(&self, ctx: &context::Context, pos: &glam::Vec2, text: &str, color: &[glam::Vec3]) { - let mut cur = glam::Vec2::new(0.0, 0.0); - let mut vertices = Vec::new(); - let mut texcoords = Vec::new(); - let mut colors = Vec::new(); - let mut indices = Vec::new(); - let cwidth = self.char_width as f32 / self.font_width as f32; - let cheight = self.char_height as f32 / self.font_height as f32; - let row_len = self.font_width as u32 / self.char_width as u32; - for (i, c) in text.chars().enumerate() { - if c == '\n' { - cur.x = 0.0; - cur.y -= self.char_height as f32; - } else { - let idx = vertices.len() as u32; - vertices.push(cur); - vertices.push(cur + glam::Vec2::new(self.char_width as f32, 0.0)); - vertices.push(cur + glam::Vec2::new(self.char_width as f32, self.char_height as f32)); - vertices.push(cur + glam::Vec2::new(0.0, self.char_height as f32)); - let cidx = c as u32 - ' ' as u32; - let col = cidx % row_len; - let row = cidx / row_len; - let tcbase = glam::Vec2::new(col as f32 * cwidth, row as f32 * cheight); - texcoords.push(tcbase + glam::Vec2::new(0.0, cheight)); - texcoords.push(tcbase + glam::Vec2::new(cwidth, cheight)); - texcoords.push(tcbase + glam::Vec2::new(cwidth, 0.0)); - texcoords.push(tcbase); - let c = if let Some(c) = color.get(if color.len() == 0 { 0 } else { i % color.len() }) { - *c - } else { - glam::Vec3::new(1.0, 1.0, 1.0) - }; - colors.push(c); colors.push(c); colors.push(c); colors.push(c); - indices.push(idx + 0); indices.push(idx + 1); indices.push(idx + 2); - indices.push(idx + 0); indices.push(idx + 3); indices.push(idx + 2); - cur.x += self.char_width as f32; - } - } - let index_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); - self.shader.bind(ctx); - self.font.bind(ctx); - let scale = glam::Vec2::new(2.0 / ctx.render_width, 2.0 / ctx.render_height); - let offset = glam::Vec2::new( - -ctx.render_width / 2.0, - ctx.render_height / 2.0 - self.char_height as f32, - ); - let npos = (glam::Vec2::new(pos.x, -pos.y) + offset) * scale; - self.shader.set_mat4( - ctx, "transform", - &glam::Mat4::from_scale_rotation_translation( - glam::Vec3::new(scale.x, scale.y, 1.0), - glam::Quat::IDENTITY, - glam::Vec3::new(npos.x, npos.y, 0.0), - ), - ); - unsafe { - ctx.gl.bind_vertex_array(Some(self.vao)); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - vertices.as_ptr() as _, - vertices.len() * std::mem::size_of::() * 2, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.texcoords_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - texcoords.as_ptr() as _, - texcoords.len() * std::mem::size_of::() * 2, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.colors_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - colors.as_ptr() as _, - colors.len() * std::mem::size_of::() * 3, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - &index_bytes, - glow::STATIC_DRAW, - ); - ctx.gl.draw_elements(glow::TRIANGLES, indices.len() as _, glow::UNSIGNED_INT, 0); - } - } - - pub fn render_text(&self, ctx: &context::Context, pos: &glam::Vec2, text: &str) { - self.render_text_helper(ctx, pos, text, &[]); - } -} - -pub struct AtlasInfo { - pub pos: usize, -} - -pub struct TrueType { - pub shader: shader::Shader, - pub font: fontdue::Font, - pub atlas: texture::Texture, - pub atlaswidth: usize, - pub cellwidth: usize, - pub cellheight: usize, - pub info: HashMap, - pub vao: glow::VertexArray, - pub vertex_buf: glow::Buffer, - pub texcoords_buf: glow::Buffer, - pub colors_buf: glow::Buffer, - pub index_buf: glow::Buffer, -} - -impl TrueType { - pub fn new(ctx: &context::Context, size: f32, data: &[u8]) -> Self { - let shader = shader::Shader::new_nolib( - &ctx, - include_str!("assets/shaders/truetype/vert.glsl"), - include_str!("assets/shaders/truetype/frag.glsl"), - ); - let font = fontdue::Font::from_bytes(data, fontdue::FontSettings::default()) - .expect("failed to load font"); - let mut chardata = HashMap::new(); - for ci in 0..128 { - if let Some(c) = char::from_u32(ci) { - if !c.is_ascii_graphic() { continue; } - let res = font.rasterize(c, size); - chardata.insert(c, res); - } - } - let mut cellwidth = 0; - let mut cellbase = 0; - let mut cellextra = 0; - for (_, (m, _)) in &chardata { - if m.width > cellwidth { cellwidth = m.width } - if m.height > cellbase { cellbase = m.height } - let extra = (-m.ymin.min(0)) as usize; - if extra > cellextra { cellextra = extra } - } - let mut cellheight = cellbase + cellextra; - cellwidth = cellwidth.next_power_of_two(); - cellheight = cellheight.next_power_of_two(); - let atlaswidth = (chardata.len() * cellwidth).next_power_of_two(); - let mut info = HashMap::new(); - let mut atlas_bmp: Vec = vec![0; atlaswidth * cellheight]; - for (i, (c, (m, bmp))) in chardata.iter().enumerate() { - let by = ((cellbase as i32) - (m.height as i32) - m.ymin) as usize; - let bx = cellwidth * i; - info.insert(*c, AtlasInfo { - pos: cellwidth * i, - }); - for x in 0..m.width { - for y in 0..m.height { - atlas_bmp[bx + x + (by + y) * atlaswidth] = bmp[x + y * m.width]; - } - } - } - let atlas = texture::Texture::new_empty(ctx); - unsafe { - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(atlas.tex)); - ctx.gl.pixel_store_i32(glow::UNPACK_ALIGNMENT, 1); - ctx.gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::R8 as i32, - atlaswidth as i32, - cellheight as i32, - 0, - glow::RED, - glow::UNSIGNED_BYTE, - Some(&atlas_bmp), - ); - ctx.gl.generate_mipmap(glow::TEXTURE_2D); - let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); - ctx.gl.bind_vertex_array(Some(vao)); - let vertex_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 2, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); - let texcoords_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); - let colors_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(colors_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_COLOR, 3, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_COLOR); - let index_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buf)); - Self { - shader, font, atlas, atlaswidth, cellwidth, cellheight, info, - vao, vertex_buf, texcoords_buf, colors_buf, index_buf, - } - } - } - - pub fn render_text_helper(&self, ctx: &context::Context, pos: &glam::Vec2, spacing: &glam::Vec2, text: &str, color: &[glam::Vec3]) { - let mut cur = glam::Vec2::new(0.0, 0.0); - let mut vertices = Vec::new(); - let mut texcoords = Vec::new(); - let mut colors = Vec::new(); - let mut indices = Vec::new(); - let cellwidth = self.cellwidth as f32; - let cellheight = self.cellheight as f32; - let cwidth = cellwidth / self.atlaswidth as f32; - let cheight = 1.0; - for (i, c) in text.chars().enumerate() { - if c == '\n' { - cur.x = 0.0; - cur.y -= spacing.y; - } else { - let idx = vertices.len() as u32; - if let Some(off) = self.info.get(&c) { - vertices.push(cur); - vertices.push(cur + glam::Vec2::new(cellwidth, 0.0)); - vertices.push(cur + glam::Vec2::new(cellwidth, cellheight)); - vertices.push(cur + glam::Vec2::new(0.0, cellheight)); - let tcbase = glam::Vec2::new(off.pos as f32 / self.atlaswidth as f32, 0.0); - texcoords.push(tcbase + glam::Vec2::new(0.0, cheight)); - texcoords.push(tcbase + glam::Vec2::new(cwidth, cheight)); - texcoords.push(tcbase + glam::Vec2::new(cwidth, 0.0)); - texcoords.push(tcbase); - let c = if let Some(c) = color.get(i) { - *c - } else { - glam::Vec3::new(1.0, 1.0, 1.0) - }; - colors.push(c); colors.push(c); colors.push(c); colors.push(c); - indices.push(idx + 0); indices.push(idx + 1); indices.push(idx + 2); - indices.push(idx + 0); indices.push(idx + 3); indices.push(idx + 2); - } - cur.x += spacing.x; - } - } - let index_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); - let scale = glam::Vec2::new(2.0 / ctx.render_width, 2.0 / ctx.render_height); - let offset = glam::Vec2::new( - -ctx.render_width / 2.0, - ctx.render_height / 2.0 - cellheight as f32, - ); - let npos = (glam::Vec2::new(pos.x, -pos.y) + offset) * scale; - self.shader.bind(ctx); - self.shader.set_mat4( - ctx, "transform", - &glam::Mat4::from_scale_rotation_translation( - glam::Vec3::new(scale.x, scale.y, 1.0), - glam::Quat::IDENTITY, - glam::Vec3::new(npos.x, npos.y, 0.0), - ), - ); - unsafe { - ctx.gl.active_texture(glow::TEXTURE0); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.atlas.tex)); - ctx.gl.bind_vertex_array(Some(self.vao)); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.vertex_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - vertices.as_ptr() as _, - vertices.len() * std::mem::size_of::() * 2, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.texcoords_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - texcoords.as_ptr() as _, - texcoords.len() * std::mem::size_of::() * 2, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(self.colors_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - colors.as_ptr() as _, - colors.len() * std::mem::size_of::() * 3, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(self.index_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - &index_bytes, - glow::STATIC_DRAW, - ); - ctx.gl.draw_elements(glow::TRIANGLES, indices.len() as _, glow::UNSIGNED_INT, 0); - } - } -} diff --git a/src/framebuffer.rs b/src/framebuffer.rs deleted file mode 100644 index db808e4..0000000 --- a/src/framebuffer.rs +++ /dev/null @@ -1,145 +0,0 @@ -use glow::HasContext; - -use crate::context; - -pub struct Framebuffer { - pub tex: Option, - pub fbo: Option, - pub dims: glam::Vec2, - pub offsets: glam::Vec2, -} - -impl Framebuffer { - pub fn screen(ctx: &context::Context) -> Self { - #[cfg(target_arch = "wasm32")] - let (windoww, windowh): (f32, f32) = if ctx.resize { - ctx.window.inner_size().into() - } else { - (ctx.render_width, ctx.render_height) - }; - #[cfg(not(target_arch = "wasm32"))] - let (windoww, windowh) = { - let (w, h) = ctx.window.borrow().get_size(); - (w as f32, h as f32) - }; - let ratio = ctx.compute_upscale(windoww as _, windowh as _) as f32; - let upscalew = ctx.render_width * ratio; - let upscaleh = ctx.render_height * ratio; - let offsetx = (windoww - upscalew) / 2.0; - let offsety = (windowh - upscaleh) / 2.0; - log::info!("resize window: {:?}, upscale: {:?}, offset: {:?}", (windoww, windowh), (upscalew, upscaleh), (offsetx, offsety)); - Self { - tex: None, - fbo: None, - dims: glam::Vec2::new(upscalew, upscaleh), - offsets: glam::Vec2::new(offsetx, offsety), - } - } - - pub fn new(ctx: &context::Context, dims: &glam::Vec2, offsets: &glam::Vec2) -> Self { - unsafe { - let fbo = ctx.gl.create_framebuffer() - .expect("failed to create framebuffer"); - ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); - - let depth_buffer = ctx.gl.create_renderbuffer() - .expect("failed to create depth buffer"); - ctx.gl.bind_renderbuffer(glow::RENDERBUFFER, Some(depth_buffer)); - ctx.gl.renderbuffer_storage(glow::RENDERBUFFER, glow::DEPTH_COMPONENT32F, dims.x as _, dims.y as _); - ctx.gl.framebuffer_renderbuffer(glow::FRAMEBUFFER, glow::DEPTH_ATTACHMENT, glow::RENDERBUFFER, Some(depth_buffer)); - - let stencil_buffer = ctx.gl.create_renderbuffer() - .expect("failed to create stencil buffer"); - ctx.gl.bind_renderbuffer(glow::RENDERBUFFER, Some(stencil_buffer)); - ctx.gl.renderbuffer_storage(glow::RENDERBUFFER, glow::DEPTH_STENCIL, dims.x as _, dims.y as _); - ctx.gl.framebuffer_renderbuffer(glow::FRAMEBUFFER, glow::DEPTH_STENCIL_ATTACHMENT, glow::RENDERBUFFER, Some(stencil_buffer)); - - let tex = ctx.gl.create_texture() - .expect("failed to create framebuffer texture"); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); - ctx.gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::RGBA as _, - dims.x as _, - dims.y as _, - 0, - glow::RGBA, - glow::UNSIGNED_BYTE, - None, - ); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as _); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as _); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as _); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as _); - ctx.gl.framebuffer_texture_2d(glow::FRAMEBUFFER, glow::COLOR_ATTACHMENT0, glow::TEXTURE_2D, Some(tex), 0); - ctx.gl.draw_buffer(glow::COLOR_ATTACHMENT0); - - let status = ctx.gl.check_framebuffer_status(glow::FRAMEBUFFER); - if status != glow::FRAMEBUFFER_COMPLETE { - panic!("error initializing framebuffer:\n{}", status); - } - - Self { - tex: Some(tex), - fbo: Some(fbo), - dims: dims.clone(), - offsets: offsets.clone(), - } - } - } - - pub fn bind_texture(&self, ctx: &context::Context) { - unsafe { - ctx.gl.active_texture(glow::TEXTURE0); - ctx.gl.bind_texture(glow::TEXTURE_2D, self.tex); - } - } - - pub fn bind(&self, ctx: &context::Context) { - unsafe { - ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, self.fbo); - ctx.gl.viewport( - self.offsets.x as _, - self.offsets.y as _, - self.dims.x as _, - self.dims.y as _, - ); - } - } - - pub fn blit(&self, ctx: &context::Context, dest: &Self, pos: &glam::Vec2, scale: &glam::Vec2) { - unsafe { - ctx.gl.bind_framebuffer(glow::READ_FRAMEBUFFER, self.fbo); - ctx.gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, dest.fbo); - ctx.gl.blit_framebuffer( - 0, 0, self.dims.x as _, self.dims.y as _, - pos.x as _, pos.y as _, (pos.x + scale.x) as _, (pos.y + scale.y) as _, - glow::COLOR_BUFFER_BIT, glow::NEAREST - ); - } - } - - pub fn get_pixels(&self, ctx: &context::Context, buf: &mut [glam::Vec3]) { - let w = self.dims.x as usize; - let h = self.dims.y as usize; - let tmp = vec![glam::Vec3::default(); w * h]; - unsafe { - ctx.gl.bind_texture(glow::TEXTURE_2D, self.tex); - ctx.gl.get_tex_image( - glow::TEXTURE_2D, 0, glow::RGB, glow::FLOAT, - glow::PixelPackData::Slice( - std::slice::from_raw_parts_mut( - tmp.as_ptr() as *mut u8, - tmp.len() * std::mem::size_of::() - ) - ), - ); - } - for y in 0..h { - for x in 0..w { - buf[x + (h - 1 - y) * w] = tmp[x + y * w]; - } - } - } -} diff --git a/src/helpers.js b/src/helpers.js deleted file mode 100644 index aaaafa1..0000000 --- a/src/helpers.js +++ /dev/null @@ -1,13 +0,0 @@ -let resized = false; - -export async function js_track_resized_setup() { - window.addEventListener("resize", () => { - resized = true; - }); -} - -export function js_poll_resized() { - let ret = resized; - resized = false; - return ret; -} diff --git a/src/js/module.js b/src/js/module.js deleted file mode 100644 index f7bff39..0000000 --- a/src/js/module.js +++ /dev/null @@ -1,7 +0,0 @@ -export function js_build_interface() { - return { - env: { - log_info: window.wasmBindings.log_info, - }, - }; -} diff --git a/src/level2d.rs b/src/level2d.rs deleted file mode 100644 index 8b033ff..0000000 --- a/src/level2d.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod tiled; diff --git a/src/level2d/tiled.rs b/src/level2d/tiled.rs deleted file mode 100644 index 466260a..0000000 --- a/src/level2d/tiled.rs +++ /dev/null @@ -1,298 +0,0 @@ -use std::collections::HashMap; -use serde::Deserialize; -use glow::HasContext; - -use crate::{context, erm, mesh, shader, texture, Erm}; - -#[derive(Debug)] -pub enum Err { - LayerIndexOutOfBounds, - LayerDataTooSmall, - GIDNotFound(u32), - AssetNotFound(String), - GL(String), -} -impl std::error::Error for Err {} -impl std::fmt::Display for Err { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::LayerIndexOutOfBounds => write!(f, "layer index out of bounds"), - Self::LayerDataTooSmall => write!(f, "layer data too small for dimensions"), - Self::GIDNotFound(gid) => write!(f, "GID not found: {}", gid), - Self::AssetNotFound(ass) => write!(f, "asset not found: {}", ass), - Self::GL(msg) => write!(f, "GL error: {msg:}"), - } - } -} - -#[derive(Debug, Deserialize)] -pub enum LayerType { - #[serde(rename = "tilelayer")] - Tile, - #[serde(rename = "imagelayer")] - Image, - #[serde(rename = "objectgroup")] - ObjectGroup, - #[serde(rename = "group")] - Group, -} - -#[derive(Debug, Deserialize)] -pub struct Layer { - name: String, - id: i32, - #[serde(rename = "type")] ty: LayerType, - width: i32, height: i32, - x: i32, y: i32, - opacity: f32, - visible: bool, - data: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct LevelTileset { - firstgid: i32, - source: String, -} - -#[derive(Debug, Deserialize)] -pub struct Level { - width: i32, height: i32, - tilewidth: i32, tileheight: i32, - layers: Vec, - tilesets: Vec, -} -impl Level { - pub fn new(bytes: &str) -> Erm { - Ok(serde_json::from_str(bytes)?) - } -} - -#[derive(Debug, Deserialize)] -pub struct Tileset { - name: String, - imagewidth: i32, imageheight: i32, - tilewidth: i32, tileheight: i32, - margin: i32, spacing: i32, -} -impl Tileset { - pub fn new(bytes: &str) -> Erm { - Ok(serde_json::from_str(bytes)?) - } -} - -// TODO -pub enum Flip { - None, -} - -pub struct Asset { - tileset: Tileset, - texture: texture::Texture, -} -pub struct Assets { - entries: HashMap, -} -impl Assets { - pub fn new() -> Self { Self { entries: HashMap::new() } } - pub fn load(&mut self, ctx: &context::Context, nm: &str, ts: &str, img: &[u8]) -> Erm<()> { - let ass = Asset { - tileset: Tileset::new(ts)?, - texture: texture::Texture::new(ctx, img), - }; - if self.entries.insert(nm.to_string(), ass).is_some() { - log::warn!("duplicate tileset entry named: {}", nm); - } - Ok(()) - } - pub fn lookup_gid(&self, level: &Level, gid: u32) -> Erm<(i32, &Asset, Flip)> { - let offset = (gid & 0x0fffffff) as i32; - for lts in level.tilesets.iter().rev() { - if lts.firstgid <= offset { - return Ok(( - offset - lts.firstgid, - self.entries.get(<s.source).ok_or(Err::AssetNotFound(lts.source.clone()))?, - Flip::None - )) - } - } - return erm(Err::GIDNotFound(gid)); - } -} - -pub struct LayerRenderer { - pub vao: glow::VertexArray, - pub vertex_buf: glow::Buffer, - pub texcoords_buf: glow::Buffer, - pub index_buf: glow::Buffer, - pub index_count: usize, -} -impl LayerRenderer { - pub fn new(ctx: &context::Context) -> Erm { - unsafe { - let vao = ctx.gl.create_vertex_array().map_err(Err::GL)?; - ctx.gl.bind_vertex_array(Some(vao)); - let vertex_buf = ctx.gl.create_buffer().map_err(Err::GL)?; - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertex_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 2, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); - let texcoords_buf = ctx.gl.create_buffer().map_err(Err::GL)?; - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_buf)); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); - let index_buf = ctx.gl.create_buffer().map_err(Err::GL)?; - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(index_buf)); - Ok(Self { - vao, - vertex_buf, - texcoords_buf, - index_buf, - index_count: 0, - }) - } - } -} -pub struct LevelRenderer { - pub layers: Vec, - pub shader: shader::Shader, -} -impl LevelRenderer { - pub fn new(ctx: &context::Context, level: &Level) -> Erm { - let mut layers = Vec::new(); - for _ in level.layers.iter() { - layers.push(LayerRenderer::new(ctx)?); - } - let shader = shader::Shader::new_nolib( - &ctx, - include_str!("../assets/shaders/tiled/vert.glsl"), - include_str!("../assets/shaders/tiled/frag.glsl"), - ); - Ok(Self { - layers, - shader, - }) - } - pub fn populate_layer( - &mut self, - ctx: &context::Context, - assets: &Assets, - level: &Level, - lidx: usize - ) -> Erm<()> { - let lr = self.layers.get_mut(lidx).ok_or(Err::LayerIndexOutOfBounds)?; - let layer = level.layers.get(lidx).ok_or(Err::LayerIndexOutOfBounds)?; - let mut vertices = Vec::new(); - let mut texcoords = Vec::new(); - let mut indices = Vec::new(); - for y in 0..layer.height { - for x in 0..layer.width { - let idx = x as usize + (y * layer.width) as usize; - let gid = *layer.data.get(idx).ok_or(Err::LayerDataTooSmall)?; - if gid == 0 { continue; } - let (lid, ass, _) = assets.lookup_gid(level, gid)?; - let cols = ass.tileset.imagewidth / ass.tileset.tilewidth; - let rows = ass.tileset.imageheight / ass.tileset.tileheight; - let col = lid % cols; - let row = lid / cols; - let twidth = 1.0 / cols as f32; - let theight = 1.0 / rows as f32; - - let i = vertices.len() as u32; - let v = glam::Vec2::new(x as _, y as _); - vertices.push(v); - vertices.push(v + glam::Vec2::new(1.0, 0.0)); - vertices.push(v + glam::Vec2::new(1.0, 1.0)); - vertices.push(v + glam::Vec2::new(0.0, 1.0)); - let uvbase = glam::Vec2::new(col as f32 / cols as f32, row as f32 / rows as f32); - texcoords.push(uvbase + glam::Vec2::new(0.0, theight)); - texcoords.push(uvbase + glam::Vec2::new(twidth, theight)); - texcoords.push(uvbase + glam::Vec2::new(twidth, 0.0)); - texcoords.push(uvbase); - indices.push(i + 0); indices.push(i + 1); indices.push(i + 2); - indices.push(i + 0); indices.push(i + 3); indices.push(i + 2); - } - } - let index_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); - unsafe { - ctx.gl.bind_vertex_array(Some(lr.vao)); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(lr.vertex_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - vertices.as_ptr() as _, - vertices.len() * std::mem::size_of::() * 2, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(lr.texcoords_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - texcoords.as_ptr() as _, - texcoords.len() * std::mem::size_of::() * 2, - ), - glow::STATIC_DRAW, - ); - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(lr.index_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - &index_bytes, - glow::STATIC_DRAW, - ); - lr.index_count = indices.len(); - } - Ok(()) - } - pub fn render_layer( - &self, - ctx: &context::Context, - assets: &Assets, - level: &Level, - lidx: usize, - ) -> Erm<()> { - let layer = level.layers.get(lidx).ok_or(Err::LayerIndexOutOfBounds)?; - let lr = self.layers.get(lidx).ok_or(Err::LayerIndexOutOfBounds)?; - // TODO: handle layers with multiple textures - let gid = *layer.data.iter().find(|g| **g > 0).unwrap(); - let (_, ass, _) = assets.lookup_gid(level, gid)?; - ass.texture.bind(ctx); - unsafe { - ctx.gl.bind_vertex_array(Some(lr.vao)); - ctx.gl.draw_elements(glow::TRIANGLES, lr.index_count as _, glow::UNSIGNED_INT, 0); - } - Ok(()) - } - pub fn populate( - &mut self, - ctx: &context::Context, - assets: &Assets, - level: &Level, - ) -> Erm<()> { - for lidx in 0..level.layers.len() { - self.populate_layer(ctx, assets, level, lidx)?; - } - Ok(()) - } - pub fn render( - &self, - ctx: &context::Context, - assets: &Assets, - level: &Level, - ) -> Erm<()> { - self.shader.bind(ctx); - let sx = 2.0 * level.tilewidth as f32 / ctx.render_width; - let sy = 2.0 * level.tileheight as f32 / ctx.render_height; - self.shader.set_mat4( - ctx, "transform", - &glam::Mat4::from_scale_rotation_translation( - glam::Vec3::new(sx, -sy, 1.0), - glam::Quat::IDENTITY, - glam::Vec3::new(0.0, 0.0, 0.0), - ), - ); - for lidx in 0..level.layers.len() { - self.render_layer(ctx, assets, level, lidx)?; - } - Ok(()) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 916a405..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,328 +0,0 @@ -pub mod utils; -pub mod ui; -pub mod context; -pub mod state; -pub mod framebuffer; -pub mod shader; -pub mod mesh; -pub mod texture; -pub mod scene; -pub mod font; -pub mod shadow; -pub mod audio; -pub mod net; -pub mod physics; -pub mod save; -pub mod level2d; - -pub use utils::{erm, install_error_handler, Erm}; -pub use color_eyre::eyre::WrapErr; - -#[cfg(target_arch = "wasm32")] -use winit::platform::web::EventLoopExtWebSys; - -#[cfg(target_arch = "wasm32")] -use winit::platform::web::WindowExtWebSys; - -#[cfg(target_arch = "wasm32")] -use wasm_bindgen::JsCast; - -#[cfg(not(target_arch = "wasm32"))] -use glfw::Context; - -use bitflags::bitflags; -bitflags! { - pub struct Options: u32 { - const OVERLAY = 0b00000001; - const HIDDEN = 0b00000010; - const NORESIZE = 0b00000100; - } -} - -static mut CTX: Option<*const context::Context> = None; -static mut ST: Option<*mut state::State> = None; -static mut G: Option<*mut std::ffi::c_void> = None; - -pub fn contextualize(mut f: F) -> X -where - G: state::Game + 'static, - F: FnMut(&context::Context, &mut state::State, &mut G) -> X { - unsafe { - match (CTX, ST, G) { - (Some(c), Some(s), Some(g)) => f(&*c, &mut*s, &mut*(g as *mut G)), - _ => panic!("context not set"), - } - } -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn run<'a, F, G>(title: &str, w: u32, h: u32, options: Options, gnew: F) -> Erm<()> -where - G: state::Game + 'static, - F: (Fn(&'a context::Context) -> G), -{ - env_logger::Builder::new() - .filter(None, log::LevelFilter::Info) - .init(); - install_error_handler(); - - log::info!("hello computer, starting up..."); - - let resize = !options.contains(Options::NORESIZE); - let (rglfw, rwindow, gl, events) = { - use glfw::fail_on_errors; - let mut glfw = glfw::init(glfw::fail_on_errors!()).expect("failed to initialize GLFW"); - // let gl_attr = video.gl_attr(); - // gl_attr.set_context_profile(sdl2::video::GLProfile::Core); - // gl_attr.set_context_version(3, 0); - let (mut window, events) = glfw.with_primary_monitor(|glfw, primary| { - if options.contains(Options::HIDDEN) { - glfw.window_hint(glfw::WindowHint::Visible(false)); - glfw.create_window(w as _, h as _, title, glfw::WindowMode::Windowed) - .expect("failed to create window") - } else if options.contains(Options::OVERLAY) { - let mon = primary.expect("failed to get monitor"); - let mode = mon.get_video_mode().expect("failed to get video mode"); - glfw.window_hint(glfw::WindowHint::RedBits(Some(mode.red_bits))); - glfw.window_hint(glfw::WindowHint::GreenBits(Some(mode.green_bits))); - glfw.window_hint(glfw::WindowHint::BlueBits(Some(mode.blue_bits))); - glfw.window_hint(glfw::WindowHint::RefreshRate(Some(mode.refresh_rate))); - glfw.window_hint(glfw::WindowHint::Resizable(false)); - glfw.window_hint(glfw::WindowHint::Decorated(false)); - glfw.window_hint(glfw::WindowHint::Floating(true)); - glfw.window_hint(glfw::WindowHint::TransparentFramebuffer(true)); - unsafe { - // glfw.window_hint(glfw::WindowHint::MousePassthrough(true)); - glfw::ffi::glfwWindowHint(0x0002000D, 1); // mouse passthrough - } - glfw.create_window(mode.width, mode.height, title, glfw::WindowMode::FullScreen(mon)) - .expect("failed to create window") - } else { - glfw.create_window(w as _, h as _, title, glfw::WindowMode::Windowed) - .expect("failed to create window") - } - }); - window.make_current(); - window.set_key_polling(true); - window.set_mouse_button_polling(true); - window.set_size_polling(true); - window.set_focus_polling(true); - window.set_cursor_pos_polling(true); - let gl = unsafe { - glow::Context::from_loader_function(|s| window.get_proc_address(s) as *const _) - }; - glfw.set_swap_interval(glfw::SwapInterval::Sync(1)); - (glfw, window, gl, events) - }; - let glfw = std::cell::RefCell::new(rglfw); - let window = std::cell::RefCell::new(rwindow); - - let ctx = Box::leak(Box::new(context::Context::new( - glfw, window, gl, - w as f32, h as f32, resize, - ))); - let game = Box::leak(Box::new(gnew(ctx))); - let st = Box::leak(Box::new(state::State::new(&ctx))); - - unsafe { - CTX = Some(ctx as _); - ST = Some(st as _); - G = Some(game as *mut G as *mut std::ffi::c_void); - } - - game.initialize(ctx, st)?; - 'running: loop { - if ctx.window.borrow().should_close() { - game.finalize(ctx, st)?; - log::info!("bye!"); - break 'running; - } - ctx.glfw.borrow_mut().poll_events(); - for (_, event) in glfw::flush_messages(&events) { - match event { - glfw::WindowEvent::Size(_, _) => st.handle_resize(&ctx), - glfw::WindowEvent::Focus(false) => { - st.keys = state::Keys::new(); - }, - glfw::WindowEvent::CursorPos(x, y) => { - st.mouse_moved(&ctx, x as f32, y as f32, game); - } - glfw::WindowEvent::MouseButton(_, glfw::Action::Press, _) => { - st.mouse_pressed(&ctx, game) - }, - glfw::WindowEvent::MouseButton(_, glfw::Action::Release, _) => { - st.mouse_released(&ctx) - }, - glfw::WindowEvent::Key(key, _, glfw::Action::Press, _) => { - st.key_pressed(&ctx, state::Keycode::new(key)) - }, - glfw::WindowEvent::Key(key, _, glfw::Action::Release, _) => { - st.key_released(&ctx, state::Keycode::new(key)) - }, - _ => {}, - } - } - if ctx.resize_necessary() { - st.handle_resize(&ctx); - } - if let Some(f) = &mut st.request { - match std::future::Future::poll(f.as_mut(), &mut st.waker_ctx) { - std::task::Poll::Pending => {}, - std::task::Poll::Ready(res) => { - st.request = None; - match res { - Ok(r) => st.request_returned(&ctx, game, r), - Err(e) => log::warn!("error during HTTP request: {}", e), - } - }, - } - } - st.run_update(&ctx, game)?; - st.run_render(&ctx, game)?; - ctx.window.borrow_mut().swap_buffers(); - } - Ok(()) -} - -#[cfg(target_arch = "wasm32")] -pub fn run<'a, F, G>(w: u32, h: u32, options: Options, gnew: F) -where - G: state::Game + 'static, - F: (Fn(&'a context::Context) -> G), -{ - console_log::init_with_level(log::Level::Debug).unwrap(); - console_error_panic_hook::set_once(); - tracing_wasm::set_as_global_default(); - // install_error_handler(); - - log::info!("hello computer, starting up..."); - - let event_loop = winit::event_loop::EventLoop::new() - .expect("failed to initialize event loop"); - - let resize = !options.contains(Options::NORESIZE); - let (window, gl) = { - let window = winit::window::WindowBuilder::new() - .with_maximized(resize) - .with_decorations(false) - .build(&event_loop) - .expect("failed to initialize window"); - let gl = web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - let dst = doc.get_element_by_id("teleia-parent")?; - let canvas = web_sys::Element::from(window.canvas().expect("failed to find canvas")); - dst.append_child(&canvas).ok()?; - let c = canvas.dyn_into::().ok()?; - let webgl2_context = c.get_context("webgl2").ok()?? - .dyn_into::().ok()?; - Some(glow::Context::from_webgl2_context(webgl2_context)) - }) - .expect("couldn't add canvas to document"); - (window, gl) - }; - - let ctx = Box::leak(Box::new(context::Context::new(window, gl, w as f32, h as f32, resize))); - ctx.maximize_canvas(); - let game = Box::leak(Box::new(gnew(ctx))); - let st = Box::leak(Box::new(state::State::new(&ctx))); - - unsafe { - CTX = Some(ctx as _); - ST = Some(st as _); - G = Some(game as *mut G as *mut std::ffi::c_void); - } - - let _ = game.initialize(ctx, st); - let res = std::rc::Rc::new(std::cell::RefCell::new(Ok(()))); - let result = res.clone(); - event_loop.set_control_flow(winit::event_loop::ControlFlow::Wait); - event_loop.spawn(move |event, elwt| { - let res: Erm<()> = contextualize(|ctx, st, game: &mut G| { - match &event { - winit::event::Event::WindowEvent { - event: wev, - window_id, - .. - } => match wev { - winit::event::WindowEvent::CloseRequested - if *window_id == ctx.window.id() => elwt.exit(), - winit::event::WindowEvent::Resized{..} => { - #[cfg(target_arch = "wasm32")] - ctx.maximize_canvas(); - st.handle_resize(&ctx); - }, - winit::event::WindowEvent::Focused(false) => { - st.keys = state::Keys::new(); - }, - winit::event::WindowEvent::CursorMoved { position, ..} => { - st.mouse_moved(&ctx, position.x as f32, position.y as f32, game); - }, - winit::event::WindowEvent::MouseInput { - state, - .. - } => match state { - winit::event::ElementState::Pressed => { - st.mouse_pressed(&ctx, game) - }, - winit::event::ElementState::Released => { - st.mouse_released(&ctx) - }, - } - winit::event::WindowEvent::KeyboardInput { - event: winit::event::KeyEvent { - physical_key: winit::keyboard::PhysicalKey::Code(key), - state, - repeat: false, - .. - }, - .. - } => match state { - winit::event::ElementState::Pressed => { - st.key_pressed(&ctx, *key) - }, - winit::event::ElementState::Released => { - st.key_released(&ctx, *key) - }, - } - _ => {}, - }, - - winit::event::Event::AboutToWait => { - if ctx.resize_necessary() { - #[cfg(target_arch = "wasm32")] - ctx.maximize_canvas(); - st.handle_resize(&ctx); - } - if let Some(f) = &mut st.request { - match std::future::Future::poll(f.as_mut(), &mut st.waker_ctx) { - std::task::Poll::Pending => {}, - std::task::Poll::Ready(res) => { - st.request = None; - match res { - Ok(r) => st.request_returned(&ctx, game, r), - Err(e) => log::warn!("error during HTTP request: {}", e), - } - }, - } - // f.poll(); - } - st.run_update(&ctx, game)?; - st.run_render(&ctx, game)?; - ctx.window.request_redraw(); - }, - - _ => {}, - } - Ok(()) - }); - if let Err(e) = res { - *result.borrow_mut() = Err(e); - elwt.exit(); - } - }); - let _ = game.finalize(ctx, st); - if let Err(e) = res.replace(Ok(())) { - panic!("{}", e); - } -} diff --git a/src/mesh.rs b/src/mesh.rs deleted file mode 100644 index 7209de1..0000000 --- a/src/mesh.rs +++ /dev/null @@ -1,132 +0,0 @@ -use glow::HasContext; - -use crate::context; - -pub const ATTRIB_VERTEX: u32 = 0; -pub const ATTRIB_NORMAL: u32 = 1; -pub const ATTRIB_TEXCOORD: u32 = 2; -pub const ATTRIB_JOINT: u32 = 3; -pub const ATTRIB_WEIGHT: u32 = 4; -pub const ATTRIB_COLOR: u32 = 5; - -pub struct Mesh { - pub vao: glow::VertexArray, - pub mode: u32, // glow::TRIANGLES, etc. - pub index_count: usize, - pub index_type: u32, // glow::BYTE, glow::FLOAT, etc. - pub index_offset: i32, -} - -impl Mesh { - pub fn build( - ctx: &context::Context, - vertices: &Vec, - indices: &Vec, - snormals: &Option>, - stexcoords: &Option>, - ) -> Self { - unsafe { - let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); - ctx.gl.bind_vertex_array(Some(vao)); - - let buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - vertices.as_ptr() as _, - vertices.len() * std::mem::size_of::(), - ), - glow::STATIC_DRAW, - ); - ctx.gl.vertex_attrib_pointer_f32(ATTRIB_VERTEX, 3, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(ATTRIB_VERTEX); - - let indices_vbo = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(indices_vbo)); - ctx.gl.buffer_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - std::slice::from_raw_parts( - indices.as_ptr() as _, - indices.len() * std::mem::size_of::(), - ), - glow::STATIC_DRAW, - ); - - if let Some(normals) = snormals { - let normals_vbo = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(normals_vbo)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - normals.as_ptr() as _, - normals.len() * std::mem::size_of::(), - ), - glow::STATIC_DRAW, - ); - ctx.gl.vertex_attrib_pointer_f32(ATTRIB_NORMAL, 3, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(ATTRIB_NORMAL); - } - - if let Some(texcoords) = stexcoords { - let texcoords_vbo = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(texcoords_vbo)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - texcoords.as_ptr() as _, - texcoords.len() * std::mem::size_of::(), - ), - glow::STATIC_DRAW, - ); - ctx.gl.vertex_attrib_pointer_f32(ATTRIB_TEXCOORD, 2, glow::FLOAT, false, 0, 0); - ctx.gl.enable_vertex_attrib_array(ATTRIB_TEXCOORD); - } - - Self { - vao, - mode: glow::TRIANGLES, - index_count: indices.len(), - index_type: glow::UNSIGNED_INT, - index_offset: 0, - } - } - } - - pub fn from_obj(ctx: &context::Context, mut bytes: &[u8]) -> Self { - let lopts = tobj::LoadOptions { - triangulate: true, - single_index: true, - ..Default::default() - }; - let (meshes, _materials) = tobj::load_obj_buf( - &mut bytes, - &lopts, - |_| Err(tobj::LoadError::GenericFailure) - ).expect("failed to load mesh"); - let mesh = meshes.into_iter().next() - .expect("failed to load mesh") - .mesh; - Self::build( - ctx, - &mesh.positions, - &mesh.indices, - &Some(mesh.normals), - &Some(mesh.texcoords), - ) - } - - pub fn render(&self, ctx: &context::Context) { - unsafe { - ctx.gl.bind_vertex_array(Some(self.vao)); - ctx.gl.draw_elements(self.mode, self.index_count as _, self.index_type, self.index_offset); - } - } - - pub fn render_instanced(&self, ctx: &context::Context, count: u64) { - unsafe { - ctx.gl.bind_vertex_array(Some(self.vao)); - ctx.gl.draw_elements_instanced(self.mode, self.index_count as _, self.index_type, self.index_offset, count as _); - } - } -} diff --git a/src/net.rs b/src/net.rs deleted file mode 100644 index b9babe5..0000000 --- a/src/net.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod client; diff --git a/src/net/client.rs b/src/net/client.rs deleted file mode 100644 index fcd51b8..0000000 --- a/src/net/client.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(target_arch = "wasm32")] -pub mod wasm; diff --git a/src/net/client/wasm.rs b/src/net/client/wasm.rs deleted file mode 100644 index d9936e5..0000000 --- a/src/net/client/wasm.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{collections::VecDeque, sync::{Arc, Mutex}}; - -use wasm_bindgen::prelude::*; - -#[derive(Debug)] -pub enum Message { - Binary(Vec), - Text(String), -} - -impl Message { - pub fn from_messageevent(e: web_sys::MessageEvent) -> Self { - if let Ok(abuf) = e.data().dyn_into::() { - let array = js_sys::Uint8Array::new(&abuf).to_vec(); - Message::Binary(array) - } else if let Ok(txt) = e.data().dyn_into::() { - Message::Text(txt.into()) - } else { - panic!("received weird websocked message: {:?}", e); - } - } -} - -pub struct Client { - pub ws: Option, - pub messages: Arc>>, -} - -impl Client { - pub fn new() -> Self { - Self { - ws: None, - messages: Arc::new(Mutex::new(VecDeque::new())), - } - } - pub fn connect(&mut self, url: &str) { - let ws = web_sys::WebSocket::new(url).expect("failed to open websocket"); - let messages_ref = self.messages.clone(); - let cb: Closure = Closure::new(move |e: web_sys::MessageEvent| { - let msg = Message::from_messageevent(e); - log::info!("incoming: {:?}", msg); - messages_ref.lock().unwrap().push_back(msg); - }); - ws.set_onmessage(Some(cb.as_ref().unchecked_ref())); - cb.forget(); - self.ws = Some(ws); - } - pub fn poll(&mut self) -> Option { - self.messages.lock().unwrap().pop_front() - } - pub fn send(&self, msg: Message) { - if let Some(ws) = &self.ws { - match msg { - Message::Text(txt) => { - if let Err(e) = ws.send_with_str(&txt) { - log::warn!("failed to send string: {:?}", e); - } - }, - Message::Binary(bytes) => { - if let Err(e) = ws.send_with_u8_array(&bytes) { - log::warn!("failed to send bytes: {:?}", e); - } - }, - } - } - } -} diff --git a/src/physics.rs b/src/physics.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/save.rs b/src/save.rs deleted file mode 100644 index 3f244cc..0000000 --- a/src/save.rs +++ /dev/null @@ -1,43 +0,0 @@ -#[cfg(target_arch = "wasm32")] -pub fn save(id: &str, data: &W) where W: serde::Serialize { - let window = web_sys::window().expect("failed to get window object"); - let storage = window.local_storage() - .expect("failed to get local storage") - .expect("local storage not present"); - let key = format!("{}_save", id); - let val = serde_json::to_string(data).expect("failed to serialize save"); - storage.set_item(&key, &val).expect("failed to set save"); -} - -#[cfg(target_arch = "wasm32")] -pub fn load(id: &str) -> Option where W: serde::de::DeserializeOwned { - let window = web_sys::window().expect("failed to get window object"); - let storage = window.local_storage() - .expect("failed to get local storage") - .expect("local storage not present"); - let key = format!("{}_save", id); - let s = storage.get_item(&key).expect("failed to get save").expect("save not present"); - let mut cur = std::io::Cursor::new(s); - serde_json::from_reader(&mut cur).ok() -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn save(id: &str, data: &W) where W: serde::Serialize { - let pd = directories::ProjectDirs::from("", "milkfat", id).expect("failed to get save directory"); - let _ = std::fs::create_dir_all(pd.data_dir()); - let path = pd.data_dir().join("teleia.save"); - let mut file = std::fs::File::create(&path).expect("failed to open save file"); - // serde_json::to_writer(file, data).expect("failed to write save file"); - bincode::serde::encode_into_std_write(data, &mut file, bincode::config::standard()) - .expect("failed to write save file"); -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn load(id: &str) -> Option where W: serde::de::DeserializeOwned { - let pd = directories::ProjectDirs::from("", "milkfat", id).expect("failed to get save directory"); - let _ = std::fs::create_dir_all(pd.data_dir()); - let path = pd.data_dir().join("teleia.save"); - let mut file = std::fs::File::open(&path).ok()?; - // serde_json::from_reader(file).ok() - bincode::serde::decode_from_std_read(&mut file, bincode::config::standard()).ok() -} diff --git a/src/scene.rs b/src/scene.rs deleted file mode 100644 index 3146b5d..0000000 --- a/src/scene.rs +++ /dev/null @@ -1,398 +0,0 @@ -use std::{collections::{HashMap, VecDeque}, mem::offset_of}; - -use glow::HasContext; -use image::EncodableLayout; - -use crate::{context, mesh, shader, texture}; - -pub type Index = usize; - -pub struct Primitive { - pub mesh: mesh::Mesh, - pub material: Index, -} - -pub struct Object { - pub primitives: Vec, -} - -pub struct Material { - pub base_color_factor: glam::Vec4, - pub base_color_texture: Option, - - pub metallic_factor: f32, - pub roughness_factor: f32, - pub metallic_roughness_texture: Option, - - pub normal_texture: Option, - - pub occlusion_texture: Option, - - pub emissive_factor: glam::Vec3, - pub emissive_texture: Option, -} - -pub struct Skin { - pub inverse_bind_matrices: Vec, - pub joints: Vec, -} - -pub enum ChannelValues { - Translation(Vec), - Rotation(Vec), - Scale(Vec), -} - -pub enum Interpolation { - Linear, - Step, - CubicSpline, -} - -pub struct Channel { - pub target: Index, - pub interpolation: Interpolation, - pub keyframes: Vec, - pub values: ChannelValues, -} - -pub struct Animation { - pub channels: Vec, -} - -pub struct Node { - pub children: Vec, - pub object: Option, - pub skin: Option, - pub transform: glam::Mat4, -} - -pub struct Scene { - pub objects: Vec, - pub textures: Vec, - pub materials: Vec, - pub skins: Vec, - pub animations: HashMap, - pub nodes: Vec, - pub nodes_by_name: HashMap, - pub scene_nodes: Vec, -} - -impl Scene { - pub fn load_default_shader(ctx: &context::Context) -> shader::Shader { - shader::Shader::new( - ctx, - include_str!("assets/shaders/scene/vert.glsl"), - include_str!("assets/shaders/scene/frag.glsl") - ) - } - pub fn from_gltf(ctx: &context::Context, bytes: &[u8]) -> Self { - let (gltf, buffers, images) = gltf::import_slice(bytes).expect("failed to parse GLTF"); - let get_buffer_data = |b: gltf::Buffer| { - buffers.get(b.index()).map(|gltf::buffer::Data(bytes)| bytes.as_slice()) - }; - let objects = gltf.meshes().map(|m| { - let primitives = m.primitives().filter_map(|p| { - let mode = match p.mode() { - gltf::mesh::Mode::Points => glow::POINTS, - gltf::mesh::Mode::Lines => glow::LINES, - gltf::mesh::Mode::LineLoop => glow::LINE_LOOP, - gltf::mesh::Mode::LineStrip => glow::LINE_STRIP, - gltf::mesh::Mode::Triangles => glow::TRIANGLES, - gltf::mesh::Mode::TriangleStrip => glow::TRIANGLE_STRIP, - gltf::mesh::Mode::TriangleFan => glow::TRIANGLE_FAN, - }; - unsafe { - let vao = ctx.gl.create_vertex_array().expect("failed to initialize vao"); - ctx.gl.bind_vertex_array(Some(vao)); - - // in the past, I've been lazy and just uploaded whole buffers to the GPU. - // this is certainly not the right thing to do in general. - // perhaps I am misunderstanding, but it feels like GLTF makes it pretty difficult to do - // that in general, on account of things like sparse accessors. - // instead, we'll use the gltf crate's handy "reader" abstraction to iterate over all of the - // data in the buffers, assemble it ourselves, and then upload that. - let reader = p.reader(get_buffer_data); - - // on to the actual vertex data. - // this is the layout of a single vertex in the buffer we send to the GPU. - struct Vertex { - pos: glam::Vec3, - normal: glam::Vec3, - texcoord: glam::Vec2, - joints: glam::Vec4, - weights: glam::Vec4, - } - - // vertices always have positions - let mut vertices = Vec::new(); - for pos in reader.read_positions().expect("primitive has no positions") { - vertices.push(Vertex { - pos: glam::Vec3::from_array(pos), - normal: glam::Vec3::default(), - texcoord: glam::Vec2::default(), - joints: glam::Vec4::default(), - weights: glam::Vec4::default(), - }); - } - - // if we find indices, use those. otherwise generate indices - let indices: Vec = if let Some(ri) = reader.read_indices() { - ri.into_u32().collect() - } else { - vertices.iter().enumerate().map(|(i, _)| i as u32).collect() - }; - let indices_bytes: Vec = indices.iter().flat_map(|x| x.to_ne_bytes()).collect(); - let indices_buf = ctx.gl.create_buffer().expect("failed to create index buffer object"); - ctx.gl.bind_buffer(glow::ELEMENT_ARRAY_BUFFER, Some(indices_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ELEMENT_ARRAY_BUFFER, - &indices_bytes, - glow::STATIC_DRAW, - ); - - // optionally, we might have some other vertex attributes too - if let Some(iter) = reader.read_normals() { - for (i, n) in iter.enumerate() { - vertices[i].normal = glam::Vec3::from_array(n) - } - } - if let Some(iter) = reader.read_tex_coords(0) { - for (i, uv) in iter.into_f32().enumerate() { - vertices[i].texcoord = glam::Vec2::from_array(uv) - } - } - if let Some(iter) = reader.read_joints(0) { - for (i, j) in iter.into_u16().enumerate() { - vertices[i].joints = glam::Vec4::from_slice(&j.into_iter().map(|x| x as f32).collect::>()) - } - } - if let Some(iter) = reader.read_weights(0) { - for (i, w) in iter.into_f32().enumerate() { - vertices[i].weights = glam::Vec4::from_array(w) - } - } - - let vertex_size = std::mem::size_of::() as i32; - let vertices_buf = ctx.gl.create_buffer().expect("failed to create buffer object"); - ctx.gl.bind_buffer(glow::ARRAY_BUFFER, Some(vertices_buf)); - ctx.gl.buffer_data_u8_slice( - glow::ARRAY_BUFFER, - std::slice::from_raw_parts( - vertices.as_ptr() as _, - vertices.len() * (vertex_size as usize), - ), - glow::STATIC_DRAW, - ); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_VERTEX); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_VERTEX, 3, glow::FLOAT, false, vertex_size, offset_of!(Vertex, pos) as _); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_NORMAL); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_NORMAL, 3, glow::FLOAT, false, vertex_size, offset_of!(Vertex, normal) as _); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_TEXCOORD); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_TEXCOORD, 2, glow::FLOAT, false, vertex_size, offset_of!(Vertex, texcoord) as _); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_JOINT); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_JOINT, 4, glow::FLOAT, false, vertex_size, offset_of!(Vertex, joints) as _); - ctx.gl.enable_vertex_attrib_array(mesh::ATTRIB_WEIGHT); - ctx.gl.vertex_attrib_pointer_f32(mesh::ATTRIB_WEIGHT, 4, glow::FLOAT, false, vertex_size, offset_of!(Vertex, weights) as _); - - - Some(Primitive { - mesh: mesh::Mesh { - vao, - mode, - index_count: indices.len(), - index_type: glow::UNSIGNED_INT, - index_offset: 0, - }, - material: p.material().index().unwrap(), - }) - } - }).collect(); - Object { - primitives, - } - }).collect(); - let textures: Vec = images.into_iter().map(|bi| { - unsafe { - let i = bi.image.into_rgba8(); - let tex = ctx.gl.create_texture().expect("failed to create texture"); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); - ctx.gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::RGBA as i32, - i.width() as i32, - i.height() as i32, - 0, - glow::RGBA, - glow::UNSIGNED_BYTE, - Some(&i.as_bytes()), - ); - ctx.gl.generate_mipmap(glow::TEXTURE_2D); - texture::Texture { tex } - } - }).collect(); - let materials: Vec = gltf.materials().map(|m| { - let pbr = m.pbr_metallic_roughness(); - let [bcr, bcg, bcb, bca] = pbr.base_color_factor(); - let [emx, emy, emz] = m.emissive_factor(); - Material { - base_color_factor: glam::Vec4::new(bcr, bcg, bcb, bca), - base_color_texture: pbr.base_color_texture().map(|tex| tex.texture().source().index()), - metallic_factor: pbr.metallic_factor(), - roughness_factor: pbr.roughness_factor(), - metallic_roughness_texture: pbr.metallic_roughness_texture().map(|tex| tex.texture().source().index()), - normal_texture: m.normal_texture().map(|tex| tex.texture().source().index()), - occlusion_texture: m.occlusion_texture().map(|tex| tex.texture().source().index()), - emissive_factor: glam::Vec3::new(emx, emy, emz), - emissive_texture: m.emissive_texture().map(|tex| tex.texture().source().index()), - } - }).collect(); - - let skins = gltf.skins().map(|s| { - let ibm = s.reader(get_buffer_data).read_inverse_bind_matrices() - .expect("missing read inverse bind matrices") - .map(|m| glam::Mat4::from_cols_array_2d(&m)).collect(); - Skin { - inverse_bind_matrices: ibm, - joints: s.joints().map(|j| j.index()).collect(), - } - }).collect(); - - let animations = HashMap::from_iter(gltf.animations().filter_map(|a| { - let channels = a.channels().map(|c| { - let read = c.reader(get_buffer_data); - Channel { - target: c.target().node().index(), - interpolation: match c.sampler().interpolation() { - gltf::animation::Interpolation::Linear => Interpolation::Linear, - gltf::animation::Interpolation::Step => Interpolation::Step, - gltf::animation::Interpolation::CubicSpline => Interpolation::CubicSpline, - }, - keyframes: read.read_inputs().expect("channel has no inputs").collect(), - values: match read.read_outputs().expect("channel has no outputs") { - gltf::animation::util::ReadOutputs::Translations(ts) => - ChannelValues::Translation(ts.map(glam::Vec3::from_array).collect()), - gltf::animation::util::ReadOutputs::Rotations(ts) => - ChannelValues::Rotation(ts.into_f32().map(glam::Quat::from_array).collect()), - gltf::animation::util::ReadOutputs::Scales(ts) => - ChannelValues::Scale(ts.map(glam::Vec3::from_array).collect()), - _ => panic!("unsupport channel outputs"), - }, - } - }).collect(); - a.name().map(|nm| (nm.to_owned(), Animation { channels })) - })); - - let nodes = gltf.nodes().map(|n| { - Node { - children: n.children().map(|c| c.index()).collect(), - object: n.mesh().map(|m| m.index()), - skin: n.skin().map(|s| s.index()), - transform: glam::Mat4::from_cols_array_2d(&n.transform().matrix()), - } - }).collect(); - - let mut nodes_by_name = HashMap::new(); - for n in gltf.nodes() { - if let Some(nm) = n.name() { - nodes_by_name.insert(nm.to_owned(), n.index()); - } - } - - let scene_nodes = gltf.default_scene().unwrap().nodes().map(|n| n.index()).collect(); - - Self { - objects, - textures, - materials, - skins, - animations, - nodes, - nodes_by_name, - scene_nodes, - } - } - - pub fn compute_joint_matrices(&self, skin: &Skin) -> Vec { - let mut q: VecDeque<(Index, glam::Mat4)> = VecDeque::new(); - q.push_back((skin.joints[0], glam::Mat4::IDENTITY)); - let mut transforms = vec![glam::Mat4::IDENTITY; self.nodes.len()]; - while let Some((ni, m)) = q.pop_front() { - let n = &self.nodes[ni]; - transforms[ni] = m.mul_mat4(&n.transform); - for ci in &n.children { - q.push_back((*ci, transforms[ni])); - } - } - let mut ret = vec![glam::Mat4::IDENTITY; skin.joints.len()]; - for (idx, ni) in skin.joints.iter().enumerate() { - ret[idx] = transforms[*ni].mul_mat4(&skin.inverse_bind_matrices[idx]); - } - ret - } - - fn render_node(&self, ctx: &context::Context, shader: &shader::Shader, n: &Node) { - if let Some(o) = n.object.and_then(|i| self.objects.get(i)) { - if let Some(s) = n.skin.and_then(|i| self.skins.get(i)) { - let jms = self.compute_joint_matrices(s); - shader.set_mat4_array(ctx, "joint_matrices[0]", &jms); - } - for p in &o.primitives { - if let Some(tex) = self.materials.get(p.material) - .and_then(|m| m.base_color_texture) - .and_then(|t| self.textures.get(t)) { - tex.bind(ctx); - } - p.mesh.render(ctx); - } - } - } - - pub fn render(&self, ctx: &context::Context, shader: &shader::Shader) { - let mut q: VecDeque = VecDeque::new(); - for sn in &self.scene_nodes { - q.push_back(*sn); - } - while let Some(ni) = q.pop_front() { - let n = &self.nodes[ni]; - self.render_node(ctx, shader, n); - for ci in &n.children { - q.push_back(*ci); - } - } - } - - pub fn reflect_animation(&mut self, nm: &str, time: f32) { - if let Some(anim) = self.animations.get(nm) { - for c in &anim.channels { - let (previ, nexti) = if let Some(nexti) = c.keyframes.iter().position(|x| *x > time) { - let previ = if nexti > 0 { nexti - 1 } else { c.keyframes.len() - 1 }; - (previ, nexti) - } else { - (c.keyframes.len() - 1, 0) - }; - match &c.values { - ChannelValues::Rotation(vs) => { - let prevt = c.keyframes[previ]; - let nextt = c.keyframes[nexti]; - let prev = vs[previ]; - let next = vs[nexti]; - let new = prev.slerp(next, (time - prevt) / (nextt - prevt)); - let (scale, _, trans) = self.nodes[c.target].transform.to_scale_rotation_translation(); - self.nodes[c.target].transform = glam::Mat4::from_scale_rotation_translation( - scale, - new, - trans, - ); - }, - _ => {}, - } - } - } - } -} diff --git a/src/shader.rs b/src/shader.rs deleted file mode 100644 index 178833a..0000000 --- a/src/shader.rs +++ /dev/null @@ -1,253 +0,0 @@ -use std::collections::HashMap; - -use glow::HasContext; - -use crate::{context, mesh}; - -const COMMON_VERT: &'static str = include_str!("assets/shaders/common/vert.glsl"); -const COMMON_FRAG: &'static str = include_str!("assets/shaders/common/frag.glsl"); - -#[derive(Clone)] -pub struct Shader { - pub program: glow::Program, - pub uniforms: std::rc::Rc> -} - -impl Shader { - pub fn new_helper(ctx: &context::Context, vsrc: &str, fsrc: &str) -> Result { - unsafe { - let program = ctx.gl.create_program()?; - - let vert = ctx.gl.create_shader(glow::VERTEX_SHADER)?; - ctx.gl.shader_source(vert, &vsrc); - ctx.gl.compile_shader(vert); - if !ctx.gl.get_shader_compile_status(vert) { - return Err(format!( - "failed to compile vertex shader:\n{}", - ctx.gl.get_shader_info_log(vert) - )); - } - ctx.gl.attach_shader(program, vert); - - let frag = ctx.gl.create_shader(glow::FRAGMENT_SHADER)?; - ctx.gl.shader_source(frag, &fsrc); - ctx.gl.compile_shader(frag); - if !ctx.gl.get_shader_compile_status(frag) { - return Err(format!( - "failed to compile fragment shader:\n{}", - ctx.gl.get_shader_info_log(frag) - )); - } - ctx.gl.attach_shader(program, frag); - - ctx.gl.bind_attrib_location(program, mesh::ATTRIB_VERTEX, "vertex"); - ctx.gl.bind_attrib_location(program, mesh::ATTRIB_NORMAL, "normal"); - ctx.gl.bind_attrib_location(program, mesh::ATTRIB_TEXCOORD, "texcoord"); - ctx.gl.bind_attrib_location(program, mesh::ATTRIB_JOINT, "joint"); - ctx.gl.bind_attrib_location(program, mesh::ATTRIB_WEIGHT, "weight"); - ctx.gl.bind_attrib_location(program, mesh::ATTRIB_COLOR, "color"); - - ctx.gl.link_program(program); - if !ctx.gl.get_program_link_status(program) { - return Err(format!( - "failed to link shader program:\n{}", - ctx.gl.get_program_info_log(program), - )); - } - - ctx.gl.detach_shader(program, vert); - ctx.gl.delete_shader(vert); - ctx.gl.detach_shader(program, frag); - ctx.gl.delete_shader(frag); - - ctx.gl.use_program(Some(program)); - - let mut uniforms = HashMap::new(); - for index in 0..ctx.gl.get_active_uniforms(program) { - if let Some(active) = ctx.gl.get_active_uniform(program, index) { - if let Some(loc) = ctx.gl.get_uniform_location(program, &active.name) { - uniforms.insert(active.name, loc); - } else { - log::warn!("failed to get location for uniform: {}", active.name); - } - } else { - log::warn!("failed to get active uniform for index: {}", index); - } - } - - Ok(Self { - program, - uniforms: std::rc::Rc::new(uniforms), - }) - } - } - - pub fn new_nolib(ctx: &context::Context, vsrc: &str, fsrc: &str) -> Self { - Self::new_helper(ctx, vsrc, fsrc).expect("failed to load shader") - } - - pub fn new(ctx: &context::Context, vsrcstr: &str, fsrcstr: &str) -> Self { - let vsrc = format!("{}\n{}\n", COMMON_VERT, vsrcstr); - let fsrc = format!("{}\n{}\n", COMMON_FRAG, fsrcstr); - Self::new_nolib(ctx, &vsrc, &fsrc) - } - - pub fn delete(&mut self, ctx: &context::Context) { - unsafe { ctx.gl.delete_program(self.program); } - } - - pub fn replace(&mut self, ctx: &context::Context, vsrc: &str, fsrc: &str) -> Result<(), String> { - self.delete(ctx); - *self = Self::new_helper(ctx, vsrc, fsrc)?; - Ok(()) - } - - pub fn set_i32(&self, ctx: &context::Context, name: &str, val: i32) { - if let Some(loc) = self.uniforms.get(name) { - unsafe { ctx.gl.uniform_1_i32(Some(loc), val) } - } - } - - pub fn set_i32_array(&self, ctx: &context::Context, name: &str, val: &[i32]) { - if let Some(loc) = self.uniforms.get(name) { - unsafe { - ctx.gl.uniform_1_i32_slice(Some(loc), val) - } - } - } - - pub fn set_f32(&self, ctx: &context::Context, name: &str, val: f32) { - if let Some(loc) = self.uniforms.get(name) { - unsafe { ctx.gl.uniform_1_f32(Some(loc), val) } - } - } - - pub fn set_vec2(&self, ctx: &context::Context, name: &str, val: &glam::Vec2) { - if let Some(loc) = self.uniforms.get(name) { - unsafe { - ctx.gl.uniform_2_f32( - Some(loc), - val.x, - val.y, - ); - } - } - } - - pub fn set_vec2_array(&self, ctx: &context::Context, name: &str, val: &[glam::Vec2]) { - if let Some(loc) = self.uniforms.get(name) { - let vs: Vec = val.iter().flat_map(|v| [v.x, v.y]).collect(); - unsafe { - ctx.gl.uniform_2_f32_slice( - Some(loc), - &vs, - ); - } - } - } - - pub fn set_vec3(&self, ctx: &context::Context, name: &str, val: &glam::Vec3) { - if let Some(loc) = self.uniforms.get(name) { - unsafe { - ctx.gl.uniform_3_f32( - Some(loc), - val.x, - val.y, - val.z, - ); - } - } - } - - pub fn set_vec3_array(&self, ctx: &context::Context, name: &str, val: &[glam::Vec3]) { - if let Some(loc) = self.uniforms.get(name) { - let vs: Vec = val.iter().flat_map(|v| [v.x, v.y, v.z]).collect(); - unsafe { - ctx.gl.uniform_3_f32_slice( - Some(loc), - &vs, - ); - } - } - } - - pub fn set_vec4(&self, ctx: &context::Context, name: &str, val: &glam::Vec4) { - if let Some(loc) = self.uniforms.get(name) { - unsafe { - ctx.gl.uniform_4_f32( - Some(loc), - val.x, - val.y, - val.z, - val.w, - ); - } - } - } - - pub fn set_mat4(&self, ctx: &context::Context, name: &str, val: &glam::Mat4) { - if let Some(loc) = self.uniforms.get(name) { - unsafe { - ctx.gl.uniform_matrix_4_f32_slice( - Some(loc), - false, - &val.to_cols_array(), - ); - } - } - } - - pub fn set_mat4_array(&self, ctx: &context::Context, name: &str, val: &[glam::Mat4]) { - if let Some(loc) = self.uniforms.get(name) { - let vs: Vec = val.iter().flat_map(|m| m.to_cols_array()).collect(); - unsafe { - ctx.gl.uniform_matrix_4_f32_slice( - Some(loc), - false, - &vs, - ); - } - } - } - - pub fn set_position_3d(&self, ctx: &context::Context, position: &glam::Mat4) { - self.set_mat4(&ctx, "position", &position); - self.set_mat4(&ctx, "normal_matrix", &position.inverse().transpose()); - } - - pub fn set_position_2d_helper(&self, ctx: &context::Context, pos: &glam::Vec2, dims: &glam::Vec2, rot: &glam::Quat) { - let halfwidth = dims.x / 2.0; - let halfheight = dims.y / 2.0; - self.set_mat4( - &ctx, "position", - &glam::Mat4::from_scale_rotation_translation( - glam::Vec3::new(halfwidth, halfheight, 1.0), - rot.clone(), - glam::Vec3::new( - -ctx.render_width / 2.0 + pos.x + halfwidth, - ctx.render_height / 2.0 - pos.y - halfheight, - 0.0, - ), - ) - ); - } - - pub fn set_position_2d(&self, ctx: &context::Context, pos: &glam::Vec2, dims: &glam::Vec2) { - self.set_position_2d_helper(ctx, pos, dims, &glam::Quat::IDENTITY) - } - - pub fn set_texture_offset(&self, ctx: &context::Context, inc: i32, x: i32, y: i32) { - let count = inc as f32; - let ratio = 1.0 / count; - self.set_vec3( - ctx, "texture_offset", - &glam::Vec3::new((x % inc) as f32 * ratio, (y % inc) as f32 * ratio, count) - ); - } - - pub fn bind(&self, ctx: &context::Context) { - unsafe { - ctx.gl.use_program(Some(self.program)); - } - } -} diff --git a/src/shadow.rs b/src/shadow.rs deleted file mode 100644 index f827967..0000000 --- a/src/shadow.rs +++ /dev/null @@ -1,127 +0,0 @@ -use glow::HasContext; - -use crate::context; - -pub struct ShadowBuffer { - pub fbo: glow::Framebuffer, - pub depth_tex: glow::Texture, - pub width: i32, - pub height: i32, -} - -impl ShadowBuffer { - pub fn new(ctx: &context::Context, w: i32, h: i32) -> Self { - unsafe { - // generate and bind FBO - let fbo = ctx.gl.create_framebuffer().expect("failed to create framebuffer"); - ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); - - // generate and attach depth buffer - let depth_tex = ctx.gl.create_texture().expect("failed to create texture"); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(depth_tex)); - ctx.gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::DEPTH_COMPONENT as i32, - w, - h, - 0, - glow::DEPTH_COMPONENT, - glow::FLOAT, - None, - ); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_BORDER as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_BORDER as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_COMPARE_MODE, glow::COMPARE_REF_TO_TEXTURE as i32); - ctx.gl.tex_parameter_f32_slice(glow::TEXTURE_2D, glow::TEXTURE_BORDER_COLOR, &[1.0, 1.0, 1.0, 1.0]); - ctx.gl.framebuffer_texture_2d(glow::FRAMEBUFFER, glow::DEPTH_ATTACHMENT, glow::TEXTURE_2D, Some(depth_tex), 0); - ctx.gl.draw_buffer(glow::NONE); - ctx.gl.read_buffer(glow::NONE); - - let status = ctx.gl.check_framebuffer_status(glow::FRAMEBUFFER); - if status != glow::FRAMEBUFFER_COMPLETE { - panic!("error initializing framebuffer: {}", status); - } - Self { - fbo, - depth_tex, - width: w, - height: h, - } - } - } - - pub fn bind(&self, ctx: &context::Context) { - unsafe { - ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); - ctx.gl.viewport(0, 0, self.width as _, self.height as _); - ctx.gl.clear(glow::DEPTH_BUFFER_BIT); - - } - } -} - -pub struct ShadowBuffer3D { - pub fbo: glow::Framebuffer, - pub depth_cubemap: glow::Texture, - pub width: i32, - pub height: i32, -} - -impl ShadowBuffer3D { - pub fn new(ctx: &context::Context, w: i32, h: i32) -> Self { - unsafe { - // generate and bind FBO - let fbo = ctx.gl.create_framebuffer().expect("failed to create framebuffer"); - ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(fbo)); - - // generate and attach depth buffer - let depth_cubemap = ctx.gl.create_texture().expect("failed to create texture"); - ctx.gl.bind_texture(glow::TEXTURE_CUBE_MAP, Some(depth_cubemap)); - for i in 0..6 { - ctx.gl.tex_image_2d( - glow::TEXTURE_CUBE_MAP_POSITIVE_X + i, - 0, - glow::DEPTH_COMPONENT as i32, - w, - h, - 0, - glow::DEPTH_COMPONENT, - glow::FLOAT, - None, - ); - ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_MIN_FILTER, glow::LINEAR as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_CUBE_MAP, glow::TEXTURE_WRAP_R, glow::CLAMP_TO_EDGE as i32); - } - - ctx.gl.framebuffer_texture(glow::FRAMEBUFFER, glow::DEPTH_ATTACHMENT, Some(depth_cubemap), 0); - ctx.gl.draw_buffer(glow::NONE); - ctx.gl.read_buffer(glow::NONE); - - let status = ctx.gl.check_framebuffer_status(glow::FRAMEBUFFER); - if status != glow::FRAMEBUFFER_COMPLETE { - panic!("error initializing framebuffer: {}", status); - } - Self { - fbo, - depth_cubemap, - width: w, - height: h, - } - } - } - - pub fn bind(&self, ctx: &context::Context) { - unsafe { - ctx.gl.bind_framebuffer(glow::FRAMEBUFFER, Some(self.fbo)); - ctx.gl.viewport(0, 0, self.width as _, self.height as _); - ctx.gl.clear(glow::DEPTH_BUFFER_BIT); - - } - } -} diff --git a/src/state.rs b/src/state.rs deleted file mode 100644 index 09f0f77..0000000 --- a/src/state.rs +++ /dev/null @@ -1,514 +0,0 @@ -#![allow(dead_code, unused_variables)] -use std::collections::HashMap; -use bimap::BiHashMap; -use enum_map::{enum_map, Enum, EnumMap}; -use serde::{Serialize, Deserialize}; - -use crate::{audio, context, framebuffer, shader, utils}; - -const DELTA_TIME: f64 = 1.0 / 60.0; - -pub struct WinitWaker {} -impl WinitWaker { - fn new() -> Self { Self {} } -} -impl std::task::Wake for WinitWaker { - fn wake(self: std::sync::Arc) {} -} - -pub struct Response { - pub url: String, - pub status: reqwest::StatusCode, - pub body: bytes::Bytes, -} - -pub trait Game { - fn initialize(&self, ctx: &context::Context, st: &State) -> utils::Erm<()> { Ok(()) } - fn finalize(&self, ctx: &context::Context, st: &State) -> utils::Erm<()> { Ok(()) } - fn initialize_audio(&self, ctx: &context::Context, st: &State, actx: &audio::Context) -> - HashMap - { - HashMap::new() - } - fn finish_title(&mut self, st: &mut State) {} - fn mouse_move(&mut self, ctx: &context::Context, st: &mut State, x: i32, y: i32) {} - fn mouse_press(&mut self, ctx: &context::Context, st: &mut State) {} - fn request_return(&mut self, ctx: &context::Context, st: &mut State, res: Response) {} - fn update(&mut self, ctx: &context::Context, st: &mut State) -> utils::Erm<()> { Ok(()) } - fn render(&mut self, ctx: &context::Context, st: &mut State) -> utils::Erm<()> { Ok(()) } -} - -#[derive(Debug, Enum, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum Key { - Up, Down, Left, Right, - A, B, L, R, - Start, Select, -} -pub const KEYS: [Key; 10] = [ - Key::Up, Key::Down, Key::Left, Key::Right, - Key::A, Key::B, Key::L, Key::R, - Key::Start, Key::Select, -]; -pub struct Keys { - pub pressed: EnumMap, - pub new: EnumMap, -} -impl Keys { - pub fn new() -> Self { - Self { - pressed: enum_map! { - Key::Up => false, Key::Down => false, Key::Left => false, Key::Right => false, - Key::A => false, Key::B => false, Key::L => false, Key::R => false, - Key::Start => false, Key::Select => false, - }, - new: enum_map! { - Key::Up => false, Key::Down => false, Key::Left => false, Key::Right => false, - Key::A => false, Key::B => false, Key::L => false, Key::R => false, - Key::Start => false, Key::Select => false, - }, - } - } - pub fn up(&self) -> bool { self.pressed[Key::Up] } - pub fn down(&self) -> bool { self.pressed[Key::Down] } - pub fn left(&self) -> bool { self.pressed[Key::Left] } - pub fn right(&self) -> bool { self.pressed[Key::Right] } - pub fn a(&self) -> bool { self.pressed[Key::A] } - pub fn b(&self) -> bool { self.pressed[Key::B] } - pub fn l(&self) -> bool { self.pressed[Key::L] } - pub fn r(&self) -> bool { self.pressed[Key::R] } - pub fn start(&self) -> bool { self.pressed[Key::Start] } - pub fn select(&self) -> bool { self.pressed[Key::Select] } - pub fn new_up(&mut self) -> bool { let ret = self.new[Key::Up]; self.new[Key::Up] = false; ret } - pub fn new_down(&mut self) -> bool { let ret = self.new[Key::Down]; self.new[Key::Down] = false; ret } - pub fn new_left(&mut self) -> bool { let ret = self.new[Key::Left]; self.new[Key::Left] = false; ret } - pub fn new_right(&mut self) -> bool { let ret = self.new[Key::Right]; self.new[Key::Right] = false; ret } - pub fn new_a(&mut self) -> bool { let ret = self.new[Key::A]; self.new[Key::A] = false; ret } - pub fn new_b(&mut self) -> bool { let ret = self.new[Key::B]; self.new[Key::B] = false; ret } - pub fn new_l(&mut self) -> bool { let ret = self.new[Key::L]; self.new[Key::L] = false; ret } - pub fn new_r(&mut self) -> bool { let ret = self.new[Key::R]; self.new[Key::R] = false; ret } - pub fn new_start(&mut self) -> bool { let ret = self.new[Key::Start]; self.new[Key::Start] = false; ret } - pub fn new_select(&mut self) -> bool { let ret = self.new[Key::Select]; self.new[Key::Select] = false; ret } -} - -pub struct PointLight { - pub pos: glam::Vec3, - pub color: glam::Vec3, - pub attenuation: glam::Vec2, -} - -type Timestamp = f64; - -#[cfg(target_arch = "wasm32")] -pub type Keycode = winit::keyboard::KeyCode; - -#[cfg(not(target_arch = "wasm32"))] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Keycode { - pub kc: glfw::Key -} -#[cfg(not(target_arch = "wasm32"))] -impl Keycode { - pub fn new(kc: glfw::Key) -> Self { Self { kc } } -} -#[cfg(not(target_arch = "wasm32"))] -impl Serialize for Keycode { - fn serialize(&self, serializer: S) -> Result - where S: serde::Serializer { - (self.kc as i32).serialize(serializer) - } -} -#[cfg(not(target_arch = "wasm32"))] -impl<'de> Deserialize<'de> for Keycode { - fn deserialize(deserializer: D) -> Result - where D: serde::Deserializer<'de> { - i32::deserialize(deserializer) - .map(|x| unsafe { - std::mem::transmute(x) - }) - } -} - -pub struct State { - pub acc: f64, - pub last: Timestamp, - pub tick: u64, - - pub rebinding: Option, - pub keybindings: BiHashMap, - pub keys: Keys, - - pub screen: framebuffer::Framebuffer, - pub render_framebuffer: framebuffer::Framebuffer, - pub shader_upscale: shader::Shader, - pub audio: Option, - - pub projection: glam::Mat4, - pub camera: (glam::Vec3, glam::Vec3, glam::Vec3), - pub lighting: (glam::Vec3, glam::Vec3, glam::Vec3), - pub point_lights: Vec, - - pub waker_ctx: std::task::Context<'static>, - pub http_client: reqwest::Client, - pub request: Option>>>>, - - pub log: Vec<(u64, String)>, -} - -#[cfg(target_arch = "wasm32")] -pub fn now(ctx: &context::Context) -> Timestamp { - ctx.performance.now() / 1000.0 -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn now(ctx: &context::Context) -> Timestamp { - let elapsed = ctx.start_instant.elapsed(); - let ms = elapsed.as_millis(); - (ms as f64) / 1000.0 -} - -#[cfg(target_arch = "wasm32")] -pub fn default_keybindings() -> BiHashMap { - BiHashMap::from_iter(vec![ - (winit::keyboard::KeyCode::KeyW, Key::Up), - (winit::keyboard::KeyCode::KeyS, Key::Down), - (winit::keyboard::KeyCode::KeyA, Key::Left), - (winit::keyboard::KeyCode::KeyD, Key::Right), - (winit::keyboard::KeyCode::Digit1, Key::A), - (winit::keyboard::KeyCode::Digit2, Key::B), - (winit::keyboard::KeyCode::KeyQ, Key::L), - (winit::keyboard::KeyCode::KeyE, Key::R), - (winit::keyboard::KeyCode::Tab, Key::Start), - (winit::keyboard::KeyCode::Space, Key::Select), - ]) -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn default_keybindings() -> BiHashMap { - BiHashMap::from_iter(vec![ - (Keycode::new(glfw::Key::W), Key::Up), - (Keycode::new(glfw::Key::S), Key::Down), - (Keycode::new(glfw::Key::A), Key::Left), - (Keycode::new(glfw::Key::D), Key::Right), - (Keycode::new(glfw::Key::Num1), Key::A), - (Keycode::new(glfw::Key::Num2), Key::B), - (Keycode::new(glfw::Key::Q), Key::L), - (Keycode::new(glfw::Key::E), Key::R), - (Keycode::new(glfw::Key::Tab), Key::Start), - (Keycode::new(glfw::Key::Space), Key::Select), - ]) -} - -impl State { - pub fn new(ctx: &context::Context) -> Self { - let screen = framebuffer::Framebuffer::screen(ctx); - let render_framebuffer = framebuffer::Framebuffer::new( - ctx, - &glam::Vec2::new(ctx.render_width, ctx.render_height), - &glam::Vec2::new(0.0, 0.0), - ); - let shader_upscale = shader::Shader::new_nolib( - ctx, - include_str!("assets/shaders/scale/vert.glsl"), - include_str!("assets/shaders/scale/frag.glsl"), - ); - - let waker = std::sync::Arc::new(WinitWaker::new()); - let cwaker = Box::leak(Box::new(waker.into())); - let waker_ctx = std::task::Context::from_waker(cwaker); - - let acc = 0.0; - let last = now(ctx); - - Self { - acc, - last, - // we initialize the tick to 1000, which allows us to use "0" as the default time for - // various animation starts on entities without having them all play at game start - tick: 1000, - - rebinding: None, - keybindings: default_keybindings(), - keys: Keys::new(), - - screen, - render_framebuffer, - shader_upscale, - audio: None, - - projection: glam::Mat4::perspective_lh( - std::f32::consts::PI / 4.0, - ctx.render_width / ctx.render_height, - // 0.1, - 0.5, - 50.0, - ), - camera: (glam::Vec3::new(0.0, 0.0, 0.0), glam::Vec3::new(0.0, 0.0, 1.0), glam::Vec3::new(0.0, 1.0, 0.0)), - lighting: ( - glam::Vec3::new(1.0, 1.0, 1.0), - glam::Vec3::new(1.0, 1.0, 1.0), - glam::Vec3::new(1.0, -1.0, 1.0), - ), - point_lights: Vec::new(), - - waker_ctx, - http_client: reqwest::Client::new(), - request: None, - - log: Vec::new(), - } - } - - pub fn write_log(&mut self, e: &str) { - log::info!("log: {}", e.to_owned()); - self.log.push((self.tick, e.to_owned())); - } - - pub fn handle_resize(&mut self, ctx: &context::Context) { - self.screen = framebuffer::Framebuffer::screen(ctx); - } - - pub fn move_camera( - &mut self, - _ctx: &context::Context, - pos: &glam::Vec3, - dir: &glam::Vec3, - up: &glam::Vec3, - ) { - self.camera = (pos.clone(), dir.clone(), up.clone()); - } - - pub fn set_lighting( - &mut self, - _ctx: &context::Context, - ambient: &glam::Vec3, - color: &glam::Vec3, - dir: &glam::Vec3, - ) { - self.lighting = (ambient.clone(), color.clone(), dir.clone()); - } - - pub fn add_point_light( - &mut self, - _ctx: &context::Context, - pos: &glam::Vec3, - color: &glam::Vec3, - attenuation: &glam::Vec2, - ) { - self.point_lights.push( - PointLight { - pos: pos.clone(), - color: color.clone(), - attenuation: attenuation.clone(), - }, - ); - } - - pub fn clear_point_lights(&mut self, _ctx: &context::Context) { - self.point_lights.clear(); - } - - pub fn view(&self) -> glam::Mat4 { - glam::Mat4::look_to_lh( - self.camera.0, - self.camera.1, - self.camera.2, - ) - } - - pub fn bind_3d_helper(&mut self, ctx: &context::Context, shader: &shader::Shader, plc: usize) { - shader.bind(ctx); - shader.set_mat4(ctx, "projection", &self.projection); - shader.set_mat4(ctx, "view", &self.view()); - shader.set_vec3( - ctx, "light_ambient_color", - &self.lighting.0, - ); - shader.set_vec3( - ctx, "light_dir_color", - &self.lighting.1, - ); - shader.set_vec3( - ctx, "light_dir", - &self.lighting.2.normalize(), - ); - shader.set_i32( - ctx, &format!("light_count"), - plc as _, - ); - } - - pub fn bind_3d_no_point_lights(&mut self, ctx: &context::Context, shader: &shader::Shader) { - self.bind_3d_helper(ctx, shader, 0); - } - - pub fn bind_3d(&mut self, ctx: &context::Context, shader: &shader::Shader) { - let plc = self.point_lights.len().min(5); - self.bind_3d_helper(ctx, shader, plc); - if plc > 0 { - let lpos: Vec<_> = self.point_lights.iter().take(plc).map(|l| l.pos).collect(); - shader.set_vec3_array( - ctx, &format!("light_pos[0]"), - &lpos, - ); - let lcolor: Vec<_> = self.point_lights.iter().take(plc).map(|l| l.color).collect(); - shader.set_vec3_array( - ctx, &format!("light_color[0]"), - &lcolor, - ); - let lattenuation: Vec<_> = self.point_lights.iter().take(plc).map(|l| l.attenuation).collect(); - shader.set_vec2_array( - ctx, &format!("light_attenuation[0]"), - &lattenuation, - ); - } - } - - pub fn bind_2d(&mut self, ctx: &context::Context, shader: &shader::Shader) { - shader.bind(ctx); - shader.set_mat4(&ctx, "projection", &glam::Mat4::IDENTITY); - shader.set_mat4( - ctx, "view", - &glam::Mat4::from_scale( - glam::Vec3::new( - 2.0 / ctx.render_width, - 2.0 / ctx.render_height, - 1.0, - ), - ), - ); - } - - pub fn mouse_moved( - &mut self, - ctx: &context::Context, - x: f32, y: f32, - game: &mut G - ) where G: Game - { - let rx = ((x - self.screen.offsets.x) * ctx.render_width / self.screen.dims.x) as i32; - let ry = ((y - self.screen.offsets.y) * ctx.render_height / self.screen.dims.y) as i32; - if !(rx < 0 || rx >= ctx.render_width as i32 || ry < 0 || ry >= ctx.render_height as i32) { - game.mouse_move(ctx, self, rx, ry); - } - } - - pub fn mouse_pressed( - &mut self, - ctx: &context::Context, - game: &mut G - ) where G: Game { - log::info!("click"); - if self.audio.is_none() { - self.audio = Some(audio::Assets::new(|actx| { - game.initialize_audio(ctx, &self, actx) - })); - game.finish_title(self); - } - game.mouse_press(ctx, self); - } - - pub fn mouse_released( - &mut self, - _ctx: &context::Context, - ) { - } - - pub fn key_pressed( - &mut self, - _ctx: &context::Context, - key: Keycode, - ) { - #[cfg(target_arch = "wasm32")] - let rebind = key == winit::keyboard::KeyCode::F12; - #[cfg(not(target_arch = "wasm32"))] - let rebind = key.kc == glfw::Key::F12; - if rebind { - self.keybindings = default_keybindings(); - self.rebinding = None; - self.write_log("Reset keybindings!"); - } else if let Some(k) = self.rebinding { - self.keybindings.insert(key, k); - self.rebinding = None; - } else if let Some(k) = self.keybindings.get_by_left(&key) { - self.keys.pressed[*k] = true; - self.keys.new[*k] = true; - } - } - - pub fn key_released( - &mut self, - _ctx: &context::Context, - key: Keycode, - ) { - if let Some(k) = self.keybindings.get_by_left(&key) { - self.keys.pressed[*k] = false; - } - } - - /// Return the first keybinding for the given virtual key - pub fn keybinding_for(&self, k: &Key) -> Option { - if let Some(kc) = self.keybindings.get_by_right(k) { - Some(format!("{:?}", kc)) - } else { - None - } - } - - pub fn rebind_key(&mut self, k: &Key) { - self.rebinding = Some(*k); - } - - pub fn request(&mut self, f: F) - where F: Fn(&reqwest::Client) -> reqwest::RequestBuilder - { - let builder = f(&self.http_client); - let fut = async { - let resp = builder.send().await?; - let url = resp.url().clone().to_string(); - let status = resp.status().clone(); - let body = resp.bytes().await?; - reqwest::Result::Ok(Response { - url, - status, - body, - }) - }; - self.request = Some(Box::pin(fut)); - } - - pub fn requesting(&self) -> bool { self.request.is_some() } - - pub fn request_returned(&mut self, ctx: &context::Context, game: &mut G, res: Response) - where G: Game - { - game.request_return(ctx, self, res); - } - - pub fn run_update(&mut self, ctx: &context::Context, game: &mut G) -> utils::Erm<()> where G: Game { - let now = now(ctx); - let diff = now - self.last; - - // update, if enough time has accumulated since last update - if diff >= DELTA_TIME { - self.last = now; - self.tick += 1; - game.update(ctx, self)?; - self.acc = 0.0; - } - Ok(()) - } - - pub fn run_render(&mut self, ctx: &context::Context, game: &mut G) -> utils::Erm<()> where G: Game { - self.render_framebuffer.bind(&ctx); - - game.render(ctx, self)?; - - self.screen.bind(&ctx); - ctx.clear_color(glam::Vec4::new(0.0, 0.0, 0.0, 0.0)); - ctx.clear(); - self.shader_upscale.bind(&ctx); - self.render_framebuffer.bind_texture(&ctx); - ctx.render_no_geometry(); - Ok(()) - } -} diff --git a/src/texture.rs b/src/texture.rs deleted file mode 100644 index 68272cb..0000000 --- a/src/texture.rs +++ /dev/null @@ -1,83 +0,0 @@ -use glow::HasContext; -use image::EncodableLayout; - -use crate::context; - -pub struct Texture { - pub tex: glow::Texture, -} - -impl Texture { - pub fn new_empty(ctx: &context::Context) -> Self { - unsafe { - let tex = ctx.gl.create_texture().expect("failed to create texture"); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); - Self { - tex, - } - } - } - - pub fn new(ctx: &context::Context, bytes: &[u8]) -> Self { - let rgba = image::io::Reader::new(std::io::Cursor::new(bytes)) - .with_guessed_format() - .expect("failed to guess image format") - .decode() - .expect("failed to decode image") - .into_rgba8(); - let pixels = rgba.as_bytes(); - unsafe { - let tex = ctx.gl.create_texture().expect("failed to create texture"); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(tex)); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::CLAMP_TO_EDGE as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::NEAREST as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::NEAREST as i32); - ctx.gl.tex_image_2d( - glow::TEXTURE_2D, - 0, - glow::RGBA as i32, - rgba.width() as i32, - rgba.height() as i32, - 0, - glow::RGBA, - glow::UNSIGNED_BYTE, - Some(pixels), - ); - ctx.gl.generate_mipmap(glow::TEXTURE_2D); - - Self { - tex, - } - } - } - - pub fn set_anisotropic_filtering(&self, ctx: &context::Context) { - unsafe { - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_S, glow::REPEAT as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_WRAP_T, glow::REPEAT as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MIN_FILTER, glow::LINEAR_MIPMAP_LINEAR as i32); - ctx.gl.tex_parameter_i32(glow::TEXTURE_2D, glow::TEXTURE_MAG_FILTER, glow::LINEAR as i32); - ctx.gl.tex_parameter_f32(glow::TEXTURE_2D, glow::TEXTURE_MAX_ANISOTROPY_EXT, 4.0); - } - } - - pub fn bind(&self, ctx: &context::Context) { - unsafe { - ctx.gl.active_texture(glow::TEXTURE0); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); - } - } - - pub fn bind_index(&self, ctx: &context::Context, idx: u32) { - unsafe { - ctx.gl.active_texture(glow::TEXTURE0 + idx); - ctx.gl.bind_texture(glow::TEXTURE_2D, Some(self.tex)); - } - } -} diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index 09afdf9..0000000 --- a/src/ui.rs +++ /dev/null @@ -1,153 +0,0 @@ -use crate::utils; - -fn compute_reverse(frames: u64, tick: u64, start: u64) -> u64 { - let leftover = frames - (tick - start) - .clamp(0, frames); - tick - leftover -} - -pub enum ModeToggle { - Inactive { start: u64 }, - Active { start: u64 }, -} - -pub struct Mode { - frames: u64, - toggle: ModeToggle, - locked: bool, -} - -impl Mode { - pub fn new(frames: u64) -> Self { - Self { - frames, - toggle: ModeToggle::Inactive { start: 0 }, - locked: false, - } - } - - /// Is the current state active? - pub fn is_active(&self) -> bool { - match self.toggle { - ModeToggle::Inactive {..} => false, - ModeToggle::Active {..} => true, - } - } - - pub fn is_locked(&self) -> bool { - self.locked - } - - /// Has the current transition finished? - pub fn is_ready(&self, tick: u64) -> bool { - let started = match self.toggle { - ModeToggle::Inactive { start } => start, - ModeToggle::Active { start } => start, - }; - tick - started > self.frames - } - - pub fn progress(&self, tick: u64) -> f32 { - match self.toggle { - ModeToggle::Inactive { start } => { - 1.0 - (((tick - start) as f32) / self.frames as f32) - .clamp(0.0, 1.0) - }, - ModeToggle::Active { start } => { - (((tick - start) as f32) / self.frames as f32) - .clamp(0.0, 1.0) - } - } - } - - pub fn reset(&mut self) { - self.locked = false; - self.toggle = ModeToggle::Inactive { start: 0 }; - } - - pub fn reverse(&mut self, tick: u64) -> bool { - if !self.locked { - self.locked = true; - match self.toggle { - ModeToggle::Inactive { start } => { - self.toggle = ModeToggle::Active { - start: compute_reverse(self.frames, tick, start) - }; - }, - ModeToggle::Active { start } => { - self.toggle = ModeToggle::Inactive { - start: compute_reverse(self.frames, tick, start) - }; - }, - } - true - } else { false } - } - - pub fn lock(&mut self) { - self.locked = true; - } - - pub fn unlock(&mut self) { - self.locked = false; - } -} - -pub struct Cursor { - pub index: i32, - pub prev_index: i32, - pub change_started: u64, - pub bound: i32, - pub frames: u64, - pub locked: bool, -} - -impl Cursor { - pub fn new(bound: i32, frames: u64) -> Self { - Self { - index: 0, - prev_index: 0, - change_started: 0, - bound, - frames, - locked: false, - } - } - - pub fn animation_index(&self, tick: u64) -> f32 { - let progress = ((tick - self.change_started) as f32) - / (self.frames as f32 / 2.0); - utils::lerp( - self.prev_index as f32, - self.index as f32, - progress - ) - } - - pub fn is_ready(&self, tick: u64) -> bool { - tick - self.change_started > self.frames - } - - pub fn set(&mut self, val: i32, tick: u64) -> bool { - if self.is_ready(tick) || !self.locked { - self.change_started = tick; - self.prev_index = self.index; - self.index = val; - self.index %= self.bound; - self.locked = true; - true - } else { false } - } - - pub fn increment(&mut self, tick: u64) -> bool { - self.set(self.index + 1, tick) - } - - pub fn decrement(&mut self, tick: u64) -> bool { - self.set(self.index + self.bound - 1, tick) - } - - pub fn unlock(&mut self) { - self.locked = false; - } -} diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 05e9251..0000000 --- a/src/utils.rs +++ /dev/null @@ -1,131 +0,0 @@ -use std::f32::consts::PI; - -use serde::{Serialize, Deserialize}; -use strum::EnumIter; - -pub type Erm = color_eyre::Result; - -pub fn erm(e: E) -> Erm where E: std::error::Error + std::marker::Send + std::marker::Sync + 'static { - Err(e.into()) -} - -pub struct ErrorHandler; -impl color_eyre::eyre::EyreHandler for ErrorHandler { - fn debug( - &self, - error: &(dyn std::error::Error + 'static), - f: &mut core::fmt::Formatter<'_>, - ) -> core::fmt::Result { - if f.alternate() { - return core::fmt::Debug::fmt(error, f); - } - let mut first = true; - if let Some(s) = error.source() { - let errors: Vec<_> = std::iter::successors(Some(s), |e| (*e).source()).collect(); - for err in errors.iter().rev() { - writeln!(f)?; write!(f, "{}{}", if first {""} else {" - "}, err)?; - first = false; - } - } - writeln!(f)?; write!(f, "{}{}", if first {""} else {" - "}, error)?; - Ok(()) - } -} - -pub fn install_error_handler() { - let (panic_hook, _) = color_eyre::config::HookBuilder::default().into_hooks(); - panic_hook.install(); - color_eyre::eyre::set_hook(Box::new(move |_| Box::new(ErrorHandler))).expect("failed to install error handler"); -} - -#[derive(Clone, Copy, Debug, EnumIter, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub enum Cardinal { - North, - South, - West, - East, -} - -impl Cardinal { - pub fn to_string(&self) -> &'static str { - match self { - Self::North => "north", - Self::South => "south", - Self::West => "west", - Self::East => "east", - } - } - - pub fn turn_cw(&self) -> Self { - match self { - Self::North => Self::East, - Self::South => Self::West, - Self::West => Self::North, - Self::East => Self::South, - } - } - - pub fn turn_ccw(&self) -> Self { - match self { - Self::North => Self::West, - Self::South => Self::East, - Self::West => Self::South, - Self::East => Self::North, - } - } - - pub fn dir(&self) -> glam::Vec3 { - match self { - Self::North => glam::Vec3::new(0.0, 1.0, 0.0), - Self::South => glam::Vec3::new(0.0, -1.0, 0.0), - Self::West => glam::Vec3::new(-1.0, 0.0, 0.0), - Self::East => glam::Vec3::new(1.0, 0.0, 0.0), - } - } - - pub fn offsets(&self) -> (i32, i32) { - match self { - Self::North => (0, 1), - Self::South => (0, -1), - Self::West => (-1, 0), - Self::East => (1, 0), - } - } - - pub fn angle(&self) -> f32 { - match self { - Self::North => 0.0, - Self::South => PI, - Self::West => PI / 2.0, - Self::East => 3.0 * PI / 2.0, - } - } - - pub fn turn_by(&self, o: &Self) -> Self { - match o { - Self::North => self.clone(), - Self::South => self.turn_cw().turn_cw(), - Self::West => self.turn_cw(), - Self::East => self.turn_ccw(), - } - } - - pub fn angle_between(&self, o: &Self) -> f32 { - if o == &self.turn_cw() { -PI / 2.0 } - else if o == &self.turn_ccw() { PI / 2.0 } - else if o == &self.turn_cw().turn_cw() { PI } - else { 0.0 } - } -} - -pub fn lerp(a: f32, b: f32, t: f32) -> f32 { - a + t.clamp(0.0, 1.0) * (b - a) -} - -pub fn dir_lerp(a: &glam::Vec3, b: glam::Vec3, t: f32) -> glam::Vec3 { - let dirrotaxis = a.cross(b).normalize(); - let dirrotangle = a.angle_between(b); - let dirrotfull = glam::Quat::from_axis_angle(dirrotaxis, dirrotangle); - let dirrot = glam::Quat::IDENTITY.slerp(dirrotfull, t); - dirrot.mul_vec3(a.clone()) -} -- cgit v1.2.3