Add a point element

Also add a new test assembly, "Pointed," to try out the new element.
This commit is contained in:
Aaron Fenyes 2025-04-24 14:08:15 -07:00
parent 873de78f2d
commit 1945086586
3 changed files with 179 additions and 51 deletions

View file

@ -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" }
}
}

View file

@ -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<DVector<f64>>,
pub regulators: Signal<BTreeSet<RegulatorKey>>,
pub serial: u64,
column_index: Cell<Option<usize>>
}
impl Point {
const WEIGHT_COMPONENT: usize = 3;
pub fn new(
id: String,
label: String,
representation: DVector<f64>
) -> 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<DVector<f64>> {
self.representation
}
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
self.regulators
}
fn serial(&self) -> u64 {
self.serial
}
fn column_index(&self) -> Option<usize> {
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<Rc<dyn Element>>) {
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<ElementKey>;
fn measurement(&self) -> ReadSignal<f64>;
@ -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::<Sphere>() {
// 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(

View file

@ -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<f64>, _assembly_to_world: &DMatrix<f64>) -> Option<f64> {
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::<f32>()
).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::<Vec<_>>().as_slice()
).cast::<f32>();
// --- 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::<f32>()
).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::<Vec<_>>().as_slice()
).cast::<f32>();
// 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 ---