diff --git a/app-proto/inversive-display/Cargo.toml b/app-proto/inversive-display/Cargo.toml index c0cbf3d..bcf6b76 100644 --- a/app-proto/inversive-display/Cargo.toml +++ b/app-proto/inversive-display/Cargo.toml @@ -22,7 +22,6 @@ console_error_panic_hook = { version = "0.1.7", optional = true } version = "0.3.69" features = [ 'HtmlCanvasElement', - 'Performance', 'WebGl2RenderingContext', 'WebGlBuffer', 'WebGlProgram', diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag index 2fcd86e..51bc0ff 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -25,7 +25,7 @@ vecInv sphere(vec3 center, float radius) { // construction. the SPHERE_MAX array size seems to affect frame rate a lot, // even though we should only be using the first few elements of each array -const int SPHERE_MAX = 200; +const int SPHERE_MAX = 12; uniform int sphere_cnt; uniform vecInv sphere_list[SPHERE_MAX]; uniform vec3 color_list[SPHERE_MAX]; @@ -40,7 +40,7 @@ uniform vec2 radius; uniform float opacity; uniform float highlight; uniform int layer_threshold; -uniform bool debug_mode; +uniform bool test_mode; // light and camera const float focal_slope = 0.3; @@ -135,63 +135,36 @@ void main() { vec3 dir = vec3(focal_slope * scr, -1.); // cast rays through the spheres - const int LAYER_MAX = 12; - taggedFrag frags [LAYER_MAX]; - int layer_cnt = 0; - for (int id = 0; id < sphere_cnt; ++id) { - // find out where the ray hits the sphere - vec2 hit_depths = sphere_cast(sphere_list[id], dir); - - // insertion-sort the fragments we hit into the fragment list - for (int side = 0; side < 2; ++side) { - float hit_z = -hit_depths[side]; - if (0. > hit_z) { - for (int layer = layer_cnt; layer >= 0; --layer) { - if (layer < 1 || frags[layer-1].pt.z >= hit_z) { - // we're not as close to the screen as the fragment - // before the empty slot, so insert here - if (layer < LAYER_MAX) { - frags[layer] = sphere_shading( - sphere_list[id], - hit_depths[side] * dir, - color_list[id], - id - ); - } - break; - } else { - // we're closer to the screen than the fragment before - // the empty slot, so move that fragment into the empty - // slot - frags[layer] = frags[layer-1]; - } - } - layer_cnt = min(layer_cnt + 1, LAYER_MAX); + vec2 depth_pairs [SPHERE_MAX]; + taggedFrag frags [2*SPHERE_MAX]; + int frag_cnt = 0; + for (int i = 0; i < sphere_cnt; ++i) { + vec2 hit_depths = sphere_cast(sphere_list[i], dir); + if (!isnan(hit_depths[0])) { + frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[0] * dir, color_list[i], i); + ++frag_cnt; + } + if (!isnan(hit_depths[1])) { + frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[1] * dir, color_list[i], i); + ++frag_cnt; + } + } + + // sort the fragments by depth, using an insertion sort + for (int take = 1; take < frag_cnt; ++take) { + taggedFrag pulled = frags[take]; + for (int put = take; put >= 0; --put) { + if (put < 1 || frags[put-1].pt.z >= pulled.pt.z) { + frags[put] = pulled; + break; + } else { + frags[put] = frags[put-1]; } } } - /* DEBUG */ - // in debug mode, show the layer count instead of the shaded image - if (debug_mode) { - // at the bottom of the screen, show the color scale instead of the - // layer count - if (gl_FragCoord.y < 10.) layer_cnt = 2 * int(8. * gl_FragCoord.x / resolution.x); - - // convert number to color - vec3 color; - if (layer_cnt % 2 == 0) { - ivec3 bits = layer_cnt / ivec3(2, 4, 8); - color = mod(vec3(bits), 2.); - } else { - color = vec3(0.5); - } - outColor = vec4(color, 1.); - return; - } - // highlight intersections and cusps - for (int i = layer_cnt-1; i >= 1; --i) { + for (int i = frag_cnt-1; i >= 1; --i) { // intersections taggedFrag frag0 = frags[i]; taggedFrag frag1 = frags[i-1]; @@ -214,7 +187,7 @@ void main() { // composite the sphere fragments vec3 color = vec3(0.); - for (int i = layer_cnt-1; i >= layer_threshold; --i) { + for (int i = frag_cnt-1; i >= layer_threshold; --i) { if (frags[i].pt.z < 0.) { vec4 frag_color = frags[i].color; color = mix(color, frag_color.rgb, frag_color.a); diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index f5b42c5..ebbf713 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -9,9 +9,9 @@ extern crate js_sys; use core::array; -use nalgebra::{DMatrix, DVector}; +use nalgebra::DVector; use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}}; -use web_sys::{console, window, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation}; +use web_sys::{console, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation}; mod engine; @@ -96,58 +96,22 @@ fn main() { let radius_y = create_signal(1.0); let opacity = create_signal(0.5); let highlight = create_signal(0.2); - let turntable = create_signal(false); - let layer_threshold = create_signal(0.0); /* DEBUG */ - let debug_mode = create_signal(false); /* DEBUG */ - - /* INSTRUMENTS */ - const SAMPLE_PERIOD: i32 = 60; - let mut last_sample_time = 0.0; - let mut frames_since_last_sample = 0; - let mean_frame_interval = create_signal(0.0); + let layer_threshold = create_signal(0.0); + let debug_mode = create_signal(false); // display let display = create_node_ref(); - // change listener - let scene_changed = create_signal(true); - create_effect(move || { - ctrl_x.track(); - ctrl_y.track(); - radius_x.track(); - radius_y.track(); - opacity.track(); - highlight.track(); - turntable.track(); - layer_threshold.track(); - debug_mode.track(); - - scene_changed.set(true); - }); - on_mount(move || { // list construction elements - const SPHERE_MAX: usize = 200; + const SPHERE_MAX: usize = 12; let mut sphere_vec = Vec::>::new(); let color_vec = vec![ [1.00_f32, 0.25_f32, 0.00_f32], [0.00_f32, 0.25_f32, 1.00_f32], - [0.25_f32, 0.00_f32, 1.00_f32], - [0.25_f32, 1.00_f32, 0.00_f32], - [0.75_f32, 0.75_f32, 0.00_f32], - [0.00_f32, 0.75_f32, 0.50_f32], + [0.25_f32, 0.00_f32, 1.00_f32] ]; - // timing - let mut last_time = 0.0; - - // scene parameters - const TURNTABLE_SPEED: f64 = 0.5; - let mut turntable_angle = 0.0; - - /* INSTRUMENTS */ - let performance = window().unwrap().performance().unwrap(); - // get the display canvas let canvas = display .get::() @@ -243,110 +207,60 @@ fn main() { bind_vertex_attrib(&ctx, position_index, 3, &positions); // set up a repainting routine - let (_, start_animation_loop, _) = create_raf(move || { - // get the time step - let time = performance.now(); - let time_step = 0.001*(time - last_time); - last_time = time; + let (_, start_updating_display, _) = create_raf(move || { + // update the construction + sphere_vec.clear(); + sphere_vec.push(engine::sphere(0.5, 0.5, -5.0 + ctrl_x.get(), radius_x.get())); + sphere_vec.push(engine::sphere(-0.5, -0.5, -5.0 + ctrl_y.get(), radius_y.get())); + sphere_vec.push(engine::sphere(-0.5, 0.5, -5.0, 0.75)); - // move the turntable - let turntable_val = turntable.get(); - if turntable_val { - turntable_angle += TURNTABLE_SPEED * time_step; + // set the resolution + let width = canvas.width() as f32; + let height = canvas.height() as f32; + ctx.uniform2f(resolution_loc.as_ref(), width, height); + ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); + + // pass the construction + ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_vec.len() as i32); + for n in 0..sphere_vec.len() { + let v = &sphere_vec[n]; + ctx.uniform3f( + sphere_sp_locs[n].as_ref(), + v[0] as f32, v[1] as f32, v[2] as f32 + ); + ctx.uniform2f( + sphere_lt_locs[n].as_ref(), + v[3] as f32, v[4] as f32 + ); + ctx.uniform3fv_with_f32_array( + color_locs[n].as_ref(), + &color_vec[n] + ); } - if scene_changed.get() { - /* INSTRUMENTS */ - // measure mean frame interval - frames_since_last_sample += 1; - if frames_since_last_sample >= SAMPLE_PERIOD { - mean_frame_interval.set((time - last_sample_time) / (SAMPLE_PERIOD as f64)); - last_sample_time = time; - frames_since_last_sample = 0; - } - - // set the orientation and translation - let orientation = { - let ang_cos = turntable_angle.cos(); - let ang_sin = turntable_angle.sin(); - DMatrix::from_column_slice(5, 5, &[ - ang_cos, 0.0, ang_sin, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, 0.0, - -ang_sin, 0.0, ang_cos, 0.0, 0.0, - 0.0, 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 0.0, 1.0 - ]) - }; - let translation = { - const LEN: f64 = -5.0; - const LEN_SQ: f64 = LEN*LEN; - DMatrix::from_column_slice(5, 5, &[ - 1.0, 0.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, LEN, - 0.0, 0.0, 2.0*LEN, 1.0, LEN_SQ, - 0.0, 0.0, 0.0, 0.0, 1.0 - ]) - }; - let construction_to_world = &translation * orientation; - - // update the construction - sphere_vec.clear(); - sphere_vec.push(&construction_to_world * engine::sphere(0.5, 0.5, ctrl_x.get(), radius_x.get())); - sphere_vec.push(&construction_to_world * engine::sphere(-0.5, -0.5, ctrl_y.get(), radius_y.get())); - sphere_vec.push(&construction_to_world * engine::sphere(-0.5, 0.5, 0.0, 0.75)); - sphere_vec.push(&construction_to_world * engine::sphere(0.5, -0.5, 0.0, 0.5)); - sphere_vec.push(&construction_to_world * engine::sphere(0.0, 0.15, 1.0, 0.25)); - sphere_vec.push(&construction_to_world * engine::sphere(0.0, -0.15, -1.0, 0.25)); - - // set the resolution - let width = canvas.width() as f32; - let height = canvas.height() as f32; - ctx.uniform2f(resolution_loc.as_ref(), width, height); - ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); - - // pass the construction - ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_vec.len() as i32); - for n in 0..sphere_vec.len() { - let v = &sphere_vec[n]; - ctx.uniform3f( - sphere_sp_locs[n].as_ref(), - v[0] as f32, v[1] as f32, v[2] as f32 - ); - ctx.uniform2f( - sphere_lt_locs[n].as_ref(), - v[3] as f32, v[4] as f32 - ); - ctx.uniform3fv_with_f32_array( - color_locs[n].as_ref(), - &color_vec[n] - ); - } - - // pass the control parameters - ctx.uniform2f(ctrl_loc.as_ref(), ctrl_x.get() as f32, ctrl_y.get() as f32); /* DEBUG */ - ctx.uniform2f(radius_loc.as_ref(), radius_x.get() as f32, radius_y.get() as f32); /* DEBUG */ - ctx.uniform1f(opacity_loc.as_ref(), opacity.get() as f32); - ctx.uniform1f(highlight_loc.as_ref(), highlight.get() as f32); - ctx.uniform1i(layer_threshold_loc.as_ref(), layer_threshold.get() as i32); - ctx.uniform1i(debug_mode_loc.as_ref(), debug_mode.get() as i32); - - // draw the scene - ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); - - // clear scene change flag - scene_changed.set(turntable_val); - } else { - frames_since_last_sample = 0; - mean_frame_interval.set(-1.0); - } + // pass the control parameters + ctx.uniform2f(ctrl_loc.as_ref(), ctrl_x.get() as f32, ctrl_y.get() as f32); /* DEBUG */ + ctx.uniform2f(radius_loc.as_ref(), radius_x.get() as f32, radius_y.get() as f32); /* DEBUG */ + ctx.uniform1f(opacity_loc.as_ref(), opacity.get() as f32); + ctx.uniform1f(highlight_loc.as_ref(), highlight.get() as f32); + ctx.uniform1i(layer_threshold_loc.as_ref(), layer_threshold.get() as i32); + ctx.uniform1i(debug_mode_loc.as_ref(), debug_mode.get() as i32); + + // draw the scene + ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); }); - start_animation_loop(); + + /* + this wastes CPU time by running an animation loop, which updates the + display even when nothing has changed. there should be a way to make + Sycamore do single-frame updates in response to changes, but i + haven't found it yet + */ + start_updating_display(); }); view! { div(id="app") { - div { "Mean frame interval: " (mean_frame_interval.get()) " ms" } canvas(ref=display, width="600", height="600") div(class="control") { label(for="ctrl-x") { "Sphere 0 depth" } @@ -412,14 +326,6 @@ fn main() { bind:valueAsNumber=highlight ) } - div(class="control") { - label(for="turntable") { "Turntable" } - input( - type="checkbox", - id="turntable", - bind:checked=turntable - ) - } div(class="control") { label(for="layer-threshold") { "Layer threshold" } input(