From c67f37c934ee229ad253caca82278fe4b575d18c Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Mon, 9 Sep 2024 19:41:15 -0700 Subject: [PATCH 1/5] Implement keyboard navigation --- app-proto/inversive-display/src/main.rs | 143 ++++++++++++++++++------ 1 file changed, 109 insertions(+), 34 deletions(-) diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 3dd4653..ef2628a 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -9,9 +9,17 @@ extern crate js_sys; use core::array; -use nalgebra::{DMatrix, DVector}; +use nalgebra::{DMatrix, DVector, Rotation3, Vector3}; use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}}; -use web_sys::{console, window, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation}; +use web_sys::{ + console, + window, + KeyboardEvent, + WebGl2RenderingContext, + WebGlProgram, + WebGlShader, + WebGlUniformLocation +}; mod engine; @@ -157,6 +165,12 @@ fn main() { // 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); + // controls for general example let gen_controls = create_node_ref(); let ctrl_x = create_signal(0.0); @@ -246,8 +260,21 @@ fn main() { let mut last_time = 0.0; // scene parameters - const TURNTABLE_SPEED: f64 = 0.5; - let mut turntable_angle = 0.0; + const NAV_SPEED: f64 = 0.4; // in radians per second + const TURNTABLE_SPEED: f64 = 0.1; // in radians per second + let mut orientation = DMatrix::::identity(5, 5); + let mut rotation = DMatrix::::identity(5, 5); + let location = { + 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 + ]) + }; /* INSTRUMENTS */ let performance = window().unwrap().performance().unwrap(); @@ -353,11 +380,36 @@ fn main() { let time_step = 0.001*(time - last_time); last_time = time; - // move the turntable + // 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 turntable_val = turntable.get(); - if turntable_val { - turntable_angle += TURNTABLE_SPEED * time_step; - } + + // 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 ang_vel_from_keyboard = + if pitch != 0.0 || yaw != 0.0 { + NAV_SPEED * Vector3::new(-pitch, yaw, 0.0).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; if scene_changed.get() { /* INSTRUMENTS */ @@ -369,30 +421,8 @@ fn main() { 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; + // find the map from construction space to world space + let construction_to_world = &location * &orientation; // update the construction sphere_vec.clear(); @@ -450,7 +480,13 @@ fn main() { ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); // clear scene change flag - scene_changed.set(turntable_val); + scene_changed.set( + pitch_up_val != 0.0 + || pitch_down_val != 0.0 + || yaw_left_val != 0.0 + || yaw_right_val != 0.0 + || turntable_val + ); } else { frames_since_last_sample = 0; mean_frame_interval.set(-1.0); @@ -482,7 +518,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, + tabindex=0, + on:keydown=move |event: KeyboardEvent| { + let mut navigating = true; + match event.key().as_str() { + "ArrowUp" => pitch_up.set(1.0), + "ArrowDown" => pitch_down.set(1.0), + "ArrowRight" => yaw_right.set(1.0), + "ArrowLeft" => yaw_left.set(1.0), + _ => navigating = false + }; + if navigating { + scene_changed.set(true); + event.prevent_default(); + } + }, + on:keyup=move |event: KeyboardEvent| { + let mut navigating = true; + match event.key().as_str() { + "ArrowUp" => pitch_up.set(0.0), + "ArrowDown" => pitch_down.set(0.0), + "ArrowRight" => yaw_right.set(0.0), + "ArrowLeft" => yaw_left.set(0.0), + _ => navigating = false + }; + if navigating { + scene_changed.set(true); + event.prevent_default(); + } + }, + on:blur=move |_| { + pitch_up.set(0.0); + pitch_down.set(0.0); + yaw_right.set(0.0); + yaw_left.set(0.0); + } + ) div(ref=gen_controls) { label(class="control") { span { "Sphere 0 depth" } From 20d072d61561215cdb1ab676bc8397e9275db8b9 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 10 Sep 2024 02:29:50 -0700 Subject: [PATCH 2/5] Combine key-down and key-up handlers --- app-proto/inversive-display/src/main.rs | 45 ++++++++++--------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index ef2628a..f516ed8 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -495,6 +495,21 @@ fn main() { start_animation_loop(); }); + let set_nav_signal = move |event: KeyboardEvent, value: f64| { + let mut navigating = true; + match event.key().as_str() { + "ArrowUp" => pitch_up.set(value), + "ArrowDown" => pitch_down.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") { @@ -523,34 +538,8 @@ fn main() { width=600, height=600, tabindex=0, - on:keydown=move |event: KeyboardEvent| { - let mut navigating = true; - match event.key().as_str() { - "ArrowUp" => pitch_up.set(1.0), - "ArrowDown" => pitch_down.set(1.0), - "ArrowRight" => yaw_right.set(1.0), - "ArrowLeft" => yaw_left.set(1.0), - _ => navigating = false - }; - if navigating { - scene_changed.set(true); - event.prevent_default(); - } - }, - on:keyup=move |event: KeyboardEvent| { - let mut navigating = true; - match event.key().as_str() { - "ArrowUp" => pitch_up.set(0.0), - "ArrowDown" => pitch_down.set(0.0), - "ArrowRight" => yaw_right.set(0.0), - "ArrowLeft" => yaw_left.set(0.0), - _ => navigating = false - }; - if navigating { - scene_changed.set(true); - event.prevent_default(); - } - }, + on:keydown=move |event: KeyboardEvent| { set_nav_signal(event, 1.0); }, + on:keyup=move |event: KeyboardEvent| { set_nav_signal(event, 0.0); }, on:blur=move |_| { pitch_up.set(0.0); pitch_down.set(0.0); From aceac5e5c41006781cf88cd98c2e60b9709aaaf7 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 10 Sep 2024 03:14:33 -0700 Subject: [PATCH 3/5] Add roll to keyboard controls --- app-proto/inversive-display/src/main.rs | 37 ++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index f516ed8..350c81c 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -170,6 +170,8 @@ fn main() { 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); // controls for general example let gen_controls = create_node_ref(); @@ -385,15 +387,18 @@ fn main() { 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 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 { - NAV_SPEED * Vector3::new(-pitch, yaw, 0.0).normalize() + if pitch != 0.0 || yaw != 0.0 || roll != 0.0 { + NAV_SPEED * Vector3::new(-pitch, yaw, roll).normalize() } else { Vector3::zeros() }; @@ -485,6 +490,8 @@ fn main() { || 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 || turntable_val ); } else { @@ -500,6 +507,8 @@ fn main() { match event.key().as_str() { "ArrowUp" => pitch_up.set(value), "ArrowDown" => pitch_down.set(value), + "ArrowRight" if event.shift_key() => roll_cw.set(value), + "ArrowLeft" if event.shift_key() => roll_ccw.set(value), "ArrowRight" => yaw_right.set(value), "ArrowLeft" => yaw_left.set(value), _ => navigating = false @@ -538,13 +547,33 @@ fn main() { width=600, height=600, tabindex=0, - on:keydown=move |event: KeyboardEvent| { set_nav_signal(event, 1.0); }, - on:keyup=move |event: KeyboardEvent| { set_nav_signal(event, 0.0); }, + on:keydown=move |event: KeyboardEvent| { + if event.key() == "Shift" { + roll_cw.set(yaw_right.get()); + roll_ccw.set(yaw_left.get()); + yaw_right.set(0.0); + yaw_left.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()); + roll_cw.set(0.0); + roll_ccw.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) { From d3c9a08d22571fd5b28724fea9adf25d35f93cc0 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Tue, 10 Sep 2024 04:08:49 -0700 Subject: [PATCH 4/5] Add zoom to keyboard controls --- app-proto/inversive-display/src/main.rs | 52 ++++++++++++++++++------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 350c81c..d351149 100644 --- a/app-proto/inversive-display/src/main.rs +++ b/app-proto/inversive-display/src/main.rs @@ -172,6 +172,8 @@ fn main() { 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(); @@ -262,21 +264,12 @@ fn main() { let mut last_time = 0.0; // scene parameters - const NAV_SPEED: f64 = 0.4; // in radians per second + 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 location = { - 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 mut location_z: f64 = 5.0; /* INSTRUMENTS */ let performance = window().unwrap().performance().unwrap(); @@ -389,6 +382,8 @@ fn main() { 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 @@ -398,7 +393,7 @@ fn main() { let roll = roll_ccw_val - roll_cw_val; let ang_vel_from_keyboard = if pitch != 0.0 || yaw != 0.0 || roll != 0.0 { - NAV_SPEED * Vector3::new(-pitch, yaw, roll).normalize() + ROT_SPEED * Vector3::new(-pitch, yaw, roll).normalize() } else { Vector3::zeros() }; @@ -416,6 +411,10 @@ fn main() { ); 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 @@ -427,6 +426,16 @@ fn main() { } // 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 @@ -492,6 +501,8 @@ fn main() { || 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 { @@ -504,11 +515,14 @@ fn main() { 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 event.shift_key() => roll_cw.set(value), - "ArrowLeft" if event.shift_key() => roll_ccw.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 @@ -551,8 +565,12 @@ fn main() { 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); } @@ -561,8 +579,12 @@ fn main() { 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); } From 336b940471a18332d692b3b637c34110b8bdd1a1 Mon Sep 17 00:00:00 2001 From: Aaron Fenyes Date: Thu, 12 Sep 2024 15:24:41 -0700 Subject: [PATCH 5/5] Outline: start on editor state and outline view --- app-proto/sketch-outline/.gitignore | 3 ++ app-proto/sketch-outline/Cargo.toml | 29 ++++++++++++ app-proto/sketch-outline/index.html | 9 ++++ app-proto/sketch-outline/main.css | 50 +++++++++++++++++++++ app-proto/sketch-outline/src/editor.rs | 61 ++++++++++++++++++++++++++ app-proto/sketch-outline/src/main.rs | 13 ++++++ 6 files changed, 165 insertions(+) create mode 100644 app-proto/sketch-outline/.gitignore create mode 100644 app-proto/sketch-outline/Cargo.toml create mode 100644 app-proto/sketch-outline/index.html create mode 100644 app-proto/sketch-outline/main.css create mode 100644 app-proto/sketch-outline/src/editor.rs create mode 100644 app-proto/sketch-outline/src/main.rs diff --git a/app-proto/sketch-outline/.gitignore b/app-proto/sketch-outline/.gitignore new file mode 100644 index 0000000..238273d --- /dev/null +++ b/app-proto/sketch-outline/.gitignore @@ -0,0 +1,3 @@ +target +dist +Cargo.lock \ No newline at end of file diff --git a/app-proto/sketch-outline/Cargo.toml b/app-proto/sketch-outline/Cargo.toml new file mode 100644 index 0000000..522c994 --- /dev/null +++ b/app-proto/sketch-outline/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "sketch-outline" +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.3" + +# 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" + +[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/sketch-outline/index.html b/app-proto/sketch-outline/index.html new file mode 100644 index 0000000..5474fe9 --- /dev/null +++ b/app-proto/sketch-outline/index.html @@ -0,0 +1,9 @@ + + + + + Sketch outline + + + + diff --git a/app-proto/sketch-outline/main.css b/app-proto/sketch-outline/main.css new file mode 100644 index 0000000..2e6aedc --- /dev/null +++ b/app-proto/sketch-outline/main.css @@ -0,0 +1,50 @@ +body { + margin-left: 20px; + margin-top: 20px; + color: #fcfcfc; + background-color: #222; +} + +ul { + width: 450px; + padding: 8px; + border: 1px solid #888; + border-radius: 16px; +} + +li { + display: flex; + padding: 3px; + list-style-type: none; + background-color: #444; + border-radius: 8px; +} + +li:not(:last-child) { + margin-bottom: 8px; +} + +li > .elt-name { + flex-grow: 1; + padding: 2px 0px 2px 4px; +} + +li > .elt-rep { + display: flex; +} + +li > .elt-rep > div { + padding: 2px; + margin-left: 3px; + text-align: center; + width: 60px; + background-color: #333; +} + +li > .elt-rep > div:first-child { + border-radius: 6px 0px 0px 6px; +} + +li > .elt-rep > div:last-child { + border-radius: 0px 6px 6px 0px; +} \ No newline at end of file diff --git a/app-proto/sketch-outline/src/editor.rs b/app-proto/sketch-outline/src/editor.rs new file mode 100644 index 0000000..b0d3f2e --- /dev/null +++ b/app-proto/sketch-outline/src/editor.rs @@ -0,0 +1,61 @@ +use nalgebra::DVector; +use sycamore::{prelude::*, web::tags::div}; + +#[derive(Clone, PartialEq)] +struct Element { + id: i64, + name: String, + rep: DVector, + color: [f32; 3] +} + +struct EditorState { + elements: Signal> +} + +#[component] +pub fn Editor() -> View { + let state = EditorState { + elements: create_signal(vec![ + Element { + id: 1, + name: String::from("Central"), + rep: DVector::::from_column_slice(&[0.0, 0.0, 0.0, 0.25, -1.0]), + color: [0.75_f32, 0.75_f32, 0.75_f32] + }, + Element { + id: 2, + name: String::from("Wing A"), + rep: DVector::::from_column_slice(&[0.5, 0.5, 0.0, 0.5, -0.25]), + color: [1.00_f32, 0.25_f32, 0.00_f32] + }, + Element { + id: 3, + name: String::from("Wing B"), + rep: DVector::::from_column_slice(&[-0.5, -0.5, 0.0, 0.5, -0.25]), + color: [1.00_f32, 0.25_f32, 0.00_f32] + } + ]) + }; + + view! { + ul { + Keyed( + list=state.elements, + view=|elt| { + let name = elt.name.clone(); + let rep_components = elt.rep.iter().map( + |u| View::from(div().children(u.to_string().replace("-", "\u{2212}"))) + ).collect::>(); + view! { + li { + div(class="elt-name") { (name) } + div(class="elt-rep") { (rep_components) } + } + } + }, + key=|elt| elt.id + ) + } + } +} \ No newline at end of file diff --git a/app-proto/sketch-outline/src/main.rs b/app-proto/sketch-outline/src/main.rs new file mode 100644 index 0000000..8b7ce30 --- /dev/null +++ b/app-proto/sketch-outline/src/main.rs @@ -0,0 +1,13 @@ +use sycamore::prelude::*; + +mod editor; + +use editor::Editor; + +fn main() { + sycamore::render(|| { + view! { + Editor {} + } + }); +} \ No newline at end of file