diff --git a/app-proto/inversive-display/main.css b/app-proto/inversive-display/main.css index f8f7a96..7e8c710 100644 --- a/app-proto/inversive-display/main.css +++ b/app-proto/inversive-display/main.css @@ -14,15 +14,10 @@ body { canvas { float: left; background-color: #020202; - border: 1px solid #555; border-radius: 10px; margin-top: 5px; } -canvas:focus { - border-color: #aaa; -} - .hidden { display: none; } @@ -34,12 +29,7 @@ canvas:focus { } input[type="radio"] { - appearance: none; - width: 0px; - height: 0px; - padding: 0px; - margin: 0px; - outline: none; + display: none; } .tab-pane > label { @@ -56,16 +46,12 @@ input[type="radio"] { 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 { +label { width: 170px; } diff --git a/app-proto/inversive-display/src/engine.rs b/app-proto/inversive-display/src/engine.rs index 79668bb..7fbcd03 100644 --- a/app-proto/inversive-display/src/engine.rs +++ b/app-proto/inversive-display/src/engine.rs @@ -1,6 +1,5 @@ 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(&[ @@ -10,18 +9,4 @@ pub fn sphere(center_x: f64, center_y: f64, center_z: f64, radius: f64) -> DVect 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/inversive.frag b/app-proto/inversive-display/src/inversive.frag index 2e185a5..9913b40 100644 --- a/app-proto/inversive-display/src/inversive.frag +++ b/app-proto/inversive-display/src/inversive.frag @@ -110,37 +110,23 @@ taggedFrag sphere_shading(vecInv v, vec3 pt, vec3 base_color, int id) { // --- 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 scale = -b/(2.*a); 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.); - } + float offset = sqrt(1. - adjust); + return vec2( + scale * (1. - offset), + scale * (1. + offset) + ); } else { - // the line through `dir` misses the sphere completely + // these parameters describe points behind the camera, so the + // corresponding fragments won't be drawn return vec2(-1., -1.); } } @@ -193,13 +179,15 @@ void main() { 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); + if (gl_FragCoord.y < 10.) layer_cnt = 2 * int(8. * 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); + 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; diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 3dd4653..1fbef8d 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -10,6 +10,7 @@ extern crate js_sys; use core::array; use nalgebra::{DMatrix, DVector}; +use std::f64::consts::FRAC_1_SQRT_2; use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}}; use web_sys::{console, window, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation}; @@ -85,61 +86,30 @@ fn bind_vertex_attrib( 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, + curv_x: f64, + curv_y: 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]); + sphere_vec.push(construction_to_world * DVector::from_column_slice(&[0.0, -1.0, 0.0, 0.5*curv_x, 0.0])); + sphere_vec.push(construction_to_world * DVector::from_column_slice(&[-FRAC_1_SQRT_2, 0.0, -FRAC_1_SQRT_2, 0.5*curv_y, 0.0])); + sphere_vec.push(construction_to_world * engine::sphere(0.5, 0.0, 0.5, FRAC_1_SQRT_2)); + sphere_vec.push(construction_to_world * engine::sphere(-0.5, 0.0, -0.5, FRAC_1_SQRT_2)); } #[derive(Clone, Copy, PartialEq)] @@ -166,12 +136,8 @@ fn main() { // 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); + let curv_x = create_signal(0.0); + let curv_y = create_signal(0.0); // shared controls let opacity = create_signal(0.5); @@ -202,12 +168,8 @@ fn main() { radius_y.track(); // track controls for low-curvature example - curv1.track(); - curv2.track(); - curv3.track(); - off1.track(); - off2.track(); - off3.track(); + curv_x.track(); + curv_y.track(); // track shared controls opacity.track(); @@ -237,10 +199,17 @@ fn main() { } }); - // create list of construction elements + // list construction elements const SPHERE_MAX: usize = 200; let mut sphere_vec = Vec::>::new(); - let mut color_vec = Vec::<[f32; 3]>::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], + ]; // timing let mut last_time = 0.0; @@ -396,21 +365,17 @@ fn main() { // 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(), + curv_x.get(), curv_y.get() ) }; @@ -482,42 +447,46 @@ fn main() { } } div { "Mean frame interval: " (mean_frame_interval.get()) " ms" } - canvas(ref=display, width=600, height=600, tabindex=0) + canvas(ref=display, width="600", height="600") div(ref=gen_controls) { - label(class="control") { - span { "Sphere 0 depth" } + div(class="control") { + label(for="ctrl-x") { "Sphere 0 depth" } input( type="range", + id="ctrl-x", min=-1.0, max=1.0, step=0.001, bind:valueAsNumber=ctrl_x ) } - label(class="control") { - span { "Sphere 1 depth" } + div(class="control") { + label(for="ctrl-y") { "Sphere 1 depth" } input( type="range", + id="ctrl-y", min=-1.0, max=1.0, step=0.001, bind:valueAsNumber=ctrl_y ) } - label(class="control") { - span { "Sphere 0 radius" } + div(class="control") { + label(for="radius-x") { "Sphere 0 radius" } input( type="range", + id="radius-x", min=0.5, max=1.5, step=0.001, bind:valueAsNumber=radius_x ) } - label(class="control") { - span { "Sphere 1 radius" } + div(class="control") { + label(for="radius-y") { "Sphere 1 radius" } input( type="range", + id="radius-y", min=0.5, max=1.5, step=0.001, @@ -526,105 +495,72 @@ fn main() { } } 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" } + div(class="control") { + label(for="curv-x") { "Sphere 0 curvature" } input( type="range", + id="curv-x", min=0.0, max=2.0, step=0.001, - bind:valueAsNumber=curv1 + bind:valueAsNumber=curv_x ) } - label(class="control") { - span { "Sphere 2 curvature" } + div(class="control") { + label(for="curv-y") { "Sphere 1 curvature" } input( type="range", + id="curv-y", 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 + bind:valueAsNumber=curv_y ) } } - label(class="control") { - span { "Opacity" } + div(class="control") { + label(for="opacity") { "Opacity" } input( type="range", + id="opacity", max=1.0, step=0.001, bind:valueAsNumber=opacity ) } - label(class="control") { - span { "Highlight" } + div(class="control") { + label(for="highlight") { "Highlight" } input( type="range", + id="highlight", max=1.0, step=0.001, bind:valueAsNumber=highlight ) } - label(class="control") { - span { "Turntable" } + div(class="control") { + label(for="turntable") { "Turntable" } input( type="checkbox", + id="turntable", bind:checked=turntable ) } - label(class="control") { - span { "Layer threshold" } + div(class="control") { + label(for="layer-threshold") { "Layer threshold" } input( type="range", + id="layer-threshold", max=5.0, step=1.0, bind:valueAsNumber=layer_threshold ) } - label(class="control") { - span { "Debug mode" } + div(class="control") { + label(for="debug-mode") { "Debug mode" } input( type="checkbox", + id="debug-mode", bind:checked=debug_mode ) }