diff --git a/app-proto/inversive-display/src/main.rs b/app-proto/inversive-display/src/main.rs index 3dd4653..d351149 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,16 @@ 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); + 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); @@ -246,8 +264,12 @@ fn main() { let mut last_time = 0.0; // scene parameters - const TURNTABLE_SPEED: f64 = 0.5; - let mut turntable_angle = 0.0; + 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(); @@ -353,11 +375,45 @@ 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 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(); - 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 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 */ @@ -369,30 +425,18 @@ 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(); + // find the map from construction space to world space + let location = { + let u = -location_z; 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 + 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 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; + let construction_to_world = &location * &orientation; // update the construction sphere_vec.clear(); @@ -450,7 +494,17 @@ 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 + || 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); @@ -459,6 +513,26 @@ fn main() { 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") { @@ -482,7 +556,48 @@ 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| { + 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" } 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