Compare commits

..

No commits in common. "0fbb0715067232826e7391f1a3cb999e3fdf9147" and "3590b1ad4ee6c6089b6cd3045bc0d5986f895bc2" have entirely different histories.

8 changed files with 300 additions and 703 deletions

View file

@ -1,59 +1,58 @@
use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc};
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; use web_sys::{console, wasm_bindgen::JsValue};
use crate::{ use crate::{
engine, engine,
AppState, AppState,
assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere} assembly::{Assembly, Element, InversiveDistanceRegulator}
}; };
/* DEBUG */ /* DEBUG */
// load an example assembly for testing. this code will be removed once we've // load an example assembly for testing. this code will be removed once we've
// built a more formal test assembly system // built a more formal test assembly system
fn load_gen_assemb(assembly: &Assembly) { fn load_gen_assemb(assembly: &Assembly) {
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
String::from("gemini_a"), String::from("gemini_a"),
String::from("Castor"), String::from("Castor"),
[1.00_f32, 0.25_f32, 0.00_f32], [1.00_f32, 0.25_f32, 0.00_f32],
engine::sphere(0.5, 0.5, 0.0, 1.0) engine::sphere(0.5, 0.5, 0.0, 1.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
String::from("gemini_b"), String::from("gemini_b"),
String::from("Pollux"), String::from("Pollux"),
[0.00_f32, 0.25_f32, 1.00_f32], [0.00_f32, 0.25_f32, 1.00_f32],
engine::sphere(-0.5, -0.5, 0.0, 1.0) engine::sphere(-0.5, -0.5, 0.0, 1.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
String::from("ursa_major"), String::from("ursa_major"),
String::from("Ursa major"), String::from("Ursa major"),
[0.25_f32, 0.00_f32, 1.00_f32], [0.25_f32, 0.00_f32, 1.00_f32],
engine::sphere(-0.5, 0.5, 0.0, 0.75) engine::sphere(-0.5, 0.5, 0.0, 0.75)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
String::from("ursa_minor"), String::from("ursa_minor"),
String::from("Ursa minor"), String::from("Ursa minor"),
[0.25_f32, 1.00_f32, 0.00_f32], [0.25_f32, 1.00_f32, 0.00_f32],
engine::sphere(0.5, -0.5, 0.0, 0.5) engine::sphere(0.5, -0.5, 0.0, 0.5)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
String::from("moon_deimos"), String::from("moon_deimos"),
String::from("Deimos"), String::from("Deimos"),
[0.75_f32, 0.75_f32, 0.00_f32], [0.75_f32, 0.75_f32, 0.00_f32],
engine::sphere(0.0, 0.15, 1.0, 0.25) engine::sphere(0.0, 0.15, 1.0, 0.25)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
String::from("moon_phobos"), String::from("moon_phobos"),
String::from("Phobos"), String::from("Phobos"),
[0.00_f32, 0.75_f32, 0.50_f32], [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 // built a more formal test assembly system
fn load_low_curv_assemb(assembly: &Assembly) { fn load_low_curv_assemb(assembly: &Assembly) {
let a = 0.75_f64.sqrt(); let a = 0.75_f64.sqrt();
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
"central".to_string(), "central".to_string(),
"Central".to_string(), "Central".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere(0.0, 0.0, 0.0, 1.0) engine::sphere(0.0, 0.0, 0.0, 1.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
"assemb_plane".to_string(), "assemb_plane".to_string(),
"Assembly plane".to_string(), "Assembly plane".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0) engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
"side1".to_string(), "side1".to_string(),
"Side 1".to_string(), "Side 1".to_string(),
[1.00_f32, 0.00_f32, 0.25_f32], [1.00_f32, 0.00_f32, 0.25_f32],
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0) engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
"side2".to_string(), "side2".to_string(),
"Side 2".to_string(), "Side 2".to_string(),
[0.25_f32, 1.00_f32, 0.00_f32], [0.25_f32, 1.00_f32, 0.00_f32],
engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0) engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
"side3".to_string(), "side3".to_string(),
"Side 3".to_string(), "Side 3".to_string(),
[0.00_f32, 0.25_f32, 1.00_f32], [0.00_f32, 0.25_f32, 1.00_f32],
engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0) engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
"corner1".to_string(), "corner1".to_string(),
"Corner 1".to_string(), "Corner 1".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0) engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
"corner2".to_string(), "corner2".to_string(),
"Corner 2".to_string(), "Corner 2".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], [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) engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0)
) )
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_sphere(
Sphere::new( Element::new(
String::from("corner3"), String::from("corner3"),
String::from("Corner 3"), String::from("Corner 3"),
[0.75_f32, 0.75_f32, 0.75_f32], [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] #[component]
pub fn AddRemove() -> View { pub fn AddRemove() -> View {
/* DEBUG */ /* DEBUG */
@ -201,7 +157,6 @@ pub fn AddRemove() -> View {
match name.as_str() { match name.as_str() {
"general" => load_gen_assemb(assembly), "general" => load_gen_assemb(assembly),
"low-curv" => load_low_curv_assemb(assembly), "low-curv" => load_low_curv_assemb(assembly),
"pointed" => load_pointed_assemb(assembly),
_ => () _ => ()
}; };
}); });
@ -212,7 +167,7 @@ pub fn AddRemove() -> View {
button( button(
on:click=|_| { on:click=|_| {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
state.assembly.insert_element_default::<Sphere>(); state.assembly.insert_new_sphere();
} }
) { "+" } ) { "+" }
button( button(
@ -235,7 +190,7 @@ pub fn AddRemove() -> View {
.unwrap() .unwrap()
); );
state.assembly.insert_regulator( state.assembly.insert_regulator(
Rc::new(InversiveDistanceRegulator::new(subjects, &state.assembly)) InversiveDistanceRegulator::new(subjects, &state.assembly)
); );
state.selection.update(|sel| sel.clear()); state.selection.update(|sel| sel.clear());
} }
@ -243,7 +198,6 @@ pub fn AddRemove() -> View {
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
option(value="general") { "General" } option(value="general") { "General" }
option(value="low-curv") { "Low-curvature" } option(value="low-curv") { "Low-curvature" }
option(value="pointed") { "Pointed" }
option(value="empty") { "Empty" } option(value="empty") { "Empty" }
} }
} }

