diff --git a/app-proto/inversive-display/.gitignore b/app-proto/inversive-display/.gitignore deleted file mode 100644 index 19aa86b..0000000 --- a/app-proto/inversive-display/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -target -dist -profiling -Cargo.lock \ No newline at end of file diff --git a/app-proto/inversive-display/Cargo.toml b/app-proto/inversive-display/Cargo.toml deleted file mode 100644 index c0cbf3d..0000000 --- a/app-proto/inversive-display/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "inversive-display" -version = "0.1.0" -authors = ["Aaron"] -edition = "2021" - -[features] -default = ["console_error_panic_hook"] - -[dependencies] -js-sys = "0.3.70" -nalgebra = "0.33.0" -sycamore = "0.9.0-beta.2" - -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = { version = "0.1.7", optional = true } - -[dependencies.web-sys] -version = "0.3.69" -features = [ - 'HtmlCanvasElement', - 'Performance', - 'WebGl2RenderingContext', - 'WebGlBuffer', - 'WebGlProgram', - 'WebGlShader', - 'WebGlUniformLocation', - 'WebGlVertexArrayObject', - 'Window' -] - -[dev-dependencies] -wasm-bindgen-test = "0.3.34" - -[profile.release] -opt-level = "s" # optimize for small code size -debug = true # include debug symbols diff --git a/app-proto/inversive-display/index.html b/app-proto/inversive-display/index.html deleted file mode 100644 index 9c45180..0000000 --- a/app-proto/inversive-display/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - Inversive display - - - - diff --git a/app-proto/inversive-display/main.css b/app-proto/inversive-display/main.css deleted file mode 100644 index f8f7a96..0000000 --- a/app-proto/inversive-display/main.css +++ /dev/null @@ -1,74 +0,0 @@ -body { - margin-left: 20px; - margin-top: 20px; - color: #fcfcfc; - background-color: #202020; -} - -#app { - display: flex; - flex-direction: column; - width: 600px; -} - -canvas { - float: left; - background-color: #020202; - border: 1px solid #555; - border-radius: 10px; - margin-top: 5px; -} - -canvas:focus { - border-color: #aaa; -} - -.hidden { - display: none; -} - -.control, .tab-pane { - display: flex; - flex-direction: row; - width: 600px; -} - -input[type="radio"] { - appearance: none; - width: 0px; - height: 0px; - padding: 0px; - margin: 0px; - outline: none; -} - -.tab-pane > label { - border: 1px solid #aaa; - border-radius: 5px; - text-align: center; - padding: 5px; - margin-right: 10px; - margin-bottom: 5px; -} - -.tab-pane > label:has(:checked) { - border-color: #fcfcfc; - background-color: #555; -} - -.tab-pane > label:has(:focus-visible) { - outline: medium auto currentColor; -} - -.tab-pane > label:hover:not(:has(:checked)) { - border-color: #bbb; - background-color: #333; -} - -.control > span { - width: 170px; -} - -input { - flex-grow: 1; -} \ No newline at end of file diff --git a/app-proto/inversive-display/src/engine.rs b/app-proto/inversive-display/src/engine.rs deleted file mode 100644 index 79668bb..0000000 --- a/app-proto/inversive-display/src/engine.rs +++ /dev/null @@ -1,27 +0,0 @@ -use nalgebra::DVector; - -// the sphere with the given center and radius, with inward-pointing normals -pub fn sphere(center_x: f64, center_y: f64, center_z: f64, radius: f64) -> DVector { - let center_norm_sq = center_x * center_x + center_y * center_y + center_z * center_z; - DVector::from_column_slice(&[ - center_x / radius, - center_y / radius, - center_z / radius, - 0.5 / radius, - 0.5 * (center_norm_sq / radius - radius) - ]) -} - -// the sphere of curvature `curv` whose closest point to the origin has position -// `off * dir` and normal `dir`, where `dir` is a unit vector. setting the -// curvature to zero gives a plane -pub fn sphere_with_offset(dir_x: f64, dir_y: f64, dir_z: f64, off: f64, curv: f64) -> DVector { - let norm_sp = 1.0 + off * curv; - DVector::from_column_slice(&[ - norm_sp * dir_x, - norm_sp * dir_y, - norm_sp * dir_z, - 0.5 * curv, - off * (1.0 + 0.5 * off * curv) - ]) -} \ No newline at end of file diff --git a/app-proto/inversive-display/src/identity.vert b/app-proto/inversive-display/src/identity.vert deleted file mode 100644 index 183a65f..0000000 --- a/app-proto/inversive-display/src/identity.vert +++ /dev/null @@ -1,7 +0,0 @@ -#version 300 es - -in vec4 position; - -void main() { - gl_Position = position; -} \ No newline at end of file diff --git a/app-proto/inversive-display/src/inversive.frag b/app-proto/inversive-display/src/inversive.frag deleted file mode 100644 index f76995d..0000000 --- a/app-proto/inversive-display/src/inversive.frag +++ /dev/null @@ -1,212 +0,0 @@ -#version 300 es - -precision highp float; - -out vec4 outColor; - -// --- inversive geometry --- - -struct vecInv { - vec3 sp; - vec2 lt; -}; - -// --- uniforms --- - -// construction -const int SPHERE_MAX = 200; -uniform int sphere_cnt; -uniform vecInv sphere_list[SPHERE_MAX]; -uniform vec3 color_list[SPHERE_MAX]; - -// view -uniform vec2 resolution; -uniform float shortdim; - -// controls -uniform float opacity; -uniform float highlight; -uniform int layer_threshold; -uniform bool debug_mode; - -// light and camera -const float focal_slope = 0.3; -const vec3 light_dir = normalize(vec3(2., 2., 1.)); -const float ixn_threshold = 0.005; -const float INTERIOR_DIMMING = 0.7; - -// --- sRGB --- - -// map colors from RGB space to sRGB space, as specified in the sRGB standard -// (IEC 61966-2-1:1999) -// -// https://www.color.org/sRGB.pdf -// https://www.color.org/chardata/rgb/srgb.xalter -// -// in RGB space, color value is proportional to light intensity, so linear -// color-vector interpolation corresponds to physical light mixing. in sRGB -// space, the color encoding used by many monitors, we use more of the value -// interval to represent low intensities, and less of the interval to represent -// high intensities. this improves color quantization - -float sRGB(float t) { - if (t <= 0.0031308) { - return 12.92*t; - } else { - return 1.055*pow(t, 5./12.) - 0.055; - } -} - -vec3 sRGB(vec3 color) { - return vec3(sRGB(color.r), sRGB(color.g), sRGB(color.b)); -} - -// --- shading --- - -struct taggedFrag { - int id; - vec4 color; - vec3 pt; - vec3 normal; -}; - -taggedFrag sphere_shading(vecInv v, vec3 pt, vec3 base_color, int id) { - // the expression for normal needs to be checked. it's supposed to give the - // negative gradient of the lorentz product between the impact point vector - // and the sphere vector with respect to the coordinates of the impact - // point. i calculated it in my head and decided that the result looked good - // enough for now - vec3 normal = normalize(-v.sp + 2.*v.lt.s*pt); - - float incidence = dot(normal, light_dir); - float illum = mix(0.4, 1.0, max(incidence, 0.0)); - return taggedFrag(id, vec4(illum * base_color, opacity), pt, normal); -} - -// --- ray-casting --- - -// if `a/b` is less than this threshold, we approximate `a*u^2 + b*u + c` by -// the linear function `b*u + c` -const float DEG_THRESHOLD = 1e-9; - -// the depths, represented as multiples of `dir`, where the line generated by -// `dir` hits the sphere represented by `v`. if both depths are positive, the -// smaller one is returned in the first component. if only one depth is -// positive, it could be returned in either component -vec2 sphere_cast(vecInv v, vec3 dir) { - float a = -v.lt.s * dot(dir, dir); - float b = dot(v.sp, dir); - float c = -v.lt.t; - - float adjust = 4.*a*c/(b*b); - if (adjust < 1.) { - // as long as `b` is non-zero, the linear approximation of - // - // a*u^2 + b*u + c - // - // at `u = 0` will reach zero at a finite depth `u_lin`. the root of the - // quadratic adjacent to `u_lin` is stored in `lin_root`. if both roots - // have the same sign, `lin_root` will be the one closer to `u = 0` - float square_rect_ratio = 1. + sqrt(1. - adjust); - float lin_root = -(2.*c)/b / square_rect_ratio; - if (abs(a) > DEG_THRESHOLD * abs(b)) { - return vec2(lin_root, -b/(2.*a) * square_rect_ratio); - } else { - return vec2(lin_root, -1.); - } - } else { - // the line through `dir` misses the sphere completely - return vec2(-1., -1.); - } -} - -void main() { - vec2 scr = (2.*gl_FragCoord.xy - resolution) / shortdim; - 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 - float dimming = 1.; - 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, - dimming * 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); - dimming = INTERIOR_DIMMING; - } - } - } - - /* 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 = int(16. * gl_FragCoord.x / resolution.x); - - // convert number to color - ivec3 bits = layer_cnt / ivec3(1, 2, 4); - vec3 color = mod(vec3(bits), 2.); - if (layer_cnt % 16 >= 8) { - color = mix(color, vec3(0.5), 0.5); - } - outColor = vec4(color, 1.); - return; - } - - // highlight intersections and cusps - for (int i = layer_cnt-1; i >= 1; --i) { - // intersections - taggedFrag frag0 = frags[i]; - taggedFrag frag1 = frags[i-1]; - float ixn_sin = length(cross(frag0.normal, frag1.normal)); - vec3 disp = frag0.pt - frag1.pt; - float ixn_dist = max( - abs(dot(frag1.normal, disp)), - abs(dot(frag0.normal, disp)) - ) / ixn_sin; - float ixn_highlight = 0.5 * highlight * (1. - smoothstep(2./3.*ixn_threshold, 1.5*ixn_threshold, ixn_dist)); - frags[i].color = mix(frags[i].color, vec4(1.), ixn_highlight); - frags[i-1].color = mix(frags[i-1].color, vec4(1.), ixn_highlight); - - // cusps - float cusp_cos = abs(dot(dir, frag0.normal)); - float cusp_threshold = 2.*sqrt(ixn_threshold * sphere_list[frag0.id].lt.s); - float cusp_highlight = highlight * (1. - smoothstep(2./3.*cusp_threshold, 1.5*cusp_threshold, cusp_cos)); - frags[i].color = mix(frags[i].color, vec4(1.), cusp_highlight); - } - - // composite the sphere fragments - vec3 color = vec3(0.); - for (int i = layer_cnt-1; i >= layer_threshold; --i) { - vec4 frag_color = frags[i].color; - color = mix(color, frag_color.rgb, frag_color.a); - } - outColor = vec4(sRGB(color), 1.); -} \ No newline at end of file diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs deleted file mode 100644 index 8d16732..0000000 --- a/app-proto/inversive-display/src/main.rs +++ /dev/null @@ -1,744 +0,0 @@ -// based on the WebGL example in the `wasm-bindgen` guide -// -// https://rustwasm.github.io/wasm-bindgen/examples/webgl.html -// -// and this StackOverflow answer by wangdq -// -// https://stackoverflow.com/a/39684775 -// - -use core::array; -use nalgebra::{DMatrix, DVector, Rotation3, Vector3}; -use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}}; -use web_sys::{ - console, - window, - KeyboardEvent, - WebGl2RenderingContext, - WebGlProgram, - WebGlShader, - WebGlUniformLocation -}; - -mod engine; - -fn compile_shader( - context: &WebGl2RenderingContext, - shader_type: u32, - source: &str, -) -> WebGlShader { - let shader = context.create_shader(shader_type).unwrap(); - context.shader_source(&shader, source); - context.compile_shader(&shader); - shader -} - -fn get_uniform_array_locations( - context: &WebGl2RenderingContext, - program: &WebGlProgram, - var_name: &str, - member_name_opt: Option<&str> -) -> [Option; N] { - array::from_fn(|n| { - let name = match member_name_opt { - Some(member_name) => format!("{var_name}[{n}].{member_name}"), - None => format!("{var_name}[{n}]") - }; - context.get_uniform_location(&program, name.as_str()) - }) -} - -// load the given data into the vertex input of the given name -fn bind_vertex_attrib( - context: &WebGl2RenderingContext, - index: u32, - size: i32, - data: &[f32] -) { - // create a data buffer and bind it to ARRAY_BUFFER - let buffer = context.create_buffer().unwrap(); - context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer)); - - // load the given data into the buffer. the function `Float32Array::view` - // creates a raw view into our module's `WebAssembly.Memory` buffer. - // allocating more memory will change the buffer, invalidating the view. - // that means we have to make sure we don't allocate any memory until the - // view is dropped - unsafe { - context.buffer_data_with_array_buffer_view( - WebGl2RenderingContext::ARRAY_BUFFER, - &js_sys::Float32Array::view(&data), - WebGl2RenderingContext::STATIC_DRAW, - ); - } - - // allow the target attribute to be used - context.enable_vertex_attrib_array(index); - - // take whatever's bound to ARRAY_BUFFER---here, the data buffer created - // above---and bind it to the target attribute - // - // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer - // - context.vertex_attrib_pointer_with_i32( - index, - size, - WebGl2RenderingContext::FLOAT, - false, // don't normalize - 0, // zero stride - 0, // zero offset - ); -} - -fn push_gen_construction( - sphere_vec: &mut Vec>, - color_vec: &mut Vec<[f32; 3]>, - construction_to_world: &DMatrix, - ctrl_x: f64, - ctrl_y: f64, - radius_x: f64, - radius_y: f64 -) { - // push spheres - sphere_vec.push(construction_to_world * engine::sphere(0.5, 0.5, ctrl_x, radius_x)); - sphere_vec.push(construction_to_world * engine::sphere(-0.5, -0.5, ctrl_y, radius_y)); - 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)); - - // push colors - color_vec.push([1.00_f32, 0.25_f32, 0.00_f32]); - color_vec.push([0.00_f32, 0.25_f32, 1.00_f32]); - color_vec.push([0.25_f32, 0.00_f32, 1.00_f32]); - color_vec.push([0.25_f32, 1.00_f32, 0.00_f32]); - color_vec.push([0.75_f32, 0.75_f32, 0.00_f32]); - color_vec.push([0.00_f32, 0.75_f32, 0.50_f32]); -} - -fn push_low_curv_construction( - sphere_vec: &mut Vec>, - color_vec: &mut Vec<[f32; 3]>, - construction_to_world: &DMatrix, - off1: f64, - off2: f64, - off3: f64, - curv1: f64, - curv2: f64, - curv3: f64, -) { - // push spheres - let a = 0.75_f64.sqrt(); - sphere_vec.push(construction_to_world * engine::sphere(0.0, 0.0, 0.0, 1.0)); - sphere_vec.push(construction_to_world * engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0)); - sphere_vec.push(construction_to_world * engine::sphere_with_offset(1.0, 0.0, 0.0, off1, curv1)); - sphere_vec.push(construction_to_world * engine::sphere_with_offset(-0.5, a, 0.0, off2, curv2)); - sphere_vec.push(construction_to_world * engine::sphere_with_offset(-0.5, -a, 0.0, off3, curv3)); - sphere_vec.push(construction_to_world * engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0)); - sphere_vec.push(construction_to_world * engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0)); - sphere_vec.push(construction_to_world * engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0)); - - // push colors - color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]); - color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]); - color_vec.push([1.00_f32, 0.00_f32, 0.25_f32]); - color_vec.push([0.25_f32, 1.00_f32, 0.00_f32]); - color_vec.push([0.00_f32, 0.25_f32, 1.00_f32]); - color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]); - color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]); - color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]); -} - -#[derive(Clone, Copy, PartialEq)] -enum Tab { - GenTab, - LowCurvTab -} - -fn main() { - // set up a config option that forwards panic messages to `console.error` - #[cfg(feature = "console_error_panic_hook")] - console_error_panic_hook::set_once(); - - sycamore::render(|| { - // tab selection - let tab_selection = create_signal(Tab::GenTab); - - // navigation - let pitch_up = create_signal(0.0); - let pitch_down = create_signal(0.0); - let yaw_right = create_signal(0.0); - let yaw_left = create_signal(0.0); - let roll_ccw = create_signal(0.0); - let roll_cw = create_signal(0.0); - let zoom_in = create_signal(0.0); - let zoom_out = create_signal(0.0); - - // controls for general example - let gen_controls = create_node_ref(); - let ctrl_x = create_signal(0.0); - let ctrl_y = create_signal(0.0); - let radius_x = create_signal(1.0); - let radius_y = create_signal(1.0); - - // controls for low-curvature example - let low_curv_controls = create_node_ref(); - let curv1 = create_signal(0.0); - let curv2 = create_signal(0.0); - let curv3 = create_signal(0.0); - let off1 = create_signal(1.0); - let off2 = create_signal(1.0); - let off3 = create_signal(1.0); - - // shared controls - 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); - - // display - let display = create_node_ref(); - - // change listener - let scene_changed = create_signal(true); - create_effect(move || { - // track tab selection - tab_selection.track(); - - // track controls for general example - ctrl_x.track(); - ctrl_y.track(); - radius_x.track(); - radius_y.track(); - - // track controls for low-curvature example - curv1.track(); - curv2.track(); - curv3.track(); - off1.track(); - off2.track(); - off3.track(); - - // track shared controls - opacity.track(); - highlight.track(); - turntable.track(); - layer_threshold.track(); - debug_mode.track(); - - scene_changed.set(true); - }); - - on_mount(move || { - // tab listener - create_effect(move || { - // get the control panel nodes - let gen_controls_node = gen_controls.get::(); - let low_curv_controls_node = low_curv_controls.get::(); - - // hide all the control panels - gen_controls_node.add_class("hidden"); - low_curv_controls_node.add_class("hidden"); - - // show the selected control panel - match tab_selection.get() { - Tab::GenTab => gen_controls_node.remove_class("hidden"), - Tab::LowCurvTab => low_curv_controls_node.remove_class("hidden") - } - }); - - // create list of construction elements - const SPHERE_MAX: usize = 200; - let mut sphere_vec = Vec::>::new(); - let mut color_vec = Vec::<[f32; 3]>::new(); - - // timing - let mut last_time = 0.0; - - // scene parameters - const ROT_SPEED: f64 = 0.4; // in radians per second - const TURNTABLE_SPEED: f64 = 0.1; // in radians per second - const ZOOM_SPEED: f64 = 0.15; - let mut orientation = DMatrix::::identity(5, 5); - let mut rotation = DMatrix::::identity(5, 5); - let mut location_z: f64 = 5.0; - - /* INSTRUMENTS */ - let performance = window().unwrap().performance().unwrap(); - - // get the display canvas - let canvas = display - .get::() - .unchecked_into::(); - let ctx = canvas - .get_context("webgl2") - .unwrap() - .unwrap() - .dyn_into::() - .unwrap(); - - // compile and attach the vertex and fragment shaders - let vertex_shader = compile_shader( - &ctx, - WebGl2RenderingContext::VERTEX_SHADER, - include_str!("identity.vert"), - ); - let fragment_shader = compile_shader( - &ctx, - WebGl2RenderingContext::FRAGMENT_SHADER, - include_str!("inversive.frag"), - ); - let program = ctx.create_program().unwrap(); - ctx.attach_shader(&program, &vertex_shader); - ctx.attach_shader(&program, &fragment_shader); - ctx.link_program(&program); - let link_status = ctx - .get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS) - .as_bool() - .unwrap(); - let link_msg = if link_status { - "Linked successfully" - } else { - "Linking failed" - }; - console::log_1(&JsValue::from(link_msg)); - ctx.use_program(Some(&program)); - - /* DEBUG */ - // print the maximum number of vectors that can be passed as - // uniforms to a fragment shader. the OpenGL ES 3.0 standard - // requires this maximum to be at least 224, as discussed in the - // documentation of the GL_MAX_FRAGMENT_UNIFORM_VECTORS parameter - // here: - // - // https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml - // - // there are also other size limits. for example, on Aaron's - // machine, the the length of a float or genType array seems to be - // capped at 1024 elements - console::log_2( - &ctx.get_parameter(WebGl2RenderingContext::MAX_FRAGMENT_UNIFORM_VECTORS).unwrap(), - &JsValue::from("uniform vectors available") - ); - - // find indices of vertex attributes and uniforms - let position_index = ctx.get_attrib_location(&program, "position") as u32; - let sphere_cnt_loc = ctx.get_uniform_location(&program, "sphere_cnt"); - let sphere_sp_locs = get_uniform_array_locations::( - &ctx, &program, "sphere_list", Some("sp") - ); - let sphere_lt_locs = get_uniform_array_locations::( - &ctx, &program, "sphere_list", Some("lt") - ); - let color_locs = get_uniform_array_locations::( - &ctx, &program, "color_list", None - ); - let resolution_loc = ctx.get_uniform_location(&program, "resolution"); - let shortdim_loc = ctx.get_uniform_location(&program, "shortdim"); - let opacity_loc = ctx.get_uniform_location(&program, "opacity"); - let highlight_loc = ctx.get_uniform_location(&program, "highlight"); - let layer_threshold_loc = ctx.get_uniform_location(&program, "layer_threshold"); - let debug_mode_loc = ctx.get_uniform_location(&program, "debug_mode"); - - // create a vertex array and bind it to the graphics context - let vertex_array = ctx.create_vertex_array().unwrap(); - ctx.bind_vertex_array(Some(&vertex_array)); - - // set the vertex positions - const VERTEX_CNT: usize = 6; - let positions: [f32; 3*VERTEX_CNT] = [ - // northwest triangle - -1.0, -1.0, 0.0, - -1.0, 1.0, 0.0, - 1.0, 1.0, 0.0, - // southeast triangle - -1.0, -1.0, 0.0, - 1.0, 1.0, 0.0, - 1.0, -1.0, 0.0 - ]; - 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; - - // get the navigation state - let pitch_up_val = pitch_up.get(); - let pitch_down_val = pitch_down.get(); - let yaw_right_val = yaw_right.get(); - let yaw_left_val = yaw_left.get(); - let roll_ccw_val = roll_ccw.get(); - let roll_cw_val = roll_cw.get(); - let zoom_in_val = zoom_in.get(); - let zoom_out_val = zoom_out.get(); - let turntable_val = turntable.get(); - - // update the construction's orientation - let ang_vel = { - let pitch = pitch_up_val - pitch_down_val; - let yaw = yaw_right_val - yaw_left_val; - let roll = roll_ccw_val - roll_cw_val; - let ang_vel_from_keyboard = - if pitch != 0.0 || yaw != 0.0 || roll != 0.0 { - ROT_SPEED * Vector3::new(-pitch, yaw, roll).normalize() - } else { - Vector3::zeros() - }; - let ang_vel_from_turntable = - if turntable_val { - Vector3::new(0.0, TURNTABLE_SPEED, 0.0) - } else { - Vector3::zeros() - }; - ang_vel_from_keyboard + ang_vel_from_turntable - }; - let mut rotation_sp = rotation.fixed_view_mut::<3, 3>(0, 0); - rotation_sp.copy_from( - Rotation3::from_scaled_axis(time_step * ang_vel).matrix() - ); - orientation = &rotation * &orientation; - - // update the construction's location - let zoom = zoom_out_val - zoom_in_val; - location_z *= (time_step * ZOOM_SPEED * zoom).exp(); - - 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; - } - - // find the map from construction space to world space - let location = { - let u = -location_z; - 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, u, - 0.0, 0.0, 2.0*u, 1.0, u*u, - 0.0, 0.0, 0.0, 0.0, 1.0 - ]) - }; - let construction_to_world = &location * &orientation; - - // update the construction - sphere_vec.clear(); - color_vec.clear(); - match tab_selection.get() { - Tab::GenTab => push_gen_construction( - &mut sphere_vec, - &mut color_vec, - &construction_to_world, - ctrl_x.get(), ctrl_y.get(), - radius_x.get(), radius_y.get() - ), - Tab::LowCurvTab => push_low_curv_construction( - &mut sphere_vec, - &mut color_vec, - &construction_to_world, - off1.get(), off2.get(), off3.get(), - curv1.get(), curv2.get(), curv3.get(), - ) - }; - - // 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.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( - pitch_up_val != 0.0 - || pitch_down_val != 0.0 - || yaw_left_val != 0.0 - || yaw_right_val != 0.0 - || roll_cw_val != 0.0 - || roll_ccw_val != 0.0 - || zoom_in_val != 0.0 - || zoom_out_val != 0.0 - || turntable_val - ); - } else { - frames_since_last_sample = 0; - mean_frame_interval.set(-1.0); - } - }); - start_animation_loop(); - }); - - let set_nav_signal = move |event: KeyboardEvent, value: f64| { - let mut navigating = true; - let shift = event.shift_key(); - match event.key().as_str() { - "ArrowUp" if shift => zoom_in.set(value), - "ArrowDown" if shift => zoom_out.set(value), - "ArrowUp" => pitch_up.set(value), - "ArrowDown" => pitch_down.set(value), - "ArrowRight" if shift => roll_cw.set(value), - "ArrowLeft" if shift => roll_ccw.set(value), - "ArrowRight" => yaw_right.set(value), - "ArrowLeft" => yaw_left.set(value), - _ => navigating = false - }; - if navigating { - scene_changed.set(true); - event.prevent_default(); - } - }; - - view! { - div(id="app") { - div(class="tab-pane") { - label { - "General" - input( - type="radio", - name="tab", - prop:checked=tab_selection.get() == Tab::GenTab, - on:click=move |_| tab_selection.set(Tab::GenTab) - ) - } - label { - "Low curvature" - input( - type="radio", - name="tab", - prop:checked=tab_selection.get() == Tab::LowCurvTab, - on:change=move |_| tab_selection.set(Tab::LowCurvTab) - ) - } - } - div { "Mean frame interval: " (mean_frame_interval.get()) " ms" } - canvas( - ref=display, - width=600, - height=600, - tabindex=0, - on:keydown=move |event: KeyboardEvent| { - if event.key() == "Shift" { - roll_cw.set(yaw_right.get()); - roll_ccw.set(yaw_left.get()); - zoom_in.set(pitch_up.get()); - zoom_out.set(pitch_down.get()); - yaw_right.set(0.0); - yaw_left.set(0.0); - pitch_up.set(0.0); - pitch_down.set(0.0); - } else { - set_nav_signal(event, 1.0); - } - }, - on:keyup=move |event: KeyboardEvent| { - if event.key() == "Shift" { - yaw_right.set(roll_cw.get()); - yaw_left.set(roll_ccw.get()); - pitch_up.set(zoom_in.get()); - pitch_down.set(zoom_out.get()); - roll_cw.set(0.0); - roll_ccw.set(0.0); - zoom_in.set(0.0); - zoom_out.set(0.0); - } else { - set_nav_signal(event, 0.0); - } - }, - on:blur=move |_| { - pitch_up.set(0.0); - pitch_down.set(0.0); - yaw_right.set(0.0); - yaw_left.set(0.0); - roll_ccw.set(0.0); - roll_cw.set(0.0); - } - ) - div(ref=gen_controls) { - label(class="control") { - span { "Sphere 0 depth" } - input( - type="range", - min=-1.0, - max=1.0, - step=0.001, - bind:valueAsNumber=ctrl_x - ) - } - label(class="control") { - span { "Sphere 1 depth" } - input( - type="range", - min=-1.0, - max=1.0, - step=0.001, - bind:valueAsNumber=ctrl_y - ) - } - label(class="control") { - span { "Sphere 0 radius" } - input( - type="range", - min=0.5, - max=1.5, - step=0.001, - bind:valueAsNumber=radius_x - ) - } - label(class="control") { - span { "Sphere 1 radius" } - input( - type="range", - min=0.5, - max=1.5, - step=0.001, - bind:valueAsNumber=radius_y - ) - } - } - div(ref=low_curv_controls) { - label(class="control") { - span { "Sphere 1 offset" } - input( - type="range", - min=-1.0, - max=1.0, - step=0.001, - bind:valueAsNumber=off1 - ) - } - label(class="control") { - span { "Sphere 2 offset" } - input( - type="range", - min=-1.0, - max=1.0, - step=0.001, - bind:valueAsNumber=off2 - ) - } - label(class="control") { - span { "Sphere 3 offset" } - input( - type="range", - min=-1.0, - max=1.0, - step=0.001, - bind:valueAsNumber=off3 - ) - } - label(class="control") { - span { "Sphere 1 curvature" } - input( - type="range", - min=0.0, - max=2.0, - step=0.001, - bind:valueAsNumber=curv1 - ) - } - label(class="control") { - span { "Sphere 2 curvature" } - input( - type="range", - min=0.0, - max=2.0, - step=0.001, - bind:valueAsNumber=curv2 - ) - } - label(class="control") { - span { "Sphere 3 curvature" } - input( - type="range", - min=0.0, - max=2.0, - step=0.001, - bind:valueAsNumber=curv3 - ) - } - } - label(class="control") { - span { "Opacity" } - input( - type="range", - max=1.0, - step=0.001, - bind:valueAsNumber=opacity - ) - } - label(class="control") { - span { "Highlight" } - input( - type="range", - max=1.0, - step=0.001, - bind:valueAsNumber=highlight - ) - } - label(class="control") { - span { "Turntable" } - input( - type="checkbox", - bind:checked=turntable - ) - } - label(class="control") { - span { "Layer threshold" } - input( - type="range", - max=5.0, - step=1.0, - bind:valueAsNumber=layer_threshold - ) - } - label(class="control") { - span { "Debug mode" } - input( - type="checkbox", - bind:checked=debug_mode - ) - } - } - } - }); -} \ No newline at end of file