fast_layer/
lib.rs

1use glam::f32::{Mat4, Vec3};
2use js_sys::Uint8Array;
3use wasm_bindgen::JsCast;
4use wasm_bindgen::JsValue;
5use wasm_bindgen::prelude::*;
6use wasm_bindgen_futures::spawn_local;
7use web_sys::{
8    Element, HtmlCanvasElement, WebGl2RenderingContext, WebGlBuffer, WebGlProgram, WebGlShader,
9    WebGlUniformLocation, WebGlVertexArrayObject, console, window,
10};
11mod init;
12mod shaders;
13
14const PI: f32 = 3.14159265358979323846;
15/// Simple mesh data to be consumed by the vertex shader.
16///
17/// - `vertices` is a flat list of `f32` vertex attributes (e.g. position, uv, etc.).
18/// - `indices` describes element indices (u16) for indexed drawing.
19#[derive(Clone, Debug)]
20pub struct Mesh {
21    pub vertices: Vec<f32>,
22    pub indices: Vec<u16>,
23}
24
25impl Mesh {
26    /// Create a new mesh from raw vertex and index arrays.
27    pub fn new(vertices: Vec<f32>, indices: Vec<u16>) -> Self {
28        Mesh { vertices, indices }
29    }
30
31    /// Number of vertices (interpreted as count of attribute groups should be computed by caller).
32    pub fn vertex_count(&self) -> usize {
33        // This returns the number of f32 values; callers can divide by the vertex stride.
34        self.vertices.len()
35    }
36
37    /// Number of indices for indexed drawing.
38    pub fn index_count(&self) -> usize {
39        self.indices.len()
40    }
41}
42
43/// Returns a unit cube mesh centered at the origin with side length 2 (from -1 to +1).
44///
45/// Vertex layout (per-vertex, flat `Vec<f32>`):
46/// `[x, y, z, nx, ny, nz, u, v]` (8 floats per vertex).
47///
48/// The cube uses 24 unique vertices (4 per face) so each face has correct normals
49/// and texture coordinates. Indices are `u16` for 36 elements (6 faces × 2 tris × 3).
50pub fn cube() -> Mesh {
51    // Helper to push one vertex into the flat array
52    let mut verts: Vec<f32> = Vec::with_capacity(24 * 8);
53    let scale = 10000.0; // Scale up the cube to make it visible on the map
54    let mut push_v = |pos: [f32; 3], norm: [f32; 3], uv: [f32; 2]| {
55        verts.push(pos[0] * scale);
56        verts.push(pos[1] * scale);
57        verts.push(pos[2] * scale);
58        verts.push(norm[0]);
59        verts.push(norm[1]);
60        verts.push(norm[2]);
61        verts.push(uv[0]);
62        verts.push(uv[1]);
63    };
64
65    // Define the six faces: front, back, top, bottom, right, left
66    // Each face: 4 verts (ccw), normal, uvs arranged (0,0),(1,0),(1,1),(0,1)
67    // Front (z = +1)
68    let n = [0.0, 0.0, 1.0];
69    push_v([-1.0, -1.0, 1.0], n, [0.0, 0.0]);
70    push_v([1.0, -1.0, 1.0], n, [1.0, 0.0]);
71    push_v([1.0, 1.0, 1.0], n, [1.0, 1.0]);
72    push_v([-1.0, 1.0, 1.0], n, [0.0, 1.0]);
73
74    // Back (z = -1)
75    let n = [0.0, 0.0, -1.0];
76    push_v([1.0, -1.0, -1.0], n, [0.0, 0.0]);
77    push_v([-1.0, -1.0, -1.0], n, [1.0, 0.0]);
78    push_v([-1.0, 1.0, -1.0], n, [1.0, 1.0]);
79    push_v([1.0, 1.0, -1.0], n, [0.0, 1.0]);
80
81    // Top (y = +1)
82    let n = [0.0, 1.0, 0.0];
83    push_v([-1.0, 1.0, 1.0], n, [0.0, 0.0]);
84    push_v([1.0, 1.0, 1.0], n, [1.0, 0.0]);
85    push_v([1.0, 1.0, -1.0], n, [1.0, 1.0]);
86    push_v([-1.0, 1.0, -1.0], n, [0.0, 1.0]);
87
88    // Bottom (y = -1)
89    let n = [0.0, -1.0, 0.0];
90    push_v([-1.0, -1.0, -1.0], n, [0.0, 0.0]);
91    push_v([1.0, -1.0, -1.0], n, [1.0, 0.0]);
92    push_v([1.0, -1.0, 1.0], n, [1.0, 1.0]);
93    push_v([-1.0, -1.0, 1.0], n, [0.0, 1.0]);
94
95    // Right (x = +1)
96    let n = [1.0, 0.0, 0.0];
97    push_v([1.0, -1.0, 1.0], n, [0.0, 0.0]);
98    push_v([1.0, -1.0, -1.0], n, [1.0, 0.0]);
99    push_v([1.0, 1.0, -1.0], n, [1.0, 1.0]);
100    push_v([1.0, 1.0, 1.0], n, [0.0, 1.0]);
101
102    // Left (x = -1)
103    let n = [-1.0, 0.0, 0.0];
104    push_v([-1.0, -1.0, -1.0], n, [0.0, 0.0]);
105    push_v([-1.0, -1.0, 1.0], n, [1.0, 0.0]);
106    push_v([-1.0, 1.0, 1.0], n, [1.0, 1.0]);
107    push_v([-1.0, 1.0, -1.0], n, [0.0, 1.0]);
108
109    // Indices: 6 faces × 2 triangles × 3 indices = 36
110    let mut indices: Vec<u16> = Vec::with_capacity(36);
111    for face in 0..6u16 {
112        let base = face * 4;
113        indices.push(base + 0);
114        indices.push(base + 1);
115        indices.push(base + 2);
116        indices.push(base + 0);
117        indices.push(base + 2);
118        indices.push(base + 3);
119    }
120
121    Mesh::new(verts, indices)
122}
123
124#[wasm_bindgen]
125pub struct FastLayer {
126    canvas_id: String,
127    context: WebGl2RenderingContext,
128    program: Option<WebGlProgram>,
129    model: Option<Mesh>,
130    vao: Option<WebGlVertexArrayObject>,
131    vbo: Option<WebGlBuffer>,
132    ebo: Option<WebGlBuffer>,
133}
134
135#[wasm_bindgen]
136impl FastLayer {
137    pub fn new(canvas_id: &str) -> FastLayer {
138        let document = web_sys::window().unwrap().document().unwrap();
139        let canvas = document.get_element_by_id(canvas_id).unwrap();
140        let canvas: web_sys::HtmlCanvasElement = canvas
141            .dyn_into::<web_sys::HtmlCanvasElement>()
142            .map_err(|_| ())
143            .unwrap();
144        let context: WebGl2RenderingContext = canvas
145            .get_context("webgl2")
146            .unwrap()
147            .unwrap()
148            .dyn_into()
149            .unwrap();
150
151        // Enable depth testing for 3D rendering
152        context.enable(WebGl2RenderingContext::DEPTH_TEST);
153
154        FastLayer {
155            canvas_id: canvas_id.to_string(),
156            context,
157            program: None,
158            model: None,
159            vao: None,
160            vbo: None,
161            ebo: None,
162        }
163    }
164
165    pub fn compile(&mut self) {
166        let vertex_source = shaders::VERTEX_SOURCE;
167
168        let fragment_source = shaders::FRAGMENT_SOURCE;
169
170        let vertex_shader = self
171            .context
172            .create_shader(WebGl2RenderingContext::VERTEX_SHADER)
173            .unwrap();
174        self.context.shader_source(&vertex_shader, vertex_source);
175        self.context.compile_shader(&vertex_shader);
176
177        if !self
178            .context
179            .get_shader_parameter(&vertex_shader, WebGl2RenderingContext::COMPILE_STATUS)
180            .as_bool()
181            .unwrap_or(false)
182        {
183            let info = self
184                .context
185                .get_shader_info_log(&vertex_shader)
186                .unwrap_or_else(|| "Unknown error".to_string());
187            panic!("Could not compile vertex shader. \n\n{}", info);
188        }
189
190        let fragment_shader = self
191            .context
192            .create_shader(WebGl2RenderingContext::FRAGMENT_SHADER)
193            .unwrap();
194        self.context
195            .shader_source(&fragment_shader, fragment_source);
196        self.context.compile_shader(&fragment_shader);
197
198        if !self
199            .context
200            .get_shader_parameter(&fragment_shader, WebGl2RenderingContext::COMPILE_STATUS)
201            .as_bool()
202            .unwrap_or(false)
203        {
204            let info = self
205                .context
206                .get_shader_info_log(&fragment_shader)
207                .unwrap_or_else(|| "Unknown error".to_string());
208            panic!("Could not compile fragment shader. \n\n{}", info);
209        }
210
211        let program = self.context.create_program().unwrap();
212        self.context.attach_shader(&program, &vertex_shader);
213        self.context.attach_shader(&program, &fragment_shader);
214        self.context.link_program(&program);
215
216        if !self
217            .context
218            .get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
219            .as_bool()
220            .unwrap_or(false)
221        {
222            let info = self
223                .context
224                .get_program_info_log(&program)
225                .unwrap_or_else(|| "Unknown error".to_string());
226            panic!("Could not link WebGL program. \n\n{}", info);
227        }
228
229        self.program = Some(program);
230
231        // If we have a model, create/bind VAO, VBO, EBO and upload data.
232        if let Some(mesh) = &self.model {
233            // Create VAO
234            let vao = self.context.create_vertex_array();
235            self.context.bind_vertex_array(vao.as_ref());
236
237            // Create VBO and upload vertex data
238            let vbo = self.context.create_buffer();
239            self.context
240                .bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, vbo.as_ref());
241            // Safety: view into wasm memory for Float32Array
242            unsafe {
243                let vert_array = js_sys::Float32Array::view(&mesh.vertices);
244                self.context.buffer_data_with_array_buffer_view(
245                    WebGl2RenderingContext::ARRAY_BUFFER,
246                    &vert_array,
247                    WebGl2RenderingContext::STATIC_DRAW,
248                );
249            }
250
251            // Create EBO and upload index data
252            let ebo = self.context.create_buffer();
253            self.context
254                .bind_buffer(WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER, ebo.as_ref());
255            unsafe {
256                let idx_array = js_sys::Uint16Array::view(&mesh.indices);
257                self.context.buffer_data_with_array_buffer_view(
258                    WebGl2RenderingContext::ELEMENT_ARRAY_BUFFER,
259                    &idx_array,
260                    WebGl2RenderingContext::STATIC_DRAW,
261                );
262            }
263
264            // Configure vertex attributes
265            // Our vertex layout: [x,y,z, nx,ny,nz, u,v] => stride = 8 * 4 bytes
266            if let Some(ref prog) = self.program {
267                // position
268                let pos_loc = self.context.get_attrib_location(prog, "a_position");
269                if pos_loc >= 0 {
270                    let idx: u32 = pos_loc as u32;
271                    self.context.enable_vertex_attrib_array(idx);
272                    // size=3 (vec3), type=FLOAT, normalized=false, stride=32, offset=0
273                    self.context.vertex_attrib_pointer_with_i32(
274                        idx,
275                        3,
276                        WebGl2RenderingContext::FLOAT,
277                        false,
278                        (8 * std::mem::size_of::<f32>()) as i32,
279                        0,
280                    );
281                }
282                // normal
283                let normal_loc = self.context.get_attrib_location(prog, "a_normal");
284                if normal_loc >= 0 {
285                    let idx: u32 = normal_loc as u32;
286                    self.context.enable_vertex_attrib_array(idx);
287                    // size=3 (vec3), type=FLOAT, normalized=false, stride=32, offset=0
288                    self.context.vertex_attrib_pointer_with_i32(
289                        idx,
290                        3,
291                        WebGl2RenderingContext::FLOAT,
292                        false,
293                        (8 * std::mem::size_of::<f32>()) as i32,
294                        (3 * std::mem::size_of::<f32>()) as i32,
295                    );
296                }
297                // normal
298                let uv_loc = self.context.get_attrib_location(prog, "a_uv");
299                if uv_loc >= 0 {
300                    let idx: u32 = uv_loc as u32;
301                    self.context.enable_vertex_attrib_array(idx);
302                    // size=3 (vec3), type=FLOAT, normalized=false, stride=32, offset=0
303                    self.context.vertex_attrib_pointer_with_i32(
304                        idx,
305                        2,
306                        WebGl2RenderingContext::FLOAT,
307                        false,
308                        (8 * std::mem::size_of::<f32>()) as i32,
309                        (6 * std::mem::size_of::<f32>()) as i32,
310                    );
311                }
312            }
313
314            // Unbind VAO to avoid accidental state changes
315            self.context.bind_vertex_array(None);
316
317            // Save handles
318            self.vao = vao;
319            self.vbo = vbo;
320            self.ebo = ebo;
321        }
322    }
323
324    pub fn draw(&self, matrix: &[f32]) {
325        if matrix.len() != 16 {
326            panic!("matrix must be 16 length")
327        }
328        // Ensure the program, model and VAO are present
329        assert!(self.program.is_some(), "No compiled program available");
330        assert!(self.model.is_some(), "No model available");
331        assert!(self.vao.is_some(), "No VAO available");
332
333        // Enable depth testing for 3D rendering
334        // self.context.enable(WebGl2RenderingContext::DEPTH_TEST);
335        // self.context.depth_func(WebGl2RenderingContext::LEQUAL);
336        // self.context.clear_color(0.0, 0.0, 0.3, 1.0);
337        // self.context.clear_depth(1.0);
338        // self.context.clear(
339        //     WebGl2RenderingContext::COLOR_BUFFER_BIT | WebGl2RenderingContext::DEPTH_BUFFER_BIT,
340        // );
341        let prog = self.program.as_ref().unwrap();
342        self.context.use_program(Some(prog));
343        let location = self.context.get_uniform_location(prog, "u_matrix");
344
345        if let Some(loc) = location {
346            // let perspective: Mat4 =
347            //     Mat4::perspective_rh_gl(3.14159 / 3.0, 785.0 / 400.0, 1.0, 2000.0);
348
349            // // multiply perspective by matrix
350            // let matrix_array: [f32; 16] = [
351            //     7.585063395431608,
352            //     1.2318389044942468e-15,
353            //     0.0,
354            //     0.0,
355            //     -5.68788651470446e-32,
356            //     6.159194522471234e-16,
357            //     -3.4426192810028815,
358            //     -3.3529093323122527,
359            //     9.289023608545075e-16,
360            //     -10.058727996936758,
361            //     -2.1079963415815706e-16,
362            //     -2.0530648408237448e-16,
363            //     0.0,
364            //     0.0,
365            //     901.2587959866221,
366            //     901.5,
367            // ];
368
369            // let perspective = perspective.mul_mat4(&Mat4::from_cols_array(matrix_array));
370
371            // let matrix_array: [f32; 16] = [
372            //     1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
373            // ];
374
375            // Result main * model
376
377            // let perspective: [f32; 16] = [
378            //     7.595366038503267,
379            //     1.2318389044942468e-15,
380            //     0.0,
381            //     0.0,
382            //     -5.695612259571457e-32,
383            //     6.159194522471234e-16,
384            //     -3.4426192810028815,
385            //     -3.3529093323122527,
386            //     9.301640707405538e-16,
387            //     -10.058727996936758,
388            //     -2.1079963415815706e-16,
389            //     -2.0530648408237448e-16,
390            //     0.0,
391            //     0.0,
392            //     721.3069565217392,
393            //     721.5,
394            // ];
395            // console::log_1(&JsValue::from_str(&format!(
396            //     "perspective: {:?}",
397            //     perspective
398            // )));
399
400            // Upload as column-major mat4
401            self.context
402                .uniform_matrix4fv_with_f32_array(Some(&loc), false, &matrix);
403        }
404
405        let model_location = self.context.get_uniform_location(prog, "u_model_matrix");
406        if let Some(loc) = model_location {
407            // console::log_1(&JsValue::from_str("model matrix"));
408            // Upload identity matrix for model matrix
409            let model: [f32; 16] = [
410                2.4981121214570498e-8,
411                -3.05930501345358e-24,
412                -0.0,
413                -0.0,
414                -1.8732840461706885e-40,
415                -1.52965250672679e-24,
416                2.4981121214570498e-8,
417                0.0,
418                3.05930501345358e-24,
419                2.4981121214570498e-8,
420                1.52965250672679e-24,
421                0.0,
422                0.5,
423                0.5,
424                0.0,
425                1.0,
426            ];
427            // let model: [f32; 16] = [];
428            // // let a = perspective.mul_mat4(rhs)
429            self.context
430                .uniform_matrix4fv_with_f32_array(Some(&loc), false, &model);
431        }
432
433        // Bind VAO and draw the model with triangles
434        let mesh = self.model.as_ref().unwrap();
435        self.context.bind_vertex_array(self.vao.as_ref());
436        let count = mesh.index_count() as i32;
437        self.context.draw_elements_with_i32(
438            WebGl2RenderingContext::TRIANGLES,
439            count,
440            WebGl2RenderingContext::UNSIGNED_SHORT,
441            0,
442        );
443        self.context.bind_vertex_array(None);
444    }
445
446    pub fn load_model_from_url(&mut self, url: &str) {
447        self.model = Some(cube());
448        return;
449        let url = url.to_string();
450        spawn_local(async move {
451            let window = match web_sys::window() {
452                Some(w) => w,
453                None => {
454                    console::log_1(&JsValue::from_str("No window available"));
455                    return;
456                }
457            };
458
459            let fetch_promise = window.fetch_with_str(&url);
460            match wasm_bindgen_futures::JsFuture::from(fetch_promise).await {
461                Ok(resp_value) => {
462                    let resp: web_sys::Response = resp_value.dyn_into().unwrap();
463                    if !resp.ok() {
464                        console::log_1(&JsValue::from_str(&format!(
465                            "Failed to fetch {}: HTTP {}",
466                            url,
467                            resp.status()
468                        )));
469                        return;
470                    }
471
472                    match resp.array_buffer() {
473                        Ok(ab_promise) => {
474                            match wasm_bindgen_futures::JsFuture::from(ab_promise).await {
475                                Ok(array_buffer) => {
476                                    let u8_array = Uint8Array::new(&array_buffer);
477                                    let size = u8_array.length();
478                                    console::log_1(&JsValue::from_str(&format!(
479                                        "Loaded model size: {}",
480                                        size
481                                    )));
482                                }
483                                Err(err) => {
484                                    console::log_1(&JsValue::from_str(&format!(
485                                        "Failed to read array_buffer: {:?}",
486                                        err
487                                    )));
488                                }
489                            }
490                        }
491                        Err(err) => {
492                            console::log_1(&JsValue::from_str(&format!(
493                                "Failed to call array_buffer(): {:?}",
494                                err
495                            )));
496                        }
497                    }
498                }
499                Err(err) => {
500                    console::log_1(&JsValue::from_str(&format!("Fetch error: {:?}", err)));
501                }
502            }
503        });
504    }
505}