View file

@ -1,23 +1,15 @@
use nalgebra::{DMatrix, DVector, DVectorView}; use nalgebra::{DMatrix, DVector, DVectorView, Vector3};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use slab::Slab; use slab::Slab;
use std::{ use std::{collections::BTreeSet, rc::Rc, sync::atomic::{AtomicU64, Ordering}};
any::{Any, TypeId},
cell::Cell,
collections::BTreeSet,
rc::Rc,
sync::atomic::{AtomicU64, Ordering}
};
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
use crate::{ use crate::{
display::DisplayItem,
engine::{ engine::{
Q, Q,
change_half_curvature, change_half_curvature,
local_unif_to_std, local_unif_to_std,
point,
realize_gram, realize_gram,
sphere, sphere,
ConfigSubspace, ConfigSubspace,
@ -41,87 +33,31 @@ pub type ElementColor = [f32; 3];
static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0); static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0);
pub trait ProblemPoser { pub trait ProblemPoser {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>); fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>);
} }
pub trait Element: ProblemPoser + DisplayItem { #[derive(Clone, PartialEq)]
// the default identifier for an element of this type pub struct Element {
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<Rc<dyn Regulator>> where Self: Sized {
Vec::new()
}
fn id(&self) -> &String;
fn label(&self) -> &String;
fn representation(&self) -> Signal<DVector<f64>>;
// 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<BTreeSet<RegulatorKey>>;
// 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<usize>;
// 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<dyn Element>);
impl PartialEq for ElementRc {
fn eq(&self, ElementRc(other): &Self) -> bool {
let ElementRc(rc) = self;
Rc::ptr_eq(rc, &other)
}
}
pub struct Sphere {
pub id: String, pub id: String,
pub label: String, pub label: String,
pub color: ElementColor, pub color: ElementColor,
pub representation: Signal<DVector<f64>>, pub representation: Signal<DVector<f64>>,
// 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<BTreeSet<RegulatorKey>>, pub regulators: Signal<BTreeSet<RegulatorKey>>,
// a serial number, assigned by `Element::new`, that uniquely identifies
// each element
pub serial: u64, pub serial: u64,
column_index: Cell<Option<usize>>
// 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<usize>
} }
impl Sphere { impl Element {
const CURVATURE_COMPONENT: usize = 3; const CURVATURE_COMPONENT: usize = 3;
pub fn new( pub fn new(
@ -129,161 +65,83 @@ impl Sphere {
label: String, label: String,
color: ElementColor, color: ElementColor,
representation: DVector<f64> representation: DVector<f64>
) -> Sphere { ) -> Element {
Sphere { // 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, id: id,
label: label, label: label,
color: color, color: color,
representation: create_signal(representation), representation: create_signal(representation),
regulators: create_signal(BTreeSet::default()), regulators: create_signal(BTreeSet::default()),
serial: Self::next_serial(), serial: serial,
column_index: None.into() 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<f64>, assembly_to_world: &DMatrix<f64>) -> Option<f64> {
// 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 { impl ProblemPoser for Element {
fn default_id() -> String { fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Element>) {
"sphere".to_string() let index = self.column_index.expect(
} format!("Element \"{}\" should be indexed before writing problem data", self.id).as_str()
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<Rc<dyn Regulator>> {
vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))]
}
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 Sphere {
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
let index = self.column_index().expect(
format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str()
); );
problem.gram.push_sym(index, index, 1.0); problem.gram.push_sym(index, index, 1.0);
problem.guess.set_column(index, &self.representation.get_clone_untracked()); 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<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,
color: ElementColor,
representation: DVector<f64>
) -> 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<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 { pub trait Regulator: ProblemPoser + OutlineItem {
fn subjects(&self) -> Vec<ElementKey>; fn subjects(&self) -> Vec<ElementKey>;
fn measurement(&self) -> ReadSignal<f64>; fn measurement(&self) -> ReadSignal<f64>;
@ -310,7 +168,7 @@ impl InversiveDistanceRegulator {
pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator { pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator {
let measurement = assembly.elements.map( let measurement = assembly.elements.map(
move |elts| { move |elts| {
let representations = subjects.map(|subj| elts[subj].representation()); let representations = subjects.map(|subj| elts[subj].representation);
representations[0].with(|rep_0| representations[0].with(|rep_0|
representations[1].with(|rep_1| representations[1].with(|rep_1|
rep_0.dot(&(&*Q * rep_1)) rep_0.dot(&(&*Q * rep_1))
@ -340,11 +198,11 @@ impl Regulator for InversiveDistanceRegulator {
} }
impl ProblemPoser for InversiveDistanceRegulator { impl ProblemPoser for InversiveDistanceRegulator {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>) {
self.set_point.with_untracked(|set_pt| { self.set_point.with_untracked(|set_pt| {
if let Some(val) = set_pt.value { if let Some(val) = set_pt.value {
let [row, col] = self.subjects.map( 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" "Subjects should be indexed before inversive distance regulator writes problem data"
) )
); );
@ -363,8 +221,8 @@ pub struct HalfCurvatureRegulator {
impl HalfCurvatureRegulator { impl HalfCurvatureRegulator {
pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator { pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator {
let measurement = assembly.elements.map( let measurement = assembly.elements.map(
move |elts| elts[subject].representation().with( move |elts| elts[subject].representation.with(
|rep| rep[Sphere::CURVATURE_COMPONENT] |rep| rep[Element::CURVATURE_COMPONENT]
) )
); );
@ -391,7 +249,7 @@ impl Regulator for HalfCurvatureRegulator {
match self.set_point.with(|set_pt| set_pt.value) { match self.set_point.with(|set_pt| set_pt.value) {
Some(half_curv) => { Some(half_curv) => {
let representation = assembly.elements.with_untracked( let representation = assembly.elements.with_untracked(
|elts| elts[self.subject].representation() |elts| elts[self.subject].representation
); );
representation.update( representation.update(
|rep| change_half_curvature(rep, half_curv) |rep| change_half_curvature(rep, half_curv)
@ -404,13 +262,13 @@ impl Regulator for HalfCurvatureRegulator {
} }
impl ProblemPoser for HalfCurvatureRegulator { impl ProblemPoser for HalfCurvatureRegulator {
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) { fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>) {
self.set_point.with_untracked(|set_pt| { self.set_point.with_untracked(|set_pt| {
if let Some(val) = set_pt.value { 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" "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<ElementMotion<'a>>;
#[derive(Clone)] #[derive(Clone)]
pub struct Assembly { pub struct Assembly {
// elements and regulators // elements and regulators
pub elements: Signal<Slab<Rc<dyn Element>>>, pub elements: Signal<Slab<Element>>,
pub regulators: Signal<Slab<Rc<dyn Regulator>>>, pub regulators: Signal<Slab<Rc<dyn Regulator>>>,
// solution variety tangent space. the basis vectors are stored in // solution variety tangent space. the basis vectors are stored in
@ -459,61 +317,66 @@ impl Assembly {
// --- inserting elements and regulators --- // --- 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 // 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 // same identifier will get kicked out of the `elements_by_id` index
fn insert_element_unchecked<T: Element + 'static>(&self, elt: T) -> ElementKey { fn insert_sphere_unchecked(&self, elt: Element) -> ElementKey {
// insert the element // insert the sphere
let id = elt.id().clone(); let id = elt.id.clone();
let key = self.elements.update(|elts| elts.insert(Rc::new(elt))); let key = self.elements.update(|elts| elts.insert(elt));
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key)); self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
// create and insert the element's default regulators // regulate the sphere's curvature
for reg in T::default_regulators(key, &self) { self.insert_regulator(HalfCurvatureRegulator::new(key, &self));
self.insert_regulator(reg);
}
key key
} }
pub fn try_insert_element(&self, elt: impl Element + 'static) -> Option<ElementKey> { pub fn try_insert_sphere(&self, elt: Element) -> Option<ElementKey> {
let can_insert = self.elements_by_id.with_untracked( 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 { if can_insert {
Some(self.insert_element_unchecked(elt)) Some(self.insert_sphere_unchecked(elt))
} else { } else {
None None
} }
} }
pub fn insert_element_default<T: Element + 'static>(&self) { pub fn insert_new_sphere(&self) {
// find the next unused identifier in the default sequence // find the next unused identifier in the default sequence
let default_id = T::default_id();
let mut id_num = 1; 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( while self.elements_by_id.with_untracked(
|elts_by_id| elts_by_id.contains_key(&id) |elts_by_id| elts_by_id.contains_key(&id)
) { ) {
id_num += 1; id_num += 1;
id = format!("{default_id}{id_num}"); id = format!("sphere{}", id_num);
} }
// create and insert the default example of `T` // create and insert a sphere
let _ = self.insert_element_unchecked(T::default(id, id_num)); 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<dyn Regulator>) { pub fn insert_regulator<T: Regulator + 'static>(&self, regulator: T) {
// add the regulator to the assembly's regulator list // add the regulator to the assembly's regulator list
let regulator_rc = Rc::new(regulator);
let key = self.regulators.update( 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 // 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( let subject_regulators: Vec<_> = self.elements.with_untracked(
|elts| subjects.into_iter().map( |elts| subjects.into_iter().map(
|subj| elts[subj].regulators() |subj| elts[subj].regulators
).collect() ).collect()
); );
for regulators in subject_regulators { for regulators in subject_regulators {
@ -527,10 +390,10 @@ impl Assembly {
/* DEBUG */ /* DEBUG */
// log the regulator update // log the regulator update
console::log_1(&JsValue::from( 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(); self_for_effect.realize();
} }
}); });
@ -564,7 +427,7 @@ impl Assembly {
// index the elements // index the elements
self.elements.update_silent(|elts| { self.elements.update_silent(|elts| {
for (index, (_, elt)) in elts.into_iter().enumerate() { 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 { if success {
// read out the solution // read out the solution
for (_, elt) in self.elements.get_clone_untracked() { for (_, elt) in self.elements.get_clone_untracked() {
elt.representation().update( elt.representation.update(
|rep| rep.set_column(0, &config.column(elt.column_index().unwrap())) |rep| rep.set_column(0, &config.column(elt.column_index.unwrap()))
); );
} }
@ -658,8 +521,8 @@ impl Assembly {
let mut next_column_index = realized_dim; let mut next_column_index = realized_dim;
for elt_motion in motion.iter() { for elt_motion in motion.iter() {
let moving_elt = &mut elts[elt_motion.key]; let moving_elt = &mut elts[elt_motion.key];
if moving_elt.column_index().is_none() { if moving_elt.column_index.is_none() {
moving_elt.set_column_index(next_column_index); moving_elt.column_index = Some(next_column_index);
next_column_index += 1; next_column_index += 1;
} }
} }
@ -676,7 +539,7 @@ impl Assembly {
// we can unwrap the column index because we know that every moving // we can unwrap the column index because we know that every moving
// element has one at this point // element has one at this point
let column_index = self.elements.with_untracked( 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 { if column_index < realized_dim {
@ -692,7 +555,7 @@ impl Assembly {
let mut target_column = motion_proj.column_mut(column_index); let mut target_column = motion_proj.column_mut(column_index);
let unif_to_std = self.elements.with_untracked( let unif_to_std = self.elements.with_untracked(
|elts| { |elts| {
elts[elt_motion.key].representation().with_untracked( elts[elt_motion.key].representation.with_untracked(
|rep| local_unif_to_std(rep.as_view()) |rep| local_unif_to_std(rep.as_view())
) )
} }
@ -704,27 +567,26 @@ impl Assembly {
// step the assembly along the deformation. this changes the elements' // step the assembly along the deformation. this changes the elements'
// normalizations, so we restore those afterward // normalizations, so we restore those afterward
/* KLUDGE */ /* 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() { for (_, elt) in self.elements.get_clone_untracked() {
elt.representation().update_silent(|rep| { elt.representation.update_silent(|rep| {
match elt.column_index() { match elt.column_index {
Some(column_index) => { Some(column_index) => {
// step the assembly along the deformation // step the assembly along the deformation
*rep += motion_proj.column(column_index); *rep += motion_proj.column(column_index);
if elt.type_id() == TypeId::of::<Sphere>() { // restore normalization by contracting toward the last
// restore normalization by contracting toward the // coordinate axis
// last coordinate axis let q_sp = rep.fixed_rows::<3>(0).norm_squared();
let q_sp = rep.fixed_rows::<3>(0).norm_squared(); let half_q_lt = -2.0 * rep[3] * rep[4];
let half_q_lt = -2.0 * rep[3] * rep[4]; let half_q_lt_sq = half_q_lt * half_q_lt;
let half_q_lt_sq = half_q_lt * half_q_lt; let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt();
let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt(); rep.fixed_rows_mut::<4>(0).scale_mut(1.0 / scaling);
rep.fixed_rows_mut::<4>(0).scale_mut(1.0 / scaling);
}
}, },
None => { None => {
console::log_1(&JsValue::from( 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)] #[cfg(test)]
mod tests { mod tests {
use crate::engine;
use super::*; use super::*;
#[test] #[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() { fn unindexed_element_test() {
let _ = create_root(|| { let _ = create_root(|| {
let elt = Sphere::default("sphere".to_string(), 0); Element::new(
elt.pose(&mut ConstraintProblem::new(1), &Slab::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")] #[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")]
fn unindexed_subject_test_inversive_distance() { fn unindexed_subject_test_inversive_distance() {
let _ = create_root(|| { let _ = create_root(|| {
let mut elts = Slab::<Rc<dyn Element>>::new(); let mut elts = Slab::new();
let subjects = [0, 1].map(|k| { let subjects = [0, 1].map(|k| {
elts.insert( 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 { InversiveDistanceRegulator {
subjects: subjects, subjects: subjects,
measurement: create_memo(|| 0.0), measurement: create_memo(|| 0.0),

View file

@ -4,154 +4,17 @@ use sycamore::{prelude::*, motion::create_raf};
use web_sys::{ use web_sys::{
console, console,
window, window,
Element,
KeyboardEvent, KeyboardEvent,
MouseEvent, MouseEvent,
WebGl2RenderingContext, WebGl2RenderingContext,
WebGlBuffer,
WebGlProgram, WebGlProgram,
WebGlShader, WebGlShader,
WebGlUniformLocation, WebGlUniformLocation,
wasm_bindgen::{JsCast, JsValue} wasm_bindgen::{JsCast, JsValue}
}; };
use crate::{ use crate::{AppState, assembly::{ElementKey, ElementMotion}};
AppState,
assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere}
};
// --- scene data ---
struct SceneSpheres {
representations: Vec<DVector<f64>>,
colors: Vec<ElementColor>,
highlights: Vec<f32>
}
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<f64>, color: ElementColor, highlight: f32) {
self.representations.push(representation);
self.colors.push(color);
self.highlights.push(highlight);
}
}
struct ScenePoints {
representations: Vec<DVector<f64>>,
colors: Vec<ElementColor>,
}
impl ScenePoints {
fn new() -> ScenePoints {
ScenePoints {
representations: Vec::new(),
colors: Vec::new()
}
}
fn push(&mut self, representation: DVector<f64>, 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<f64>, assembly_to_world: &DMatrix<f64>) -> Option<f64>;
}
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<f64>, assembly_to_world: &DMatrix<f64>) -> Option<f64> {
// 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<f64>, _assembly_to_world: &DMatrix<f64>) -> Option<f64> {
None
}
}
// --- WebGL utilities ---
fn compile_shader( fn compile_shader(
context: &WebGl2RenderingContext, context: &WebGl2RenderingContext,
@ -164,7 +27,7 @@ fn compile_shader(
shader shader
} }
fn set_up_program( fn create_program_with_shaders(
context: &WebGl2RenderingContext, context: &WebGl2RenderingContext,
vertex_shader_source: &str, vertex_shader_source: &str,
fragment_shader_source: &str fragment_shader_source: &str
@ -218,39 +81,22 @@ fn get_uniform_array_locations<const N: usize>(
}) })
} }
// bind the given vertex buffer object to the given vertex attribute // load the given data into the vertex input of the given name
fn bind_to_attribute( fn bind_vertex_attrib(
context: &WebGl2RenderingContext,
attr_index: u32,
attr_size: i32,
buffer: &Option<WebGlBuffer>
) {
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(
context: &WebGl2RenderingContext, context: &WebGl2RenderingContext,
index: u32,
size: i32,
data: &[f32] data: &[f32]
) -> Option<WebGlBuffer> { ) {
// create a buffer and bind it to ARRAY_BUFFER // create a data buffer and bind it to ARRAY_BUFFER
let buffer = context.create_buffer(); let buffer = context.create_buffer().unwrap();
context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref()); context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
// load the given data into the buffer. this block is unsafe because // load the given data into the buffer. the function `Float32Array::view`
// `Float32Array::view` creates a raw view into our module's // creates a raw view into our module's `WebAssembly.Memory` buffer.
// `WebAssembly.Memory` buffer. allocating more memory will change the // allocating more memory will change the buffer, invalidating the view.
// buffer, invalidating the view, so we have to make sure we don't allocate // that means we have to make sure we don't allocate any memory until the
// any memory until the view is dropped. we're okay here because the view is // view is dropped
// used as soon as it's created
unsafe { unsafe {
context.buffer_data_with_array_buffer_view( context.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER, WebGl2RenderingContext::ARRAY_BUFFER,
@ -259,29 +105,33 @@ fn load_new_buffer(
); );
} }
buffer // allow the target attribute to be used
} context.enable_vertex_attrib_array(index);
fn bind_new_buffer_to_attribute( // take whatever's bound to ARRAY_BUFFER---here, the data buffer created
context: &WebGl2RenderingContext, // above---and bind it to the target attribute
attr_index: u32, //
attr_size: i32, // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer
data: &[f32] //
) { context.vertex_attrib_pointer_with_i32(
let buffer = load_new_buffer(context, data); index,
bind_to_attribute(context, attr_index, attr_size, &buffer); 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 // the direction in camera space that a mouse event is pointing along
fn event_dir(event: &MouseEvent) -> Vector3<f64> { fn event_dir(event: &MouseEvent) -> Vector3<f64> {
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 rect = target.get_bounding_client_rect();
let width = rect.width(); let width = rect.width();
let height = rect.height(); let height = rect.height();
let shortdim = width.min(height); let shortdim = width.min(height);
// this constant should be kept synchronized with `spheres.frag` and // this constant should be kept synchronized with `inversive.frag`
// `point.vert`
const FOCAL_SLOPE: f64 = 0.3; const FOCAL_SLOPE: f64 = 0.3;
Vector3::new( Vector3::new(
@ -291,8 +141,6 @@ fn event_dir(event: &MouseEvent) -> Vector3<f64> {
) )
} }
// --- display component ---
#[component] #[component]
pub fn Display() -> View { pub fn Display() -> View {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
@ -329,7 +177,7 @@ pub fn Display() -> View {
create_effect(move || { create_effect(move || {
state.assembly.elements.with(|elts| { state.assembly.elements.with(|elts| {
for (_, elt) in elts { for (_, elt) in elts {
elt.representation().track(); elt.representation.track();
} }
}); });
state.selection.track(); state.selection.track();
@ -361,6 +209,7 @@ pub fn Display() -> View {
// display parameters // display parameters
const OPACITY: f32 = 0.5; /* SCAFFOLDING */ const OPACITY: f32 = 0.5; /* SCAFFOLDING */
const HIGHLIGHT: f32 = 0.2; /* SCAFFOLDING */
const LAYER_THRESHOLD: i32 = 0; /* DEBUG */ const LAYER_THRESHOLD: i32 = 0; /* DEBUG */
const DEBUG_MODE: i32 = 0; /* DEBUG */ const DEBUG_MODE: i32 = 0; /* DEBUG */
@ -376,22 +225,13 @@ pub fn Display() -> View {
.dyn_into::<WebGl2RenderingContext>() .dyn_into::<WebGl2RenderingContext>()
.unwrap(); .unwrap();
// disable depth testing // create and use the rendering program
ctx.disable(WebGl2RenderingContext::DEPTH_TEST); let program = create_program_with_shaders(
// set up the sphere rendering program
let sphere_program = set_up_program(
&ctx, &ctx,
include_str!("identity.vert"), include_str!("identity.vert"),
include_str!("spheres.frag") include_str!("inversive.frag")
);
// set up the point rendering program
let point_program = set_up_program(
&ctx,
include_str!("point.vert"),
include_str!("point.frag")
); );
ctx.use_program(Some(&program));
/* DEBUG */ /* DEBUG */
// print the maximum number of vectors that can be passed as // print the maximum number of vectors that can be passed as
@ -410,33 +250,31 @@ pub fn Display() -> View {
&JsValue::from("uniform vectors available") &JsValue::from("uniform vectors available")
); );
// find the sphere program's vertex attribute // find indices of vertex attributes and uniforms
let viewport_position_attr = ctx.get_attrib_location(&sphere_program, "position") as u32;
// find the sphere program's uniforms
const SPHERE_MAX: usize = 200; 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::<SPHERE_MAX>( let sphere_sp_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &sphere_program, "sphere_list", Some("sp") &ctx, &program, "sphere_list", Some("sp")
); );
let sphere_lt_locs = get_uniform_array_locations::<SPHERE_MAX>( let sphere_lt_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &sphere_program, "sphere_list", Some("lt") &ctx, &program, "sphere_list", Some("lt")
); );
let sphere_color_locs = get_uniform_array_locations::<SPHERE_MAX>( let color_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &sphere_program, "color_list", None &ctx, &program, "color_list", None
); );
let sphere_highlight_locs = get_uniform_array_locations::<SPHERE_MAX>( let highlight_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &sphere_program, "highlight_list", None &ctx, &program, "highlight_list", None
); );
let resolution_loc = ctx.get_uniform_location(&sphere_program, "resolution"); let resolution_loc = ctx.get_uniform_location(&program, "resolution");
let shortdim_loc = ctx.get_uniform_location(&sphere_program, "shortdim"); let shortdim_loc = ctx.get_uniform_location(&program, "shortdim");
let opacity_loc = ctx.get_uniform_location(&sphere_program, "opacity"); let opacity_loc = ctx.get_uniform_location(&program, "opacity");
let layer_threshold_loc = ctx.get_uniform_location(&sphere_program, "layer_threshold"); let layer_threshold_loc = ctx.get_uniform_location(&program, "layer_threshold");
let debug_mode_loc = ctx.get_uniform_location(&sphere_program, "debug_mode"); 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; const VERTEX_CNT: usize = 6;
let viewport_positions: [f32; 3*VERTEX_CNT] = [ let positions: [f32; 3*VERTEX_CNT] = [
// northwest triangle // northwest triangle
-1.0, -1.0, 0.0, -1.0, -1.0, 0.0,
-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,
1.0, -1.0, 0.0 1.0, -1.0, 0.0
]; ];
let viewport_position_buffer = load_new_buffer(&ctx, &viewport_positions); bind_vertex_attrib(&ctx, position_index, 3, &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;
// set up a repainting routine // set up a repainting routine
let (_, start_animation_loop, _) = create_raf(move || { let (_, start_animation_loop, _) = create_raf(move || {
@ -544,9 +378,6 @@ pub fn Display() -> View {
} }
if scene_changed.get() { if scene_changed.get() {
const SPACE_DIM: usize = 3;
const COLOR_SIZE: usize = 3;
/* INSTRUMENTS */ /* INSTRUMENTS */
// measure mean frame interval // measure mean frame interval
frames_since_last_sample += 1; frames_since_last_sample += 1;
@ -556,10 +387,6 @@ pub fn Display() -> View {
frames_since_last_sample = 0; frames_since_last_sample = 0;
} }
// --- get the assembly ---
let mut scene = Scene::new();
// find the map from assembly space to world space // find the map from assembly space to world space
let location = { let location = {
let u = -location_z; let u = -location_z;
@ -573,27 +400,41 @@ pub fn Display() -> View {
}; };
let asm_to_world = &location * &orientation; let asm_to_world = &location * &orientation;
// set up the scene // get the assembly
state.assembly.elements.with_untracked( let (
|elts| for (key, elt) in elts { elt_cnt,
let selected = state.selection.with(|sel| sel.contains(&key)); reps_world,
elt.show(&mut scene, selected); colors,
} highlights
); ) = state.assembly.elements.with(|elts| {
let sphere_cnt = scene.spheres.len_i32(); (
// number of elements
// --- draw the spheres --- elts.len() as i32,
// use the sphere rendering program // representation vectors in world coordinates
ctx.use_program(Some(&sphere_program)); elts.iter().map(
|(_, elt)| elt.representation.with(|rep| &asm_to_world * rep)
// enable the sphere program's vertex attribute ).collect::<Vec<_>>(),
ctx.enable_vertex_attrib_array(viewport_position_attr);
// colors
// write the spheres in world coordinates elts.iter().map(|(key, elt)| {
let sphere_reps_world: Vec<_> = scene.spheres.representations.into_iter().map( if state.selection.with(|sel| sel.contains(&key)) {
|rep| (&asm_to_world * rep).cast::<f32>() elt.color.map(|ch| 0.2 + 0.8*ch)
).collect(); } else {
elt.color
}
}).collect::<Vec<_>>(),
// highlight levels
elts.iter().map(|(key, _)| {
if state.selection.with(|sel| sel.contains(&key)) {
1.0_f32
} else {
HIGHLIGHT
}
}).collect::<Vec<_>>()
)
});
// set the resolution // set the resolution
let width = canvas.width() as f32; let width = canvas.width() as f32;
@ -601,25 +442,25 @@ pub fn Display() -> View {
ctx.uniform2f(resolution_loc.as_ref(), width, height); ctx.uniform2f(resolution_loc.as_ref(), width, height);
ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); ctx.uniform1f(shortdim_loc.as_ref(), width.min(height));
// pass the scene data // pass the assembly
ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_cnt); ctx.uniform1i(sphere_cnt_loc.as_ref(), elt_cnt);
for n in 0..sphere_reps_world.len() { for n in 0..reps_world.len() {
let v = &sphere_reps_world[n]; let v = &reps_world[n];
ctx.uniform3fv_with_f32_array( ctx.uniform3f(
sphere_sp_locs[n].as_ref(), 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(), 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( ctx.uniform3fv_with_f32_array(
sphere_color_locs[n].as_ref(), color_locs[n].as_ref(),
&scene.spheres.colors[n] &colors[n]
); );
ctx.uniform1f( ctx.uniform1f(
sphere_highlight_locs[n].as_ref(), highlight_locs[n].as_ref(),
scene.spheres.highlights[n] highlights[n]
); );
} }
@ -628,50 +469,9 @@ pub fn Display() -> View {
ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD); ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD);
ctx.uniform1i(debug_mode_loc.as_ref(), DEBUG_MODE); 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 // draw the scene
ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32); 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::<Vec<_>>().as_slice()
).cast::<f32>();
// 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 // update the viewpoint
assembly_to_world.set(asm_to_world); assembly_to_world.set(asm_to_world);

View file

@ -4,6 +4,7 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
// --- elements --- // --- elements ---
#[cfg(feature = "dev")]
pub fn point(x: f64, y: f64, z: f64) -> DVector<f64> { pub fn point(x: f64, y: f64, z: f64) -> DVector<f64> {
DVector::from_column_slice(&[x, y, z, 0.5, 0.5*(x*x + y*y + z*z)]) DVector::from_column_slice(&[x, y, z, 0.5, 0.5*(x*x + y*y + z*z)])
} }

View file

@ -9,10 +9,9 @@ use web_sys::{
use crate::{ use crate::{
AppState, AppState,
assembly,
assembly::{ assembly::{
Element,
ElementKey, ElementKey,
ElementRc,
HalfCurvatureRegulator, HalfCurvatureRegulator,
InversiveDistanceRegulator, InversiveDistanceRegulator,
Regulator, Regulator,
@ -104,7 +103,7 @@ impl OutlineItem for InversiveDistanceRegulator {
self.subjects[0] self.subjects[0]
}; };
let other_subject_label = state.assembly.elements.with( let other_subject_label = state.assembly.elements.with(
|elts| elts[other_subject].label().clone() |elts| elts[other_subject].label.clone()
); );
view! { view! {
li(class="regulator") { 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 // a list item that shows an element in an outline view of an assembly
#[component(inline_props)] #[component(inline_props)]
fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View { fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let class = state.selection.map( let class = state.selection.map(
move |sel| if sel.contains(&key) { "selected" } else { "" } move |sel| if sel.contains(&key) { "selected" } else { "" }
); );
let label = element.label().clone(); let label = element.label.clone();
let representation = element.representation().clone();
let rep_components = move || { let rep_components = move || {
representation.with( element.representation.with(
|rep| rep.iter().map( |rep| rep.iter().map(
|u| { |u| {
let u_str = format!("{:.3}", u).replace("-", "\u{2212}"); let u_str = format!("{:.3}", u).replace("-", "\u{2212}");
@ -159,8 +157,8 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
).collect::<Vec<_>>() ).collect::<Vec<_>>()
) )
}; };
let regulated = element.regulators().map(|regs| regs.len() > 0); let regulated = element.regulators.map(|regs| regs.len() > 0);
let regulator_list = element.regulators().map( let regulator_list = element.regulators.map(
move |elt_reg_keys| elt_reg_keys move |elt_reg_keys| elt_reg_keys
.clone() .clone()
.into_iter() .into_iter()
@ -263,8 +261,7 @@ pub fn Outline() -> View {
|elts| elts |elts| elts
.clone() .clone()
.into_iter() .into_iter()
.sorted_by_key(|(_, elt)| elt.id().clone()) .sorted_by_key(|(_, elt)| elt.id.clone())
.map(|(key, elt)| (key, ElementRc(elt)))
.collect() .collect()
); );
@ -278,10 +275,10 @@ pub fn Outline() -> View {
) { ) {
Keyed( Keyed(
list=element_list, list=element_list,
view=|(key, ElementRc(elt))| view! { view=|(key, elt)| view! {
ElementOutlineItem(key=key, element=elt) ElementOutlineItem(key=key, element=elt)
}, },
key=|(_, ElementRc(elt))| elt.serial() key=|(_, elt)| elt.serial
) )
} }
} }

View file

@ -1,11 +0,0 @@
#version 300 es
precision highp float;
in vec3 point_color;
out vec4 outColor;
void main() {
outColor = vec4(point_color, 1.);
}

View file

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