diff --git a/app-proto/src/add_remove.rs b/app-proto/src/add_remove.rs index b7d6c40..c9b73de 100644 --- a/app-proto/src/add_remove.rs +++ b/app-proto/src/add_remove.rs @@ -1,11 +1,11 @@ -use std::rc::Rc; +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, Sphere} + assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere} }; /* DEBUG */ @@ -133,6 +133,46 @@ 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"), + engine::point(0.0, 0.0, FRAC_1_SQRT_2) + ) + ); + let _ = assembly.try_insert_element( + Point::new( + format!("point_back"), + format!("Back point"), + 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}"), + engine::point(x, y, 0.0) + ) + ); + } + } +} + #[component] pub fn AddRemove() -> View { /* DEBUG */ @@ -158,6 +198,7 @@ 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), _ => () }; }); @@ -199,6 +240,7 @@ 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 000a619..fae10f5 100644 --- a/app-proto/src/assembly.rs +++ b/app-proto/src/assembly.rs @@ -2,6 +2,7 @@ use nalgebra::{DMatrix, DVector, DVectorView}; use rustc_hash::FxHashMap; use slab::Slab; use std::{ + any::{Any, TypeId}, cell::Cell, collections::BTreeSet, rc::Rc, @@ -16,6 +17,7 @@ use crate::{ Q, change_half_curvature, local_unif_to_std, + point, realize_gram, sphere, ConfigSubspace, @@ -197,6 +199,87 @@ impl ProblemPoser for Sphere { } } +pub struct Point { + pub id: String, + pub label: String, + 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, + representation: DVector + ) -> Point { + Point { + id, + label, + 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}"), + 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; @@ -617,8 +700,7 @@ impl Assembly { // step the assembly along the deformation. this changes the elements' // normalizations, so we restore those afterward /* KLUDGE */ - // since our test assemblies only include spheres, we assume that every - // element is on the 1 mass shell + // for now, we only restore the normalizations of spheres for (_, elt) in self.elements.get_clone_untracked() { elt.representation().update_silent(|rep| { match elt.column_index() { @@ -626,13 +708,15 @@ impl Assembly { // step the assembly along the deformation *rep += motion_proj.column(column_index); - // 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); + 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); + } }, None => { console::log_1(&JsValue::from( diff --git a/app-proto/src/display.rs b/app-proto/src/display.rs index 46f0892..115f1df 100644 --- a/app-proto/src/display.rs +++ b/app-proto/src/display.rs @@ -14,7 +14,10 @@ use web_sys::{ wasm_bindgen::{JsCast, JsValue} }; -use crate::{AppState, assembly::{ElementKey, ElementColor, ElementMotion, Sphere}}; +use crate::{ + AppState, + assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere} +}; // --- scene data --- @@ -129,6 +132,18 @@ impl DisplayItem for Sphere { } } +impl DisplayItem for Point { + fn show(&self, scene: &mut Scene, _selected: bool) { + let representation = self.representation.get_clone_untracked(); + scene.points.representations.push(representation); + } + + /* SCAFFOLDING */ + fn cast(&self, _dir: Vector3, _assembly_to_world: &DMatrix) -> Option { + None + } +} + // --- WebGL utilities --- fn compile_shader( @@ -551,7 +566,7 @@ pub fn Display() -> View { }; let asm_to_world = &location * &orientation; - // get the spheres + // set up the scene state.assembly.elements.with_untracked( |elts| for (key, elt) in elts { let selected = state.selection.with(|sel| sel.contains(&key)); @@ -560,39 +575,16 @@ pub fn Display() -> View { ); let sphere_cnt = scene.spheres.len_i32(); - // write the spheres in world coordinates - let sphere_reps_world: Vec<_> = scene.spheres.representations.into_iter().map( - |rep| (&asm_to_world * rep).cast::() - ).collect(); - - /* SCAFFOLDING */ - // get the points - scene.points.representations.append({ - use crate::engine::point; - &mut vec![ - point(0.0, 0.0, 0.0), - point(0.5, 0.5, 0.0), - point(-0.5, -0.5, 0.0), - point(-0.5, 0.5, 0.0), - point(0.5, -0.5, 0.0), - point(0.0, 0.15, 1.0), - point(0.0, -0.15, -1.0) - ] - }); - - // 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::(); - // --- draw the spheres --- // use the sphere rendering program ctx.use_program(Some(&sphere_program)); + // write the spheres in world coordinates + let sphere_reps_world: Vec<_> = scene.spheres.representations.into_iter().map( + |rep| (&asm_to_world * rep).cast::() + ).collect(); + // set the resolution let width = canvas.width() as f32; let height = canvas.height() as f32; @@ -635,16 +627,26 @@ pub fn Display() -> View { // --- draw the points --- - // use the point rendering program - ctx.use_program(Some(&point_program)); - - // load the point positions into a new buffer and bind it to the - // position attribute in the vertex shader - let point_position_buffer = load_new_buffer(&ctx, point_positions.as_slice()); - bind_to_attribute(&ctx, point_position_attr, SPACE_DIM as i32, &point_position_buffer); - - // draw the scene - ctx.draw_arrays(WebGl2RenderingContext::POINTS, 0, point_positions.ncols() as i32); + if !scene.points.representations.is_empty() { + // use the point rendering program + ctx.use_program(Some(&point_program)); + + // 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 into a new buffer and bind it to the + // position attribute in the vertex shader + let point_position_buffer = load_new_buffer(&ctx, point_positions.as_slice()); + bind_to_attribute(&ctx, point_position_attr, SPACE_DIM as i32, &point_position_buffer); + + // draw the scene + ctx.draw_arrays(WebGl2RenderingContext::POINTS, 0, point_positions.ncols() as i32); + } // --- update the display state ---