forked from StudioInfinity/dyna3
Introduce an element trait
For now, this is just a thin wrapper around the old element structure, which was renamed to `Sphere` in the previous commit. The biggest organizational change is moving `cast` into the `DisplayItem` trait.
This commit is contained in:
parent
a1e23543cb
commit
f9df459a0d
3 changed files with 165 additions and 98 deletions
|
@ -1,11 +1,17 @@
|
||||||
use nalgebra::{DMatrix, DVector, DVectorView, Vector3};
|
use nalgebra::{DMatrix, DVector, DVectorView};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::{collections::BTreeSet, rc::Rc, sync::atomic::{AtomicU64, Ordering}};
|
use std::{
|
||||||
|
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,
|
||||||
|
@ -33,28 +39,54 @@ 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<Sphere>);
|
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Element: ProblemPoser + DisplayItem {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub struct Sphere {
|
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 Sphere {
|
||||||
|
@ -84,57 +116,44 @@ impl Sphere {
|
||||||
representation: create_signal(representation),
|
representation: create_signal(representation),
|
||||||
regulators: create_signal(BTreeSet::default()),
|
regulators: create_signal(BTreeSet::default()),
|
||||||
serial: serial,
|
serial: serial,
|
||||||
column_index: None
|
column_index: None.into()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
||||||
|
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 {
|
impl ProblemPoser for Sphere {
|
||||||
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Sphere>) {
|
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
|
||||||
let index = self.column_index.expect(
|
let index = self.column_index().expect(
|
||||||
format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str()
|
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);
|
||||||
|
@ -168,7 +187,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))
|
||||||
|
@ -198,11 +217,11 @@ impl Regulator for InversiveDistanceRegulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProblemPoser for InversiveDistanceRegulator {
|
impl ProblemPoser for InversiveDistanceRegulator {
|
||||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Sphere>) {
|
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn 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"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -221,7 +240,7 @@ 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[Sphere::CURVATURE_COMPONENT]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -249,7 +268,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)
|
||||||
|
@ -262,10 +281,10 @@ impl Regulator for HalfCurvatureRegulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProblemPoser for HalfCurvatureRegulator {
|
impl ProblemPoser for HalfCurvatureRegulator {
|
||||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Sphere>) {
|
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn 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(Sphere::CURVATURE_COMPONENT, col, val);
|
||||||
|
@ -286,7 +305,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<Sphere>>,
|
pub elements: Signal<Slab<Rc<dyn 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
|
||||||
|
@ -323,7 +342,7 @@ impl Assembly {
|
||||||
fn insert_sphere_unchecked(&self, sphere: Sphere) -> ElementKey {
|
fn insert_sphere_unchecked(&self, sphere: Sphere) -> ElementKey {
|
||||||
// insert the sphere
|
// insert the sphere
|
||||||
let id = sphere.id.clone();
|
let id = sphere.id.clone();
|
||||||
let key = self.elements.update(|elts| elts.insert(sphere));
|
let key = self.elements.update(|elts| elts.insert(Rc::new(sphere)));
|
||||||
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));
|
||||||
|
|
||||||
// regulate the sphere's curvature
|
// regulate the sphere's curvature
|
||||||
|
@ -376,7 +395,7 @@ impl Assembly {
|
||||||
let subjects = regulator_rc.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 {
|
||||||
|
@ -427,7 +446,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.column_index = Some(index);
|
elt.set_column_index(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -482,8 +501,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()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -521,8 +540,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.column_index = Some(next_column_index);
|
moving_elt.set_column_index(next_column_index);
|
||||||
next_column_index += 1;
|
next_column_index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -539,7 +558,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 {
|
||||||
|
@ -555,7 +574,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())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -570,8 +589,8 @@ impl Assembly {
|
||||||
// since our test assemblies only include spheres, we assume that every
|
// since our test assemblies only include spheres, we assume that every
|
||||||
// element is on the 1 mass shell
|
// 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);
|
||||||
|
@ -586,7 +605,7 @@ impl Assembly {
|
||||||
},
|
},
|
||||||
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())
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -623,18 +642,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::new();
|
let mut elts = Slab::<Rc<dyn Element>>::new();
|
||||||
let subjects = [0, 1].map(|k| {
|
let subjects = [0, 1].map(|k| {
|
||||||
elts.insert(
|
elts.insert(
|
||||||
Sphere::new(
|
Rc::new(Sphere::new(
|
||||||
format!("sphere{k}"),
|
format!("sphere{k}"),
|
||||||
format!("Sphere {k}"),
|
format!("Sphere {k}"),
|
||||||
[1.0_f32, 1.0_f32, 1.0_f32],
|
[1.0_f32, 1.0_f32, 1.0_f32],
|
||||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||||
)
|
))
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
elts[subjects[0]].column_index = Some(0);
|
elts[subjects[0]].set_column_index(0);
|
||||||
InversiveDistanceRegulator {
|
InversiveDistanceRegulator {
|
||||||
subjects: subjects,
|
subjects: subjects,
|
||||||
measurement: create_memo(|| 0.0),
|
measurement: create_memo(|| 0.0),
|
||||||
|
|
|
@ -56,7 +56,7 @@ impl ScenePoints {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Scene {
|
pub struct Scene {
|
||||||
spheres: SceneSpheres,
|
spheres: SceneSpheres,
|
||||||
points: ScenePoints
|
points: ScenePoints
|
||||||
}
|
}
|
||||||
|
@ -70,8 +70,13 @@ impl Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait DisplayItem {
|
pub trait DisplayItem {
|
||||||
fn show(&self, scene: &mut Scene, selected: bool);
|
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 {
|
impl DisplayItem for Sphere {
|
||||||
|
@ -82,6 +87,46 @@ impl DisplayItem for Sphere {
|
||||||
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
||||||
scene.spheres.push(representation, color, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- WebGL utilities ---
|
// --- WebGL utilities ---
|
||||||
|
@ -264,7 +309,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();
|
||||||
|
|
|
@ -9,9 +9,10 @@ use web_sys::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
assembly,
|
|
||||||
assembly::{
|
assembly::{
|
||||||
|
Element,
|
||||||
ElementKey,
|
ElementKey,
|
||||||
|
ElementRc,
|
||||||
HalfCurvatureRegulator,
|
HalfCurvatureRegulator,
|
||||||
InversiveDistanceRegulator,
|
InversiveDistanceRegulator,
|
||||||
Regulator,
|
Regulator,
|
||||||
|
@ -103,7 +104,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") {
|
||||||
|
@ -141,14 +142,15 @@ 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: assembly::Sphere) -> View {
|
fn ElementOutlineItem(key: ElementKey, element: Rc<dyn 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 || {
|
||||||
element.representation.with(
|
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}");
|
||||||
|
@ -157,8 +159,8 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Sphere) -> 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()
|
||||||
|
@ -261,7 +263,8 @@ 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()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -275,10 +278,10 @@ pub fn Outline() -> View {
|
||||||
) {
|
) {
|
||||||
Keyed(
|
Keyed(
|
||||||
list=element_list,
|
list=element_list,
|
||||||
view=|(key, elt)| view! {
|
view=|(key, ElementRc(elt))| view! {
|
||||||
ElementOutlineItem(key=key, element=elt)
|
ElementOutlineItem(key=key, element=elt)
|
||||||
},
|
},
|
||||||
key=|(_, elt)| elt.serial
|
key=|(_, ElementRc(elt))| elt.serial()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue