Compare commits

..

No commits in common. "18ebf3be2c8a9caf0cdec3338bd6ecb1333a144e" and "f5486fb0dd2c4f5f59ea52782af464c2faa4c714" have entirely different histories.

6 changed files with 77 additions and 317 deletions

View File

@ -1,227 +1,21 @@
use std::collections::BTreeSet; /* DEBUG */
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; use web_sys::{MouseEvent, console, wasm_bindgen::JsValue};
use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}}; use crate::AppState;
use crate::Constraint;
/* DEBUG */
fn load_gen_assemb(assembly: &Assembly) {
let _ = assembly.try_insert_element(
Element {
id: String::from("gemini_a"),
label: String::from("Castor"),
color: [1.00_f32, 0.25_f32, 0.00_f32],
rep: engine::sphere(0.5, 0.5, 0.0, 1.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: String::from("gemini_b"),
label: String::from("Pollux"),
color: [0.00_f32, 0.25_f32, 1.00_f32],
rep: engine::sphere(-0.5, -0.5, 0.0, 1.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: String::from("ursa_major"),
label: String::from("Ursa major"),
color: [0.25_f32, 0.00_f32, 1.00_f32],
rep: engine::sphere(-0.5, 0.5, 0.0, 0.75),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: String::from("ursa_minor"),
label: String::from("Ursa minor"),
color: [0.25_f32, 1.00_f32, 0.00_f32],
rep: engine::sphere(0.5, -0.5, 0.0, 0.5),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: String::from("moon_deimos"),
label: String::from("Deimos"),
color: [0.75_f32, 0.75_f32, 0.00_f32],
rep: engine::sphere(0.0, 0.15, 1.0, 0.25),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: String::from("moon_phobos"),
label: String::from("Phobos"),
color: [0.00_f32, 0.75_f32, 0.50_f32],
rep: engine::sphere(0.0, -0.15, -1.0, 0.25),
constraints: BTreeSet::default()
}
);
assembly.insert_constraint(
Constraint {
args: (
assembly.elements_by_id.with_untracked(|elts_by_id| elts_by_id["gemini_a"]),
assembly.elements_by_id.with_untracked(|elts_by_id| elts_by_id["gemini_b"])
),
rep: 0.5
}
);
}
/* DEBUG */
fn load_low_curv_assemb(assembly: &Assembly) {
let a = 0.75_f64.sqrt();
let _ = assembly.try_insert_element(
Element {
id: "central".to_string(),
label: "Central".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(0.0, 0.0, 0.0, 1.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: "assemb_plane".to_string(),
label: "Assembly plane".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: "side1".to_string(),
label: "Side 1".to_string(),
color: [1.00_f32, 0.00_f32, 0.25_f32],
rep: engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: "side2".to_string(),
label: "Side 2".to_string(),
color: [0.25_f32, 1.00_f32, 0.00_f32],
rep: engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: "side3".to_string(),
label: "Side 3".to_string(),
color: [0.00_f32, 0.25_f32, 1.00_f32],
rep: engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: "corner1".to_string(),
label: "Corner 1".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: "corner2".to_string(),
label: "Corner 2".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
constraints: BTreeSet::default()
}
);
let _ = assembly.try_insert_element(
Element {
id: String::from("corner3"),
label: String::from("Corner 3"),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
constraints: BTreeSet::default()
}
);
}
#[component] #[component]
pub fn AddRemove() -> View { pub fn AddRemove() -> View {
/* DEBUG */
let assembly_name = create_signal("general".to_string());
create_effect(move || {
// get name of chosen assembly
let name = assembly_name.get_clone();
console::log_1(
&JsValue::from(format!("Showing assembly \"{}\"", name.clone()))
);
batch(|| {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let assembly = &state.assembly;
// clear state
assembly.elements.update(|elts| elts.clear());
assembly.elements_by_id.update(|elts_by_id| elts_by_id.clear());
state.selection.update(|sel| sel.clear());
// load assembly
match name.as_str() {
"general" => load_gen_assemb(assembly),
"low-curv" => load_low_curv_assemb(assembly),
_ => ()
};
});
});
view! { view! {
div(id="add-remove") { div(id="add-remove") {
button( button(
on:click=|_| { on:click=move |event: MouseEvent| {
let state = use_context::<AppState>();
state.assembly.insert_new_element();
/* DEBUG */
// print updated list of elements by identifier
console::log_1(&JsValue::from("elements by identifier:"));
for (id, key) in state.assembly.elements_by_id.get_clone().iter() {
console::log_3(
&JsValue::from(" "),
&JsValue::from(id),
&JsValue::from(*key)
);
}
}
) { "+" }
button(
disabled={
let state = use_context::<AppState>();
state.selection.with(|sel| sel.len() != 2)
},
on:click=|_| {
let state = use_context::<AppState>();
let args = state.selection.with(
|sel| {
let arg_vec: Vec<_> = sel.into_iter().collect();
(arg_vec[0].clone(), arg_vec[1].clone())
}
);
state.assembly.insert_constraint(Constraint {
args: args,
rep: 0.0
});
state.selection.update(|sel| sel.clear());
/* DEBUG */
// print updated constraint list
console::log_1(&JsValue::from("constraints:")); console::log_1(&JsValue::from("constraints:"));
state.assembly.constraints.with(|csts| { state.assembly.constraints.with(|csts| {
for (_, cst) in csts.into_iter() { for (_, cst) in csts.into_iter() {
console::log_5( console::log_4(
&JsValue::from(" "),
&JsValue::from(cst.args.0), &JsValue::from(cst.args.0),
&JsValue::from(cst.args.1), &JsValue::from(cst.args.1),
&JsValue::from(":"), &JsValue::from(":"),
@ -230,11 +24,32 @@ pub fn AddRemove() -> View {
} }
}); });
} }
) { "🔗" } ) { "+" }
select(bind:value=assembly_name) { /* DEBUG */ button(
option(value="general") { "General" } disabled={
option(value="low-curv") { "Low-curvature" } state.selection.with(|sel| sel.len() != 2)
},
on:click=move |event: MouseEvent| {
let args = state.selection.with(
|sel| {
let arg_vec: Vec<_> = sel.into_iter().collect();
(arg_vec[0].clone(), arg_vec[1].clone())
} }
);
console::log_5(
&JsValue::from("add constraint"),
&JsValue::from(args.0),
&JsValue::from(args.1),
&JsValue::from(":"),
&JsValue::from(0.0)
);
state.assembly.insert_constraint(Constraint {
args: args,
rep: 0.0
});
state.selection.update(|sel| sel.clear());
}
) { "🔗" }
} }
} }
} }

View File

@ -1,7 +1,6 @@
use nalgebra::DVector; use nalgebra::DVector;
use rustc_hash::FxHashMap; use rustc_hash::FxHashSet;
use slab::Slab; use slab::Slab;
use std::collections::BTreeSet;
use sycamore::prelude::*; use sycamore::prelude::*;
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
@ -10,7 +9,7 @@ pub struct Element {
pub label: String, pub label: String,
pub color: [f32; 3], pub color: [f32; 3],
pub rep: DVector<f64>, pub rep: DVector<f64>,
pub constraints: BTreeSet<usize> pub constraints: FxHashSet<usize>
} }
#[derive(Clone)] #[derive(Clone)]
@ -22,65 +21,18 @@ pub struct Constraint {
// a complete, view-independent description of an assembly // a complete, view-independent description of an assembly
#[derive(Clone)] #[derive(Clone)]
pub struct Assembly { pub struct Assembly {
// elements and constraints
pub elements: Signal<Slab<Element>>, pub elements: Signal<Slab<Element>>,
pub constraints: Signal<Slab<Constraint>>, pub constraints: Signal<Slab<Constraint>>
// indexing
pub elements_by_id: Signal<FxHashMap<String, usize>>
} }
impl Assembly { impl Assembly {
pub fn new() -> Assembly { pub fn new() -> Assembly {
Assembly { Assembly {
elements: create_signal(Slab::new()), elements: create_signal(Slab::new()),
constraints: create_signal(Slab::new()), constraints: create_signal(Slab::new())
elements_by_id: create_signal(FxHashMap::default())
} }
} }
// insert an element into the assembly without checking whether we already
// have an element with the same identifier. any element that does have the
// same identifier will get kicked out of the `elements_by_id` index
fn insert_element_unchecked(&self, elt: Element) {
let id = elt.id.clone();
let key = self.elements.update(|elts| elts.insert(elt));
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
}
pub fn try_insert_element(&self, elt: Element) -> bool {
let can_insert = self.elements_by_id.with_untracked(
|elts_by_id| !elts_by_id.contains_key(&elt.id)
);
if can_insert {
self.insert_element_unchecked(elt);
}
can_insert
}
pub fn insert_new_element(&self) {
// find the next unused identifier in the default sequence
let mut id_num = 1;
let mut id = format!("sphere{}", id_num);
while self.elements_by_id.with_untracked(
|elts_by_id| elts_by_id.contains_key(&id)
) {
id_num += 1;
id = format!("sphere{}", id_num);
}
// create and insert a new element
self.insert_element_unchecked(
Element {
id: id,
label: format!("Sphere {}", id_num),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]),
constraints: BTreeSet::default()
}
);
}
pub fn insert_constraint(&self, constraint: Constraint) { pub fn insert_constraint(&self, constraint: Constraint) {
let args = constraint.args; let args = constraint.args;
let key = self.constraints.update(|csts| csts.insert(constraint)); let key = self.constraints.update(|csts| csts.insert(constraint));

View File

@ -98,12 +98,10 @@ pub fn Display() -> View {
let roll_cw = create_signal(0.0); let roll_cw = create_signal(0.0);
let zoom_in = create_signal(0.0); let zoom_in = create_signal(0.0);
let zoom_out = create_signal(0.0); let zoom_out = create_signal(0.0);
let turntable = create_signal(false); /* BENCHMARKING */
// change listener // change listener
let scene_changed = create_signal(true); let scene_changed = create_signal(true);
create_effect(move || { create_effect(move || {
state.assembly.elements.track();
state.selection.track(); state.selection.track();
scene_changed.set(true); scene_changed.set(true);
}); });
@ -121,7 +119,6 @@ pub fn Display() -> View {
// viewpoint // viewpoint
const ROT_SPEED: f64 = 0.4; // in radians per second const ROT_SPEED: f64 = 0.4; // in radians per second
const ZOOM_SPEED: f64 = 0.15; // multiplicative rate per second const ZOOM_SPEED: f64 = 0.15; // multiplicative rate per second
const TURNTABLE_SPEED: f64 = 0.1; /* BENCHMARKING */
let mut orientation = DMatrix::<f64>::identity(5, 5); let mut orientation = DMatrix::<f64>::identity(5, 5);
let mut rotation = DMatrix::<f64>::identity(5, 5); let mut rotation = DMatrix::<f64>::identity(5, 5);
let mut location_z: f64 = 5.0; let mut location_z: f64 = 5.0;
@ -244,7 +241,6 @@ pub fn Display() -> View {
let roll_cw_val = roll_cw.get(); let roll_cw_val = roll_cw.get();
let zoom_in_val = zoom_in.get(); let zoom_in_val = zoom_in.get();
let zoom_out_val = zoom_out.get(); let zoom_out_val = zoom_out.get();
let turntable_val = turntable.get(); /* BENCHMARKING */
// update the assembly's orientation // update the assembly's orientation
let ang_vel = { let ang_vel = {
@ -256,10 +252,6 @@ pub fn Display() -> View {
} else { } else {
Vector3::zeros() Vector3::zeros()
} }
} /* BENCHMARKING */ + if turntable_val {
Vector3::new(0.0, TURNTABLE_SPEED, 0.0)
} else {
Vector3::zeros()
}; };
let mut rotation_sp = rotation.fixed_view_mut::<3, 3>(0, 0); let mut rotation_sp = rotation.fixed_view_mut::<3, 3>(0, 0);
rotation_sp.copy_from( rotation_sp.copy_from(
@ -359,7 +351,6 @@ pub fn Display() -> View {
|| roll_ccw_val != 0.0 || roll_ccw_val != 0.0
|| zoom_in_val != 0.0 || zoom_in_val != 0.0
|| zoom_out_val != 0.0 || zoom_out_val != 0.0
|| turntable_val /* BENCHMARKING */
); );
} else { } else {
frames_since_last_sample = 0; frames_since_last_sample = 0;
@ -409,10 +400,6 @@ pub fn Display() -> View {
pitch_up.set(0.0); pitch_up.set(0.0);
pitch_down.set(0.0); pitch_down.set(0.0);
} else { } else {
if event.key() == "Enter" { /* BENCHMARKING */
turntable.set_fn(|turn| !turn);
scene_changed.set(true);
}
set_nav_signal(event, 1.0); set_nav_signal(event, 1.0);
} }
}, },

View File

@ -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<f64> {
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<f64> {
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)
])
}

View File

@ -1,14 +1,14 @@
mod add_remove; mod add_remove;
mod assembly; mod assembly;
mod display; mod display;
mod engine;
mod outline; mod outline;
use nalgebra::DVector;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use sycamore::prelude::*; use sycamore::prelude::*;
use add_remove::AddRemove; use add_remove::AddRemove;
use assembly::Assembly; use assembly::{Assembly, Constraint, Element};
use display::Display; use display::Display;
use outline::Outline; use outline::Outline;
@ -29,7 +29,45 @@ impl AppState {
fn main() { fn main() {
sycamore::render(|| { sycamore::render(|| {
provide_context(AppState::new()); let state = AppState::new();
let key_a = state.assembly.elements.update(
|elts| elts.insert(
Element {
id: String::from("wing_a"),
label: String::from("Wing A"),
color: [1.00_f32, 0.25_f32, 0.00_f32],
rep: DVector::<f64>::from_column_slice(&[0.5, 0.5, 0.0, 0.5, -0.25]),
constraints: FxHashSet::default()
}
)
);
let key_b = state.assembly.elements.update(
|elts| elts.insert(
Element {
id: String::from("wing_b"),
label: String::from("Wing B"),
color: [0.00_f32, 0.25_f32, 1.00_f32],
rep: DVector::<f64>::from_column_slice(&[-0.5, -0.5, 0.0, 0.5, -0.25]),
constraints: FxHashSet::default()
},
)
);
state.assembly.elements.update(
|elts| elts.insert(
Element {
id: String::from("central"),
label: String::from("Central"),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.4, -0.625]),
constraints: FxHashSet::default()
}
)
);
state.assembly.insert_constraint(Constraint {
args: (key_a, key_b),
rep: 0.5
});
provide_context(state);
view! { view! {
div(id="sidebar") { div(id="sidebar") {

View File

@ -148,12 +148,7 @@ pub fn Outline() -> View {
} }
} }
}, },
key=|(key, elt)| ( key=|(key, _)| key.clone()
key.clone(),
elt.id.clone(),
elt.label.clone(),
elt.constraints.clone()
)
) )
} }
} }