diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index ed05c16..14fcd41 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,59 +1,58 @@ -use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc}; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; use crate::{ engine, AppState, - assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere} + assembly::{Assembly, Element, InversiveDistanceRegulator} }; /* DEBUG */ // load an example assembly for testing. this code will be removed once we've // built a more formal test assembly system fn load_gen_assemb(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( String::from("gemini_a"), String::from("Castor"), [1.00_f32, 0.25_f32, 0.00_f32], engine::sphere(0.5, 0.5, 0.0, 1.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( String::from("gemini_b"), String::from("Pollux"), [0.00_f32, 0.25_f32, 1.00_f32], engine::sphere(-0.5, -0.5, 0.0, 1.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( String::from("ursa_major"), String::from("Ursa major"), [0.25_f32, 0.00_f32, 1.00_f32], engine::sphere(-0.5, 0.5, 0.0, 0.75) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( String::from("ursa_minor"), String::from("Ursa minor"), [0.25_f32, 1.00_f32, 0.00_f32], engine::sphere(0.5, -0.5, 0.0, 0.5) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( String::from("moon_deimos"), String::from("Deimos"), [0.75_f32, 0.75_f32, 0.00_f32], engine::sphere(0.0, 0.15, 1.0, 0.25) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( String::from("moon_phobos"), String::from("Phobos"), [0.00_f32, 0.75_f32, 0.50_f32], @@ -67,64 +66,64 @@ fn load_gen_assemb(assembly: &Assembly) { // built a more formal test assembly system fn load_low_curv_assemb(assembly: &Assembly) { let a = 0.75_f64.sqrt(); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( "central".to_string(), "Central".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], engine::sphere(0.0, 0.0, 0.0, 1.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( "assemb_plane".to_string(), "Assembly plane".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( "side1".to_string(), "Side 1".to_string(), [1.00_f32, 0.00_f32, 0.25_f32], engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( "side2".to_string(), "Side 2".to_string(), [0.25_f32, 1.00_f32, 0.00_f32], engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( "side3".to_string(), "Side 3".to_string(), [0.00_f32, 0.25_f32, 1.00_f32], engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( "corner1".to_string(), "Corner 1".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( "corner2".to_string(), "Corner 2".to_string(), [0.75_f32, 0.75_f32, 0.75_f32], engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) ) ); - let _ = assembly.try_insert_element( - Sphere::new( + let _ = assembly.try_insert_sphere( + Element::new( String::from("corner3"), String::from("Corner 3"), [0.75_f32, 0.75_f32, 0.75_f32], @@ -133,49 +132,6 @@ fn load_low_curv_assemb(assembly: &Assembly) { ); } -fn load_pointed_assemb(assembly: &Assembly) { - let _ = assembly.try_insert_element( - Point::new( - format!("point_front"), - format!("Front point"), - [0.875_f32, 0.875_f32, 0.875_f32], - engine::point(0.0, 0.0, FRAC_1_SQRT_2) - ) - ); - let _ = assembly.try_insert_element( - Point::new( - format!("point_back"), - format!("Back point"), - [0.875_f32, 0.875_f32, 0.875_f32], - engine::point(0.0, 0.0, -FRAC_1_SQRT_2) - ) - ); - for index_x in 0..=1 { - for index_y in 0..=1 { - let x = index_x as f64 - 0.5; - let y = index_y as f64 - 0.5; - - let _ = assembly.try_insert_element( - Sphere::new( - format!("sphere{index_x}{index_y}"), - format!("Sphere {index_x}{index_y}"), - [0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32], - engine::sphere(x, y, 0.0, 1.0) - ) - ); - - let _ = assembly.try_insert_element( - Point::new( - format!("point{index_x}{index_y}"), - format!("Point {index_x}{index_y}"), - [0.4*(2.0 + x) as f32, 0.4*(2.0 + y) as f32, 0.4*(2.0 - x*y) as f32], - engine::point(x, y, 0.0) - ) - ); - } - } -} - #[component] pub fn AddRemove() -> View { /* DEBUG */ @@ -201,7 +157,6 @@ pub fn AddRemove() -> View { match name.as_str() { "general" => load_gen_assemb(assembly), "low-curv" => load_low_curv_assemb(assembly), - "pointed" => load_pointed_assemb(assembly), _ => () }; }); @@ -212,7 +167,7 @@ pub fn AddRemove() -> View { button( on:click=|_| { let state = use_context::(); - state.assembly.insert_element_default::(); + state.assembly.insert_new_sphere(); } ) { "+" } button( @@ -235,7 +190,7 @@ pub fn AddRemove() -> View { .unwrap() ); state.assembly.insert_regulator( - Rc::new(InversiveDistanceRegulator::new(subjects, &state.assembly)) + InversiveDistanceRegulator::new(subjects, &state.assembly) ); state.selection.update(|sel| sel.clear()); } @@ -243,7 +198,6 @@ pub fn AddRemove() -> View { select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser option(value="general") { "General" } option(value="low-curv") { "Low-curvature" } - option(value="pointed") { "Pointed" } option(value="empty") { "Empty" } } } diff --git a/app-proto/src/assembly.rs b/app-proto/src/assembly.rs index 5cf1ba8..5c926ca 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -1,23 +1,15 @@ -use nalgebra::{DMatrix, DVector, DVectorView}; +use nalgebra::{DMatrix, DVector, DVectorView, Vector3}; use rustc_hash::FxHashMap; use slab::Slab; -use std::{ - any::{Any, TypeId}, - cell::Cell, - collections::BTreeSet, - rc::Rc, - sync::atomic::{AtomicU64, Ordering} -}; +use std::{collections::BTreeSet, rc::Rc, sync::atomic::{AtomicU64, Ordering}}; use sycamore::prelude::*; use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use crate::{ - display::DisplayItem, engine::{ Q, change_half_curvature, local_unif_to_std, - point, realize_gram, sphere, ConfigSubspace, @@ -41,87 +33,31 @@ pub type ElementColor = [f32; 3]; static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0); pub trait ProblemPoser { - fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab>); + fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab); } -pub trait Element: ProblemPoser + DisplayItem { - // the default identifier for an element of this type - fn default_id() -> String where Self: Sized; - - // create the default example of an element of this type - fn default(id: String, id_num: u64) -> Self where Self: Sized; - - // the regulators that should be created when an element of this type is - // inserted into the given assembly with the given storage key - /* KLUDGE */ - // right now, this organization makes sense because regulators identify - // their subjects by storage key, so the element has to be inserted before - // its regulators can be created. if we change the way regulators identify - // their subjects, we should consider refactoring - fn default_regulators(_key: ElementKey, _assembly: &Assembly) -> Vec> where Self: Sized { - Vec::new() - } - - fn id(&self) -> &String; - fn label(&self) -> &String; - fn representation(&self) -> Signal>; - - // the regulators the element is subject to. the assembly that owns the - // element is responsible for keeping this set up to date - fn regulators(&self) -> Signal>; - - // a serial number that uniquely identifies this element - fn serial(&self) -> u64; - - // take the next serial number, panicking if that was the last one left - fn next_serial() -> u64 where Self: Sized { - // the technique we use to panic on overflow is taken from _Rust Atomics - // and Locks_, by Mara Bos - // - // https://marabos.nl/atomics/atomics.html#example-handle-overflow - // - NEXT_ELEMENT_SERIAL.fetch_update( - Ordering::SeqCst, Ordering::SeqCst, - |serial| serial.checked_add(1) - ).expect("Out of serial numbers for elements") - } - - // the configuration matrix column index that was assigned to the element - // last time the assembly was realized, or `None` if the element has never - // been through a realization - fn column_index(&self) -> Option; - - // assign the element a configuration matrix column index. this method must - // be used carefully to preserve invariant (1), described in the comment on - // the `tangent` field of the `Assembly` structure - fn set_column_index(&self, index: usize); -} - -// the `Element` trait needs to be dyn-compatible, so its method signatures can -// only use `Self` in the type of the receiver. that means `Element` can't -// implement `PartialEq`. if you need partial equivalence for `Element` trait -// objects, use this wrapper -#[derive(Clone)] -pub struct ElementRc(pub Rc); - -impl PartialEq for ElementRc { - fn eq(&self, ElementRc(other): &Self) -> bool { - let ElementRc(rc) = self; - Rc::ptr_eq(rc, &other) - } -} - -pub struct Sphere { +#[derive(Clone, PartialEq)] +pub struct Element { pub id: String, pub label: String, pub color: ElementColor, pub representation: Signal>, + + // the regulators this element is subject to. the assembly that owns the + // element is responsible for keeping this set up to date pub regulators: Signal>, + + // a serial number, assigned by `Element::new`, that uniquely identifies + // each element pub serial: u64, - column_index: Cell> + + // the configuration matrix column index that was assigned to this element + // last time the assembly was realized, or `None` if the element has never + // been through a realization + column_index: Option } -impl Sphere { +impl Element { const CURVATURE_COMPONENT: usize = 3; pub fn new( @@ -129,161 +65,83 @@ impl Sphere { label: String, color: ElementColor, representation: DVector - ) -> Sphere { - Sphere { + ) -> Element { + // take the next serial number, panicking if that was the last number we + // had left. the technique we use to panic on overflow is taken from + // _Rust Atomics and Locks_, by Mara Bos + // + // https://marabos.nl/atomics/atomics.html#example-handle-overflow + // + let serial = NEXT_ELEMENT_SERIAL.fetch_update( + Ordering::SeqCst, Ordering::SeqCst, + |serial| serial.checked_add(1) + ).expect("Out of serial numbers for elements"); + + Element { id: id, label: label, color: color, representation: create_signal(representation), regulators: create_signal(BTreeSet::default()), - serial: Self::next_serial(), - column_index: None.into() + serial: serial, + column_index: None + } + } + + // the smallest positive depth, represented as a multiple of `dir`, where + // the line generated by `dir` hits the element (which is assumed to be a + // sphere). returns `None` if the line misses the sphere. this function + // should be kept synchronized with `sphere_cast` in `inversive.frag`, which + // does essentially the same thing on the GPU side + pub fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix) -> Option { + // if `a/b` is less than this threshold, we approximate + // `a*u^2 + b*u + c` by the linear function `b*u + c` + const DEG_THRESHOLD: f64 = 1e-9; + + let rep = self.representation.with_untracked(|rep| assembly_to_world * rep); + let a = -rep[3] * dir.norm_squared(); + let b = rep.rows_range(..3).dot(&dir); + let c = -rep[4]; + + let adjust = 4.0*a*c/(b*b); + if adjust < 1.0 { + // 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` + let square_rect_ratio = 1.0 + (1.0 - adjust).sqrt(); + let lin_root = -(2.0*c)/b / square_rect_ratio; + if a.abs() > DEG_THRESHOLD * b.abs() { + if lin_root > 0.0 { + Some(lin_root) + } else { + let other_root = -b/(2.*a) * square_rect_ratio; + (other_root > 0.0).then_some(other_root) + } + } else { + (lin_root > 0.0).then_some(lin_root) + } + } else { + // the line through `dir` misses the sphere completely + None } } } -impl Element for Sphere { - fn default_id() -> String { - "sphere".to_string() - } - - fn default(id: String, id_num: u64) -> Sphere { - Sphere::new( - id, - format!("Sphere {id_num}"), - [0.75_f32, 0.75_f32, 0.75_f32], - sphere(0.0, 0.0, 0.0, 1.0) - ) - } - - fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec> { - vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))] - } - - fn id(&self) -> &String { - &self.id - } - - fn label(&self) -> &String { - &self.label - } - - fn representation(&self) -> Signal> { - self.representation - } - - fn regulators(&self) -> Signal> { - self.regulators - } - - fn serial(&self) -> u64 { - self.serial - } - - fn column_index(&self) -> Option { - self.column_index.get() - } - - fn set_column_index(&self, index: usize) { - self.column_index.set(Some(index)); - } -} - -impl ProblemPoser for Sphere { - fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab>) { - let index = self.column_index().expect( - format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str() +impl ProblemPoser for Element { + fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab) { + let index = self.column_index.expect( + format!("Element \"{}\" should be indexed before writing problem data", self.id).as_str() ); problem.gram.push_sym(index, index, 1.0); problem.guess.set_column(index, &self.representation.get_clone_untracked()); } } -pub struct Point { - pub id: String, - pub label: String, - pub color: ElementColor, - pub representation: Signal>, - pub regulators: Signal>, - pub serial: u64, - column_index: Cell> -} - -impl Point { - const WEIGHT_COMPONENT: usize = 3; - - pub fn new( - id: String, - label: String, - color: ElementColor, - representation: DVector - ) -> Point { - Point { - id, - label, - color, - representation: create_signal(representation), - regulators: create_signal(BTreeSet::default()), - serial: Self::next_serial(), - column_index: None.into() - } - } -} - -impl Element for Point { - fn default_id() -> String { - "point".to_string() - } - - fn default(id: String, id_num: u64) -> Point { - Point::new( - id, - format!("Point {id_num}"), - [0.875_f32, 0.875_f32, 0.875_f32], - point(0.0, 0.0, 0.0) - ) - } - - fn id(&self) -> &String { - &self.id - } - - fn label(&self) -> &String { - &self.label - } - - fn representation(&self) -> Signal> { - self.representation - } - - fn regulators(&self) -> Signal> { - self.regulators - } - - fn serial(&self) -> u64 { - self.serial - } - - fn column_index(&self) -> Option { - self.column_index.get() - } - - fn set_column_index(&self, index: usize) { - self.column_index.set(Some(index)); - } -} - -impl ProblemPoser for Point { - fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab>) { - let index = self.column_index().expect( - format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str() - ); - problem.gram.push_sym(index, index, 0.0); - problem.frozen.push(Point::WEIGHT_COMPONENT, index, 0.5); - problem.guess.set_column(index, &self.representation.get_clone_untracked()); - } -} - pub trait Regulator: ProblemPoser + OutlineItem { fn subjects(&self) -> Vec; fn measurement(&self) -> ReadSignal; @@ -310,7 +168,7 @@ impl InversiveDistanceRegulator { pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator { let measurement = assembly.elements.map( move |elts| { - let representations = subjects.map(|subj| elts[subj].representation()); + let representations = subjects.map(|subj| elts[subj].representation); representations[0].with(|rep_0| representations[1].with(|rep_1| rep_0.dot(&(&*Q * rep_1)) @@ -340,11 +198,11 @@ impl Regulator for InversiveDistanceRegulator { } impl ProblemPoser for InversiveDistanceRegulator { - fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab>) { + fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab) { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { let [row, col] = self.subjects.map( - |subj| elts[subj].column_index().expect( + |subj| elts[subj].column_index.expect( "Subjects should be indexed before inversive distance regulator writes problem data" ) ); @@ -363,8 +221,8 @@ pub struct HalfCurvatureRegulator { impl HalfCurvatureRegulator { pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator { let measurement = assembly.elements.map( - move |elts| elts[subject].representation().with( - |rep| rep[Sphere::CURVATURE_COMPONENT] + move |elts| elts[subject].representation.with( + |rep| rep[Element::CURVATURE_COMPONENT] ) ); @@ -391,7 +249,7 @@ impl Regulator for HalfCurvatureRegulator { match self.set_point.with(|set_pt| set_pt.value) { Some(half_curv) => { let representation = assembly.elements.with_untracked( - |elts| elts[self.subject].representation() + |elts| elts[self.subject].representation ); representation.update( |rep| change_half_curvature(rep, half_curv) @@ -404,13 +262,13 @@ impl Regulator for HalfCurvatureRegulator { } impl ProblemPoser for HalfCurvatureRegulator { - fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab>) { + fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab) { self.set_point.with_untracked(|set_pt| { if let Some(val) = set_pt.value { - let col = elts[self.subject].column_index().expect( + let col = elts[self.subject].column_index.expect( "Subject should be indexed before half-curvature regulator writes problem data" ); - problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val); + problem.frozen.push(Element::CURVATURE_COMPONENT, col, val); } }); } @@ -428,7 +286,7 @@ type AssemblyMotion<'a> = Vec>; #[derive(Clone)] pub struct Assembly { // elements and regulators - pub elements: Signal>>, + pub elements: Signal>, pub regulators: Signal>>, // solution variety tangent space. the basis vectors are stored in @@ -459,61 +317,66 @@ impl Assembly { // --- inserting elements and regulators --- - // insert an element into the assembly without checking whether we already + // insert a sphere 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: T) -> ElementKey { - // insert the element - let id = elt.id().clone(); - let key = self.elements.update(|elts| elts.insert(Rc::new(elt))); + fn insert_sphere_unchecked(&self, elt: Element) -> ElementKey { + // insert the sphere + 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)); - // create and insert the element's default regulators - for reg in T::default_regulators(key, &self) { - self.insert_regulator(reg); - } + // regulate the sphere's curvature + self.insert_regulator(HalfCurvatureRegulator::new(key, &self)); key } - pub fn try_insert_element(&self, elt: impl Element + 'static) -> Option { + pub fn try_insert_sphere(&self, elt: Element) -> Option { let can_insert = self.elements_by_id.with_untracked( - |elts_by_id| !elts_by_id.contains_key(elt.id()) + |elts_by_id| !elts_by_id.contains_key(&elt.id) ); if can_insert { - Some(self.insert_element_unchecked(elt)) + Some(self.insert_sphere_unchecked(elt)) } else { None } } - pub fn insert_element_default(&self) { + pub fn insert_new_sphere(&self) { // find the next unused identifier in the default sequence - let default_id = T::default_id(); let mut id_num = 1; - let mut id = format!("{default_id}{id_num}"); + 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!("{default_id}{id_num}"); + id = format!("sphere{}", id_num); } - // create and insert the default example of `T` - let _ = self.insert_element_unchecked(T::default(id, id_num)); + // create and insert a sphere + let _ = self.insert_sphere_unchecked( + Element::new( + id, + format!("Sphere {}", id_num), + [0.75_f32, 0.75_f32, 0.75_f32], + sphere(0.0, 0.0, 0.0, 1.0) + ) + ); } - pub fn insert_regulator(&self, regulator: Rc) { + pub fn insert_regulator(&self, regulator: T) { // add the regulator to the assembly's regulator list + let regulator_rc = Rc::new(regulator); let key = self.regulators.update( - |regs| regs.insert(regulator.clone()) + |regs| regs.insert(regulator_rc.clone()) ); // add the regulator to each subject's regulator list - let subjects = regulator.subjects(); + let subjects = regulator_rc.subjects(); let subject_regulators: Vec<_> = self.elements.with_untracked( |elts| subjects.into_iter().map( - |subj| elts[subj].regulators() + |subj| elts[subj].regulators ).collect() ); for regulators in subject_regulators { @@ -527,10 +390,10 @@ impl Assembly { /* DEBUG */ // log the regulator update console::log_1(&JsValue::from( - format!("Updated regulator with subjects {:?}", regulator.subjects()) + format!("Updated regulator with subjects {:?}", regulator_rc.subjects()) )); - if regulator.try_activate(&self_for_effect) { + if regulator_rc.try_activate(&self_for_effect) { self_for_effect.realize(); } }); @@ -564,7 +427,7 @@ impl Assembly { // index the elements self.elements.update_silent(|elts| { for (index, (_, elt)) in elts.into_iter().enumerate() { - elt.set_column_index(index); + elt.column_index = Some(index); } }); @@ -619,8 +482,8 @@ impl Assembly { if success { // read out the solution for (_, elt) in self.elements.get_clone_untracked() { - elt.representation().update( - |rep| rep.set_column(0, &config.column(elt.column_index().unwrap())) + elt.representation.update( + |rep| rep.set_column(0, &config.column(elt.column_index.unwrap())) ); } @@ -658,8 +521,8 @@ impl Assembly { let mut next_column_index = realized_dim; for elt_motion in motion.iter() { let moving_elt = &mut elts[elt_motion.key]; - if moving_elt.column_index().is_none() { - moving_elt.set_column_index(next_column_index); + if moving_elt.column_index.is_none() { + moving_elt.column_index = Some(next_column_index); next_column_index += 1; } } @@ -676,7 +539,7 @@ impl Assembly { // we can unwrap the column index because we know that every moving // element has one at this point let column_index = self.elements.with_untracked( - |elts| elts[elt_motion.key].column_index().unwrap() + |elts| elts[elt_motion.key].column_index.unwrap() ); if column_index < realized_dim { @@ -692,7 +555,7 @@ impl Assembly { let mut target_column = motion_proj.column_mut(column_index); let unif_to_std = self.elements.with_untracked( |elts| { - elts[elt_motion.key].representation().with_untracked( + elts[elt_motion.key].representation.with_untracked( |rep| local_unif_to_std(rep.as_view()) ) } @@ -704,27 +567,26 @@ impl Assembly { // step the assembly along the deformation. this changes the elements' // normalizations, so we restore those afterward /* KLUDGE */ - // for now, we only restore the normalizations of spheres + // since our test assemblies only include spheres, we assume that every + // element is on the 1 mass shell for (_, elt) in self.elements.get_clone_untracked() { - elt.representation().update_silent(|rep| { - match elt.column_index() { + elt.representation.update_silent(|rep| { + match elt.column_index { Some(column_index) => { // step the assembly along the deformation *rep += motion_proj.column(column_index); - if elt.type_id() == TypeId::of::() { - // restore normalization by contracting toward the - // last coordinate axis - let q_sp = rep.fixed_rows::<3>(0).norm_squared(); - let half_q_lt = -2.0 * rep[3] * rep[4]; - let half_q_lt_sq = half_q_lt * half_q_lt; - let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt(); - rep.fixed_rows_mut::<4>(0).scale_mut(1.0 / scaling); - } + // restore normalization by contracting toward the last + // coordinate axis + let q_sp = rep.fixed_rows::<3>(0).norm_squared(); + let half_q_lt = -2.0 * rep[3] * rep[4]; + let half_q_lt_sq = half_q_lt * half_q_lt; + let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt(); + rep.fixed_rows_mut::<4>(0).scale_mut(1.0 / scaling); }, None => { console::log_1(&JsValue::from( - format!("No velocity to unpack for fresh element \"{}\"", elt.id()) + format!("No velocity to unpack for fresh element \"{}\"", elt.id) )) } }; @@ -740,14 +602,20 @@ impl Assembly { #[cfg(test)] mod tests { + use crate::engine; + use super::*; #[test] - #[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")] + #[should_panic(expected = "Element \"sphere\" should be indexed before writing problem data")] fn unindexed_element_test() { let _ = create_root(|| { - let elt = Sphere::default("sphere".to_string(), 0); - elt.pose(&mut ConstraintProblem::new(1), &Slab::new()); + Element::new( + "sphere".to_string(), + "Sphere".to_string(), + [1.0_f32, 1.0_f32, 1.0_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ).pose(&mut ConstraintProblem::new(1), &Slab::new()); }); } @@ -755,13 +623,18 @@ mod tests { #[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")] fn unindexed_subject_test_inversive_distance() { let _ = create_root(|| { - let mut elts = Slab::>::new(); + let mut elts = Slab::new(); let subjects = [0, 1].map(|k| { elts.insert( - Rc::new(Sphere::default(format!("sphere{k}"), k)) + Element::new( + format!("sphere{k}"), + format!("Sphere {k}"), + [1.0_f32, 1.0_f32, 1.0_f32], + engine::sphere(0.0, 0.0, 0.0, 1.0) + ) ) }); - elts[subjects[0]].set_column_index(0); + elts[subjects[0]].column_index = Some(0); InversiveDistanceRegulator { subjects: subjects, measurement: create_memo(|| 0.0), diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index 154d495..903193c 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -4,154 +4,17 @@ use sycamore::{prelude::*, motion::create_raf}; use web_sys::{ console, window, + Element, KeyboardEvent, MouseEvent, WebGl2RenderingContext, - WebGlBuffer, WebGlProgram, WebGlShader, WebGlUniformLocation, wasm_bindgen::{JsCast, JsValue} }; -use crate::{ - AppState, - assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere} -}; - -// --- scene data --- - -struct SceneSpheres { - representations: Vec>, - colors: Vec, - highlights: Vec -} - -impl SceneSpheres { - fn new() -> SceneSpheres{ - SceneSpheres { - representations: Vec::new(), - colors: Vec::new(), - highlights: Vec::new() - } - } - - fn len_i32(&self) -> i32 { - self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer") - } - - fn push(&mut self, representation: DVector, color: ElementColor, highlight: f32) { - self.representations.push(representation); - self.colors.push(color); - self.highlights.push(highlight); - } -} - -struct ScenePoints { - representations: Vec>, - colors: Vec, -} - -impl ScenePoints { - fn new() -> ScenePoints { - ScenePoints { - representations: Vec::new(), - colors: Vec::new() - } - } - - fn push(&mut self, representation: DVector, color: ElementColor) { - self.representations.push(representation); - self.colors.push(color); - } -} - -pub struct Scene { - spheres: SceneSpheres, - points: ScenePoints -} - -impl Scene { - fn new() -> Scene { - Scene { - spheres: SceneSpheres::new(), - points: ScenePoints::new() - } - } -} - -pub trait DisplayItem { - fn show(&self, scene: &mut Scene, selected: bool); - - // the smallest positive depth, represented as a multiple of `dir`, where - // the line generated by `dir` hits the element. returns `None` if the line - // misses the element - fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix) -> Option; -} - -impl DisplayItem for Sphere { - fn show(&self, scene: &mut Scene, selected: bool) { - const HIGHLIGHT: f32 = 0.2; /* SCAFFOLDING */ - let representation = self.representation.get_clone_untracked(); - let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color }; - let highlight = if selected { 1.0 } else { HIGHLIGHT }; - scene.spheres.push(representation, color, highlight); - } - - // this method should be kept synchronized with `sphere_cast` in - // `spheres.frag`, which does essentially the same thing on the GPU side - fn cast(&self, dir: Vector3, assembly_to_world: &DMatrix) -> Option { - // if `a/b` is less than this threshold, we approximate - // `a*u^2 + b*u + c` by the linear function `b*u + c` - const DEG_THRESHOLD: f64 = 1e-9; - - let rep = self.representation.with_untracked(|rep| assembly_to_world * rep); - let a = -rep[3] * dir.norm_squared(); - let b = rep.rows_range(..3).dot(&dir); - let c = -rep[4]; - - let adjust = 4.0*a*c/(b*b); - if adjust < 1.0 { - // 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` - let square_rect_ratio = 1.0 + (1.0 - adjust).sqrt(); - let lin_root = -(2.0*c)/b / square_rect_ratio; - if a.abs() > DEG_THRESHOLD * b.abs() { - if lin_root > 0.0 { - Some(lin_root) - } else { - let other_root = -b/(2.*a) * square_rect_ratio; - (other_root > 0.0).then_some(other_root) - } - } else { - (lin_root > 0.0).then_some(lin_root) - } - } else { - // the line through `dir` misses the sphere completely - None - } - } -} - -impl DisplayItem for Point { - fn show(&self, scene: &mut Scene, _selected: bool) { - let representation = self.representation.get_clone_untracked(); - scene.points.push(representation, self.color); - } - - /* SCAFFOLDING */ - fn cast(&self, _dir: Vector3, _assembly_to_world: &DMatrix) -> Option { - None - } -} - -// --- WebGL utilities --- +use crate::{AppState, assembly::{ElementKey, ElementMotion}}; fn compile_shader( context: &WebGl2RenderingContext, @@ -164,7 +27,7 @@ fn compile_shader( shader } -fn set_up_program( +fn create_program_with_shaders( context: &WebGl2RenderingContext, vertex_shader_source: &str, fragment_shader_source: &str @@ -218,39 +81,22 @@ fn get_uniform_array_locations( }) } -// bind the given vertex buffer object to the given vertex attribute -fn bind_to_attribute( - context: &WebGl2RenderingContext, - attr_index: u32, - attr_size: i32, - buffer: &Option -) { - context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref()); - context.vertex_attrib_pointer_with_i32( - attr_index, - attr_size, - WebGl2RenderingContext::FLOAT, - false, // don't normalize - 0, // zero stride - 0, // zero offset - ); -} - -// load the given data into a new vertex buffer object -fn load_new_buffer( +// load the given data into the vertex input of the given name +fn bind_vertex_attrib( context: &WebGl2RenderingContext, + index: u32, + size: i32, data: &[f32] -) -> Option { - // create a buffer and bind it to ARRAY_BUFFER - let buffer = context.create_buffer(); - context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref()); +) { + // 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. this block is unsafe because - // `Float32Array::view` creates a raw view into our module's - // `WebAssembly.Memory` buffer. allocating more memory will change the - // buffer, invalidating the view, so we have to make sure we don't allocate - // any memory until the view is dropped. we're okay here because the view is - // used as soon as it's created + // 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, @@ -259,29 +105,33 @@ fn load_new_buffer( ); } - buffer -} - -fn bind_new_buffer_to_attribute( - context: &WebGl2RenderingContext, - attr_index: u32, - attr_size: i32, - data: &[f32] -) { - let buffer = load_new_buffer(context, data); - bind_to_attribute(context, attr_index, attr_size, &buffer); + // 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 + ); } // the direction in camera space that a mouse event is pointing along fn event_dir(event: &MouseEvent) -> Vector3 { - let target: web_sys::Element = event.target().unwrap().unchecked_into(); + let target: Element = event.target().unwrap().unchecked_into(); let rect = target.get_bounding_client_rect(); let width = rect.width(); let height = rect.height(); let shortdim = width.min(height); - // this constant should be kept synchronized with `spheres.frag` and - // `point.vert` + // this constant should be kept synchronized with `inversive.frag` const FOCAL_SLOPE: f64 = 0.3; Vector3::new( @@ -291,8 +141,6 @@ fn event_dir(event: &MouseEvent) -> Vector3 { ) } -// --- display component --- - #[component] pub fn Display() -> View { let state = use_context::(); @@ -329,7 +177,7 @@ pub fn Display() -> View { create_effect(move || { state.assembly.elements.with(|elts| { for (_, elt) in elts { - elt.representation().track(); + elt.representation.track(); } }); state.selection.track(); @@ -361,6 +209,7 @@ pub fn Display() -> View { // display parameters const OPACITY: f32 = 0.5; /* SCAFFOLDING */ + const HIGHLIGHT: f32 = 0.2; /* SCAFFOLDING */ const LAYER_THRESHOLD: i32 = 0; /* DEBUG */ const DEBUG_MODE: i32 = 0; /* DEBUG */ @@ -376,22 +225,13 @@ pub fn Display() -> View { .dyn_into::() .unwrap(); - // disable depth testing - ctx.disable(WebGl2RenderingContext::DEPTH_TEST); - - // set up the sphere rendering program - let sphere_program = set_up_program( + // create and use the rendering program + let program = create_program_with_shaders( &ctx, include_str!("identity.vert"), - include_str!("spheres.frag") - ); - - // set up the point rendering program - let point_program = set_up_program( - &ctx, - include_str!("point.vert"), - include_str!("point.frag") + include_str!("inversive.frag") ); + ctx.use_program(Some(&program)); /* DEBUG */ // print the maximum number of vectors that can be passed as @@ -410,33 +250,31 @@ pub fn Display() -> View { &JsValue::from("uniform vectors available") ); - // find the sphere program's vertex attribute - let viewport_position_attr = ctx.get_attrib_location(&sphere_program, "position") as u32; - - // find the sphere program's uniforms + // find indices of vertex attributes and uniforms const SPHERE_MAX: usize = 200; - let sphere_cnt_loc = ctx.get_uniform_location(&sphere_program, "sphere_cnt"); + 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, &sphere_program, "sphere_list", Some("sp") + &ctx, &program, "sphere_list", Some("sp") ); let sphere_lt_locs = get_uniform_array_locations::( - &ctx, &sphere_program, "sphere_list", Some("lt") + &ctx, &program, "sphere_list", Some("lt") ); - let sphere_color_locs = get_uniform_array_locations::( - &ctx, &sphere_program, "color_list", None + let color_locs = get_uniform_array_locations::( + &ctx, &program, "color_list", None ); - let sphere_highlight_locs = get_uniform_array_locations::( - &ctx, &sphere_program, "highlight_list", None + let highlight_locs = get_uniform_array_locations::( + &ctx, &program, "highlight_list", None ); - let resolution_loc = ctx.get_uniform_location(&sphere_program, "resolution"); - let shortdim_loc = ctx.get_uniform_location(&sphere_program, "shortdim"); - let opacity_loc = ctx.get_uniform_location(&sphere_program, "opacity"); - let layer_threshold_loc = ctx.get_uniform_location(&sphere_program, "layer_threshold"); - let debug_mode_loc = ctx.get_uniform_location(&sphere_program, "debug_mode"); + 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 layer_threshold_loc = ctx.get_uniform_location(&program, "layer_threshold"); + let debug_mode_loc = ctx.get_uniform_location(&program, "debug_mode"); - // load the viewport vertex positions into a new vertex buffer object + // set the vertex positions const VERTEX_CNT: usize = 6; - let viewport_positions: [f32; 3*VERTEX_CNT] = [ + let positions: [f32; 3*VERTEX_CNT] = [ // northwest triangle -1.0, -1.0, 0.0, -1.0, 1.0, 0.0, @@ -446,11 +284,7 @@ pub fn Display() -> View { 1.0, 1.0, 0.0, 1.0, -1.0, 0.0 ]; - let viewport_position_buffer = load_new_buffer(&ctx, &viewport_positions); - - // find the point program's vertex attributes - let point_position_attr = ctx.get_attrib_location(&point_program, "position") as u32; - let point_color_attr = ctx.get_attrib_location(&point_program, "color") as u32; + bind_vertex_attrib(&ctx, position_index, 3, &positions); // set up a repainting routine let (_, start_animation_loop, _) = create_raf(move || { @@ -544,9 +378,6 @@ pub fn Display() -> View { } if scene_changed.get() { - const SPACE_DIM: usize = 3; - const COLOR_SIZE: usize = 3; - /* INSTRUMENTS */ // measure mean frame interval frames_since_last_sample += 1; @@ -556,10 +387,6 @@ pub fn Display() -> View { frames_since_last_sample = 0; } - // --- get the assembly --- - - let mut scene = Scene::new(); - // find the map from assembly space to world space let location = { let u = -location_z; @@ -573,27 +400,41 @@ pub fn Display() -> View { }; let asm_to_world = &location * &orientation; - // set up the scene - state.assembly.elements.with_untracked( - |elts| for (key, elt) in elts { - let selected = state.selection.with(|sel| sel.contains(&key)); - elt.show(&mut scene, selected); - } - ); - let sphere_cnt = scene.spheres.len_i32(); - - // --- draw the spheres --- - - // use the sphere rendering program - ctx.use_program(Some(&sphere_program)); - - // enable the sphere program's vertex attribute - ctx.enable_vertex_attrib_array(viewport_position_attr); - - // write the spheres in world coordinates - let sphere_reps_world: Vec<_> = scene.spheres.representations.into_iter().map( - |rep| (&asm_to_world * rep).cast::() - ).collect(); + // get the assembly + let ( + elt_cnt, + reps_world, + colors, + highlights + ) = state.assembly.elements.with(|elts| { + ( + // number of elements + elts.len() as i32, + + // representation vectors in world coordinates + elts.iter().map( + |(_, elt)| elt.representation.with(|rep| &asm_to_world * rep) + ).collect::>(), + + // colors + elts.iter().map(|(key, elt)| { + if state.selection.with(|sel| sel.contains(&key)) { + elt.color.map(|ch| 0.2 + 0.8*ch) + } else { + elt.color + } + }).collect::>(), + + // highlight levels + elts.iter().map(|(key, _)| { + if state.selection.with(|sel| sel.contains(&key)) { + 1.0_f32 + } else { + HIGHLIGHT + } + }).collect::>() + ) + }); // set the resolution let width = canvas.width() as f32; @@ -601,25 +442,25 @@ pub fn Display() -> View { ctx.uniform2f(resolution_loc.as_ref(), width, height); ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); - // pass the scene data - ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_cnt); - for n in 0..sphere_reps_world.len() { - let v = &sphere_reps_world[n]; - ctx.uniform3fv_with_f32_array( + // pass the assembly + ctx.uniform1i(sphere_cnt_loc.as_ref(), elt_cnt); + for n in 0..reps_world.len() { + let v = &reps_world[n]; + ctx.uniform3f( sphere_sp_locs[n].as_ref(), - v.rows(0, 3).as_slice() + v[0] as f32, v[1] as f32, v[2] as f32 ); - ctx.uniform2fv_with_f32_array( + ctx.uniform2f( sphere_lt_locs[n].as_ref(), - v.rows(3, 2).as_slice() + v[3] as f32, v[4] as f32 ); ctx.uniform3fv_with_f32_array( - sphere_color_locs[n].as_ref(), - &scene.spheres.colors[n] + color_locs[n].as_ref(), + &colors[n] ); ctx.uniform1f( - sphere_highlight_locs[n].as_ref(), - scene.spheres.highlights[n] + highlight_locs[n].as_ref(), + highlights[n] ); } @@ -628,50 +469,9 @@ pub fn Display() -> View { ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD); ctx.uniform1i(debug_mode_loc.as_ref(), DEBUG_MODE); - // bind the viewport vertex position buffer to the position - // attribute in the vertex shader - bind_to_attribute(&ctx, viewport_position_attr, SPACE_DIM as i32, &viewport_position_buffer); - // draw the scene ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); - // disable the sphere program's vertex attribute - ctx.disable_vertex_attrib_array(viewport_position_attr); - - // --- draw the points --- - - if !scene.points.representations.is_empty() { - // use the point rendering program - ctx.use_program(Some(&point_program)); - - // enable the point program's vertex attributes - ctx.enable_vertex_attrib_array(point_position_attr); - ctx.enable_vertex_attrib_array(point_color_attr); - - // write the points in world coordinates - let asm_to_world_sp = asm_to_world.rows(0, SPACE_DIM); - let point_positions = DMatrix::from_columns( - &scene.points.representations.into_iter().map( - |rep| &asm_to_world_sp * rep - ).collect::>().as_slice() - ).cast::(); - - // load the point positions and colors into new buffers and - // bind them to the corresponding attributes in the vertex - // shader - bind_new_buffer_to_attribute(&ctx, point_position_attr, SPACE_DIM as i32, point_positions.as_slice()); - bind_new_buffer_to_attribute(&ctx, point_color_attr, COLOR_SIZE as i32, scene.points.colors.concat().as_slice()); - - // draw the scene - ctx.draw_arrays(WebGl2RenderingContext::POINTS, 0, point_positions.ncols() as i32); - - // disable the point program's vertex attributes - ctx.disable_vertex_attrib_array(point_position_attr); - ctx.disable_vertex_attrib_array(point_color_attr); - } - - // --- update the display state --- - // update the viewpoint assembly_to_world.set(asm_to_world); diff --git a/app-proto/src/engine.rs b/app-proto/src/engine.rs index b0fa23d..869a7de 100644 --- a/app-proto/src/engine.rs +++ b/app-proto/src/engine.rs @@ -4,6 +4,7 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ // --- elements --- +#[cfg(feature = "dev")] pub fn point(x: f64, y: f64, z: f64) -> DVector { DVector::from_column_slice(&[x, y, z, 0.5, 0.5*(x*x + y*y + z*z)]) } diff --git a/app-proto/src/spheres.frag b/app-proto/src/inversive.frag similarity index 100% rename from app-proto/src/spheres.frag rename to app-proto/src/inversive.frag diff --git a/app-proto/src/outline.rs b/app-proto/src/outline.rs index 2893b6d..2446337 100644 --- a/app-proto/src/outline.rs +++ b/app-proto/src/outline.rs @@ -9,10 +9,9 @@ use web_sys::{ use crate::{ AppState, + assembly, assembly::{ - Element, ElementKey, - ElementRc, HalfCurvatureRegulator, InversiveDistanceRegulator, Regulator, @@ -104,7 +103,7 @@ impl OutlineItem for InversiveDistanceRegulator { self.subjects[0] }; let other_subject_label = state.assembly.elements.with( - |elts| elts[other_subject].label().clone() + |elts| elts[other_subject].label.clone() ); view! { li(class="regulator") { @@ -142,15 +141,14 @@ fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> // a list item that shows an element in an outline view of an assembly #[component(inline_props)] -fn ElementOutlineItem(key: ElementKey, element: Rc) -> View { +fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { let state = use_context::(); let class = state.selection.map( move |sel| if sel.contains(&key) { "selected" } else { "" } ); - let label = element.label().clone(); - let representation = element.representation().clone(); + let label = element.label.clone(); let rep_components = move || { - representation.with( + element.representation.with( |rep| rep.iter().map( |u| { let u_str = format!("{:.3}", u).replace("-", "\u{2212}"); @@ -159,8 +157,8 @@ fn ElementOutlineItem(key: ElementKey, element: Rc) -> View { ).collect::>() ) }; - let regulated = element.regulators().map(|regs| regs.len() > 0); - let regulator_list = element.regulators().map( + let regulated = element.regulators.map(|regs| regs.len() > 0); + let regulator_list = element.regulators.map( move |elt_reg_keys| elt_reg_keys .clone() .into_iter() @@ -263,8 +261,7 @@ pub fn Outline() -> View { |elts| elts .clone() .into_iter() - .sorted_by_key(|(_, elt)| elt.id().clone()) - .map(|(key, elt)| (key, ElementRc(elt))) + .sorted_by_key(|(_, elt)| elt.id.clone()) .collect() ); @@ -278,10 +275,10 @@ pub fn Outline() -> View { ) { Keyed( list=element_list, - view=|(key, ElementRc(elt))| view! { + view=|(key, elt)| view! { ElementOutlineItem(key=key, element=elt) }, - key=|(_, ElementRc(elt))| elt.serial() + key=|(_, elt)| elt.serial ) } } diff --git a/app-proto/src/point.frag b/app-proto/src/point.frag deleted file mode 100644 index 2abba0e..0000000 --- a/app-proto/src/point.frag +++ /dev/null @@ -1,11 +0,0 @@ -#version 300 es - -precision highp float; - -in vec3 point_color; - -out vec4 outColor; - -void main() { - outColor = vec4(point_color, 1.); -} \ No newline at end of file diff --git a/app-proto/src/point.vert b/app-proto/src/point.vert deleted file mode 100644 index 49d584b..0000000 --- a/app-proto/src/point.vert +++ /dev/null @@ -1,17 +0,0 @@ -#version 300 es - -in vec4 position; -in vec3 color; - -out vec3 point_color; - -// camera -const float focal_slope = 0.3; - -void main() { - float depth = -focal_slope * position.z; - gl_Position = vec4(position.xy / depth, 0., 1.); - gl_PointSize = 5.; - - point_color = color; -} \ No newline at end of file