Use pointers to refer to elements and regulators #84
8 changed files with 288 additions and 270 deletions
|
@ -11,7 +11,7 @@ jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
container:
|
container:
|
||||||
image: cimg/rust:1.85-node
|
image: cimg/rust:1.86-node
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
# set the default working directory for each `run` step, relative to the
|
# set the default working directory for each `run` step, relative to the
|
||||||
|
|
19
app-proto/Cargo.lock
generated
19
app-proto/Cargo.lock
generated
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
|
@ -89,8 +89,6 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
"readonly",
|
"readonly",
|
||||||
"rustc-hash",
|
|
||||||
"slab",
|
|
||||||
"sycamore",
|
"sycamore",
|
||||||
"wasm-bindgen-test",
|
"wasm-bindgen-test",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
|
@ -365,12 +363,6 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc-hash"
|
|
||||||
version = "2.0.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "safe_arch"
|
name = "safe_arch"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -414,15 +406,6 @@ dependencies = [
|
||||||
"wide",
|
"wide",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slab"
|
|
||||||
version = "0.4.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slotmap"
|
name = "slotmap"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
|
|
@ -3,6 +3,7 @@ name = "dyna3"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Aaron Fenyes", "Glen Whitney"]
|
authors = ["Aaron Fenyes", "Glen Whitney"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
rust-version = "1.86"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["console_error_panic_hook"]
|
default = ["console_error_panic_hook"]
|
||||||
|
@ -14,8 +15,6 @@ js-sys = "0.3.70"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
nalgebra = "0.33.0"
|
nalgebra = "0.33.0"
|
||||||
readonly = "0.2.12"
|
readonly = "0.2.12"
|
||||||
rustc-hash = "2.0.0"
|
|
||||||
slab = "0.4.9"
|
|
||||||
sycamore = "0.9.0-beta.3"
|
sycamore = "0.9.0-beta.3"
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
|
|
|
@ -241,7 +241,7 @@ pub fn AddRemove() -> View {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
);
|
);
|
||||||
state.assembly.insert_regulator(
|
state.assembly.insert_regulator(
|
||||||
Rc::new(InversiveDistanceRegulator::new(subjects, &state.assembly))
|
Rc::new(InversiveDistanceRegulator::new(subjects))
|
||||||
);
|
);
|
||||||
state.selection.update(|sel| sel.clear());
|
state.selection.update(|sel| sel.clear());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use nalgebra::{DMatrix, DVector, DVectorView};
|
use nalgebra::{DMatrix, DVector, DVectorView};
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use slab::Slab;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
collections::BTreeSet,
|
collections::{BTreeMap, BTreeSet},
|
||||||
|
cmp::Ordering,
|
||||||
|
fmt,
|
||||||
|
fmt::{Debug, Formatter},
|
||||||
|
hash::{Hash, Hasher},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::atomic::{AtomicU64, Ordering}
|
sync::{atomic, atomic::AtomicU64}
|
||||||
};
|
};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
||||||
|
@ -27,49 +29,16 @@ use crate::{
|
||||||
specified::SpecifiedValue
|
specified::SpecifiedValue
|
||||||
};
|
};
|
||||||
|
|
||||||
// the types of the keys we use to access an assembly's elements and regulators
|
|
||||||
pub type ElementKey = usize;
|
|
||||||
pub type RegulatorKey = usize;
|
|
||||||
|
|
||||||
pub type ElementColor = [f32; 3];
|
pub type ElementColor = [f32; 3];
|
||||||
|
|
||||||
/* KLUDGE */
|
/* KLUDGE */
|
||||||
// we should reconsider this design when we build a system for switching between
|
// we should reconsider this design when we build a system for switching between
|
||||||
// assemblies. at that point, we might want to switch to hierarchical keys,
|
// assemblies. at that point, we might want to switch to hierarchical keys,
|
||||||
// where each each element has a key that identifies it within its assembly and
|
// where each each item has a key that identifies it within its assembly and
|
||||||
// each assembly has a key that identifies it within the sesssion
|
// each assembly has a key that identifies it within the sesssion
|
||||||
static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
static NEXT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
pub trait ProblemPoser {
|
pub trait Serial {
|
||||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Element: ProblemPoser + DisplayItem {
|
|
||||||
// the default identifier for an element of this type
|
|
||||||
fn default_id() -> String where Self: Sized;
|
|
||||||
|
|
||||||
// create the default example of an element of this type
|
|
||||||
fn default(id: String, id_num: u64) -> Self where Self: Sized;
|
|
||||||
|
|
||||||
// the regulators that should be created when an element of this type is
|
|
||||||
// inserted into the given assembly with the given storage key
|
|
||||||
/* KLUDGE */
|
|
||||||
// right now, this organization makes sense because regulators identify
|
|
||||||
// their subjects by storage key, so the element has to be inserted before
|
|
||||||
// its regulators can be created. if we change the way regulators identify
|
|
||||||
// their subjects, we should consider refactoring
|
|
||||||
fn default_regulators(_key: ElementKey, _assembly: &Assembly) -> Vec<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
|
// a serial number that uniquely identifies this element
|
||||||
fn serial(&self) -> u64;
|
fn serial(&self) -> u64;
|
||||||
|
|
||||||
|
@ -80,11 +49,62 @@ pub trait Element: ProblemPoser + DisplayItem {
|
||||||
//
|
//
|
||||||
// https://marabos.nl/atomics/atomics.html#example-handle-overflow
|
// https://marabos.nl/atomics/atomics.html#example-handle-overflow
|
||||||
//
|
//
|
||||||
NEXT_ELEMENT_SERIAL.fetch_update(
|
NEXT_SERIAL.fetch_update(
|
||||||
Ordering::SeqCst, Ordering::SeqCst,
|
atomic::Ordering::SeqCst, atomic::Ordering::SeqCst,
|
||||||
|serial| serial.checked_add(1)
|
|serial| serial.checked_add(1)
|
||||||
).expect("Out of serial numbers for elements")
|
).expect("Out of serial numbers for elements")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for dyn Serial {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.serial().hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for dyn Serial {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.serial() == other.serial()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for dyn Serial {}
|
||||||
|
|
||||||
|
impl PartialOrd for dyn Serial {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for dyn Serial {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.serial().cmp(&other.serial())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ProblemPoser {
|
||||||
|
fn pose(&self, problem: &mut ConstraintProblem);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Element: Serial + ProblemPoser + DisplayItem {
|
||||||
|
// the default identifier for an element of this type
|
||||||
|
fn default_id() -> String where Self: Sized;
|
||||||
|
|
||||||
|
// the default example of an element of this type
|
||||||
|
fn default(id: String, id_num: u64) -> Self where Self: Sized;
|
||||||
|
|
||||||
|
// the default regulators that come with this element
|
||||||
|
fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> {
|
||||||
|
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<Rc<dyn Regulator>>>;
|
||||||
|
|
||||||
// the configuration matrix column index that was assigned to the element
|
// the configuration matrix column index that was assigned to the element
|
||||||
// last time the assembly was realized, or `None` if the element has never
|
// last time the assembly was realized, or `None` if the element has never
|
||||||
|
@ -97,17 +117,35 @@ pub trait Element: ProblemPoser + DisplayItem {
|
||||||
fn set_column_index(&self, index: usize);
|
fn set_column_index(&self, index: usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// the `Element` trait needs to be dyn-compatible, so its method signatures can
|
impl Debug for dyn Element {
|
||||||
// only use `Self` in the type of the receiver. that means `Element` can't
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
// implement `PartialEq`. if you need partial equivalence for `Element` trait
|
self.id().fmt(f)
|
||||||
// objects, use this wrapper
|
}
|
||||||
#[derive(Clone)]
|
}
|
||||||
pub struct ElementRc(pub Rc<dyn Element>);
|
|
||||||
|
|
||||||
impl PartialEq for ElementRc {
|
impl Hash for dyn Element {
|
||||||
fn eq(&self, ElementRc(other): &Self) -> bool {
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
let ElementRc(rc) = self;
|
<dyn Serial>::hash(self, state)
|
||||||
Rc::ptr_eq(rc, &other)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for dyn Element {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
<dyn Serial>::eq(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for dyn Element {}
|
||||||
|
|
||||||
|
impl PartialOrd for dyn Element {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
<dyn Serial>::partial_cmp(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for dyn Element {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
<dyn Serial>::cmp(self, other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,8 +154,8 @@ pub struct Sphere {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub color: ElementColor,
|
pub color: ElementColor,
|
||||||
pub representation: Signal<DVector<f64>>,
|
pub representation: Signal<DVector<f64>>,
|
||||||
pub regulators: Signal<BTreeSet<RegulatorKey>>,
|
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
||||||
pub serial: u64,
|
serial: u64,
|
||||||
column_index: Cell<Option<usize>>
|
column_index: Cell<Option<usize>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,7 +173,7 @@ impl Sphere {
|
||||||
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::new()),
|
||||||
serial: Self::next_serial(),
|
serial: Self::next_serial(),
|
||||||
column_index: None.into()
|
column_index: None.into()
|
||||||
}
|
}
|
||||||
|
@ -156,8 +194,8 @@ impl Element for Sphere {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec<Rc<dyn Regulator>> {
|
fn default_regulators(self: Rc<Self>) -> Vec<Rc<dyn Regulator>> {
|
||||||
vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))]
|
vec![Rc::new(HalfCurvatureRegulator::new(self))]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> &String {
|
fn id(&self) -> &String {
|
||||||
|
@ -172,14 +210,10 @@ impl Element for Sphere {
|
||||||
self.representation
|
self.representation
|
||||||
}
|
}
|
||||||
|
|
||||||
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
|
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
|
||||||
self.regulators
|
self.regulators
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serial(&self) -> u64 {
|
|
||||||
self.serial
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column_index(&self) -> Option<usize> {
|
fn column_index(&self) -> Option<usize> {
|
||||||
self.column_index.get()
|
self.column_index.get()
|
||||||
}
|
}
|
||||||
|
@ -189,8 +223,14 @@ impl Element for Sphere {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serial for Sphere {
|
||||||
|
fn serial(&self) -> u64 {
|
||||||
|
self.serial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ProblemPoser for Sphere {
|
impl ProblemPoser for Sphere {
|
||||||
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
|
fn pose(&self, problem: &mut ConstraintProblem) {
|
||||||
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()
|
||||||
);
|
);
|
||||||
|
@ -204,8 +244,8 @@ pub struct Point {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub color: ElementColor,
|
pub color: ElementColor,
|
||||||
pub representation: Signal<DVector<f64>>,
|
pub representation: Signal<DVector<f64>>,
|
||||||
pub regulators: Signal<BTreeSet<RegulatorKey>>,
|
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
||||||
pub serial: u64,
|
serial: u64,
|
||||||
column_index: Cell<Option<usize>>
|
column_index: Cell<Option<usize>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +263,7 @@ impl Point {
|
||||||
label,
|
label,
|
||||||
color,
|
color,
|
||||||
representation: create_signal(representation),
|
representation: create_signal(representation),
|
||||||
regulators: create_signal(BTreeSet::default()),
|
regulators: create_signal(BTreeSet::new()),
|
||||||
serial: Self::next_serial(),
|
serial: Self::next_serial(),
|
||||||
column_index: None.into()
|
column_index: None.into()
|
||||||
}
|
}
|
||||||
|
@ -256,14 +296,10 @@ impl Element for Point {
|
||||||
self.representation
|
self.representation
|
||||||
}
|
}
|
||||||
|
|
||||||
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
|
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
|
||||||
self.regulators
|
self.regulators
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serial(&self) -> u64 {
|
|
||||||
self.serial
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column_index(&self) -> Option<usize> {
|
fn column_index(&self) -> Option<usize> {
|
||||||
self.column_index.get()
|
self.column_index.get()
|
||||||
}
|
}
|
||||||
|
@ -273,8 +309,14 @@ impl Element for Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serial for Point {
|
||||||
|
fn serial(&self) -> u64 {
|
||||||
|
self.serial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ProblemPoser for Point {
|
impl ProblemPoser for Point {
|
||||||
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
|
fn pose(&self, problem: &mut ConstraintProblem) {
|
||||||
let index = self.column_index().expect(
|
let index = self.column_index().expect(
|
||||||
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str()
|
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str()
|
||||||
);
|
);
|
||||||
|
@ -284,8 +326,8 @@ impl ProblemPoser for Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Regulator: ProblemPoser + OutlineItem {
|
pub trait Regulator: Serial + ProblemPoser + OutlineItem {
|
||||||
fn subjects(&self) -> Vec<ElementKey>;
|
fn subjects(&self) -> Vec<Rc<dyn Element>>;
|
||||||
fn measurement(&self) -> ReadSignal<f64>;
|
fn measurement(&self) -> ReadSignal<f64>;
|
||||||
fn set_point(&self) -> Signal<SpecifiedValue>;
|
fn set_point(&self) -> Signal<SpecifiedValue>;
|
||||||
|
|
||||||
|
@ -295,39 +337,65 @@ pub trait Regulator: ProblemPoser + OutlineItem {
|
||||||
// preconditioning when the set point is present, and use its return value
|
// preconditioning when the set point is present, and use its return value
|
||||||
// to report whether the set is present. the default implementation does no
|
// to report whether the set is present. the default implementation does no
|
||||||
// preconditioning
|
// preconditioning
|
||||||
fn try_activate(&self, _assembly: &Assembly) -> bool {
|
fn try_activate(&self) -> bool {
|
||||||
self.set_point().with(|set_pt| set_pt.is_present())
|
self.set_point().with(|set_pt| set_pt.is_present())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for dyn Regulator {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
<dyn Serial>::hash(self, state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for dyn Regulator {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
<dyn Serial>::eq(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for dyn Regulator {}
|
||||||
|
|
||||||
|
impl PartialOrd for dyn Regulator {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
<dyn Serial>::partial_cmp(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for dyn Regulator {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
<dyn Serial>::cmp(self, other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct InversiveDistanceRegulator {
|
pub struct InversiveDistanceRegulator {
|
||||||
pub subjects: [ElementKey; 2],
|
pub subjects: [Rc<dyn Element>; 2],
|
||||||
pub measurement: ReadSignal<f64>,
|
pub measurement: ReadSignal<f64>,
|
||||||
pub set_point: Signal<SpecifiedValue>
|
pub set_point: Signal<SpecifiedValue>,
|
||||||
|
serial: u64
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InversiveDistanceRegulator {
|
impl InversiveDistanceRegulator {
|
||||||
pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator {
|
pub fn new(subjects: [Rc<dyn Element>; 2]) -> InversiveDistanceRegulator {
|
||||||
let measurement = assembly.elements.map(
|
let representations = subjects.each_ref().map(|subj| subj.representation());
|
||||||
move |elts| {
|
let measurement = create_memo(move || {
|
||||||
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))
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
);
|
});
|
||||||
|
|
||||||
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
||||||
|
let serial = Self::next_serial();
|
||||||
|
|
||||||
InversiveDistanceRegulator { subjects, measurement, set_point }
|
InversiveDistanceRegulator { subjects, measurement, set_point, serial }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Regulator for InversiveDistanceRegulator {
|
impl Regulator for InversiveDistanceRegulator {
|
||||||
fn subjects(&self) -> Vec<ElementKey> {
|
fn subjects(&self) -> Vec<Rc<dyn Element>> {
|
||||||
self.subjects.into()
|
self.subjects.clone().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measurement(&self) -> ReadSignal<f64> {
|
fn measurement(&self) -> ReadSignal<f64> {
|
||||||
|
@ -339,12 +407,18 @@ impl Regulator for InversiveDistanceRegulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serial for InversiveDistanceRegulator {
|
||||||
|
fn serial(&self) -> u64 {
|
||||||
|
self.serial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
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.each_ref().map(
|
||||||
|subj| elts[subj].column_index().expect(
|
|subj| 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"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -355,28 +429,28 @@ impl ProblemPoser for InversiveDistanceRegulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct HalfCurvatureRegulator {
|
pub struct HalfCurvatureRegulator {
|
||||||
pub subject: ElementKey,
|
pub subject: Rc<dyn Element>,
|
||||||
pub measurement: ReadSignal<f64>,
|
pub measurement: ReadSignal<f64>,
|
||||||
pub set_point: Signal<SpecifiedValue>
|
pub set_point: Signal<SpecifiedValue>,
|
||||||
|
serial: u64
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HalfCurvatureRegulator {
|
impl HalfCurvatureRegulator {
|
||||||
pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator {
|
pub fn new(subject: Rc<dyn Element>) -> HalfCurvatureRegulator {
|
||||||
let measurement = assembly.elements.map(
|
let measurement = subject.representation().map(
|
||||||
move |elts| elts[subject].representation().with(
|
|rep| rep[Sphere::CURVATURE_COMPONENT]
|
||||||
|rep| rep[Sphere::CURVATURE_COMPONENT]
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
let set_point = create_signal(SpecifiedValue::from_empty_spec());
|
||||||
|
let serial = Self::next_serial();
|
||||||
|
|
||||||
HalfCurvatureRegulator { subject, measurement, set_point }
|
HalfCurvatureRegulator { subject, measurement, set_point, serial }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Regulator for HalfCurvatureRegulator {
|
impl Regulator for HalfCurvatureRegulator {
|
||||||
fn subjects(&self) -> Vec<ElementKey> {
|
fn subjects(&self) -> Vec<Rc<dyn Element>> {
|
||||||
vec![self.subject]
|
vec![self.subject.clone()]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn measurement(&self) -> ReadSignal<f64> {
|
fn measurement(&self) -> ReadSignal<f64> {
|
||||||
|
@ -387,13 +461,10 @@ impl Regulator for HalfCurvatureRegulator {
|
||||||
self.set_point
|
self.set_point
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_activate(&self, assembly: &Assembly) -> bool {
|
fn try_activate(&self) -> bool {
|
||||||
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(
|
self.subject.representation().update(
|
||||||
|elts| elts[self.subject].representation()
|
|
||||||
);
|
|
||||||
representation.update(
|
|
||||||
|rep| change_half_curvature(rep, half_curv)
|
|rep| change_half_curvature(rep, half_curv)
|
||||||
);
|
);
|
||||||
true
|
true
|
||||||
|
@ -403,11 +474,17 @@ impl Regulator for HalfCurvatureRegulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serial for HalfCurvatureRegulator {
|
||||||
|
fn serial(&self) -> u64 {
|
||||||
|
self.serial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
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 = 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);
|
||||||
|
@ -418,7 +495,7 @@ impl ProblemPoser for HalfCurvatureRegulator {
|
||||||
|
|
||||||
// the velocity is expressed in uniform coordinates
|
// the velocity is expressed in uniform coordinates
|
||||||
pub struct ElementMotion<'a> {
|
pub struct ElementMotion<'a> {
|
||||||
pub key: ElementKey,
|
pub element: Rc<dyn Element>,
|
||||||
pub velocity: DVectorView<'a, f64>
|
pub velocity: DVectorView<'a, f64>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -428,8 +505,8 @@ 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<BTreeSet<Rc<dyn Element>>>,
|
||||||
pub regulators: Signal<Slab<Rc<dyn Regulator>>>,
|
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
||||||
|
|
||||||
// solution variety tangent space. the basis vectors are stored in
|
// solution variety tangent space. the basis vectors are stored in
|
||||||
// configuration matrix format, ordered according to the elements' column
|
// configuration matrix format, ordered according to the elements' column
|
||||||
|
@ -444,16 +521,16 @@ pub struct Assembly {
|
||||||
pub tangent: Signal<ConfigSubspace>,
|
pub tangent: Signal<ConfigSubspace>,
|
||||||
|
|
||||||
// indexing
|
// indexing
|
||||||
pub elements_by_id: Signal<FxHashMap<String, ElementKey>>
|
pub elements_by_id: Signal<BTreeMap<String, Rc<dyn Element>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assembly {
|
impl Assembly {
|
||||||
pub fn new() -> Assembly {
|
pub fn new() -> Assembly {
|
||||||
Assembly {
|
Assembly {
|
||||||
elements: create_signal(Slab::new()),
|
elements: create_signal(BTreeSet::new()),
|
||||||
regulators: create_signal(Slab::new()),
|
regulators: create_signal(BTreeSet::new()),
|
||||||
tangent: create_signal(ConfigSubspace::zero(0)),
|
tangent: create_signal(ConfigSubspace::zero(0)),
|
||||||
elements_by_id: create_signal(FxHashMap::default())
|
elements_by_id: create_signal(BTreeMap::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,29 +539,27 @@ impl Assembly {
|
||||||
// insert an element into the assembly without checking whether we already
|
// insert an element 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_element_unchecked(&self, elt: impl Element + 'static) {
|
||||||
// insert the element
|
// insert the element
|
||||||
let id = elt.id().clone();
|
let id = elt.id().clone();
|
||||||
let key = self.elements.update(|elts| elts.insert(Rc::new(elt)));
|
let elt_rc = Rc::new(elt);
|
||||||
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
|
self.elements.update(|elts| elts.insert(elt_rc.clone()));
|
||||||
|
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, elt_rc.clone()));
|
||||||
|
|
||||||
// create and insert the element's default regulators
|
// create and insert the element's default regulators
|
||||||
for reg in T::default_regulators(key, &self) {
|
for reg in elt_rc.default_regulators() {
|
||||||
self.insert_regulator(reg);
|
self.insert_regulator(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_insert_element(&self, elt: impl Element + 'static) -> Option<ElementKey> {
|
pub fn try_insert_element(&self, elt: impl Element + 'static) -> bool {
|
||||||
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))
|
self.insert_element_unchecked(elt);
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
can_insert
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_element_default<T: Element + 'static>(&self) {
|
pub fn insert_element_default<T: Element + 'static>(&self) {
|
||||||
|
@ -505,19 +580,16 @@ impl Assembly {
|
||||||
|
|
||||||
pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) {
|
pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) {
|
||||||
// add the regulator to the assembly's regulator list
|
// add the regulator to the assembly's regulator list
|
||||||
let key = self.regulators.update(
|
self.regulators.update(
|
||||||
|regs| regs.insert(regulator.clone())
|
|regs| regs.insert(regulator.clone())
|
||||||
);
|
);
|
||||||
|
|
||||||
// add the regulator to each subject's regulator list
|
// add the regulator to each subject's regulator list
|
||||||
let subjects = regulator.subjects();
|
let subject_regulators: Vec<_> = regulator.subjects().into_iter().map(
|
||||||
let subject_regulators: Vec<_> = self.elements.with_untracked(
|
|subj| subj.regulators()
|
||||||
|elts| subjects.into_iter().map(
|
).collect();
|
||||||
|subj| elts[subj].regulators()
|
|
||||||
).collect()
|
|
||||||
);
|
|
||||||
for regulators in subject_regulators {
|
for regulators in subject_regulators {
|
||||||
regulators.update(|regs| regs.insert(key));
|
regulators.update(|regs| regs.insert(regulator.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// update the realization when the regulator becomes a constraint, or is
|
// update the realization when the regulator becomes a constraint, or is
|
||||||
|
@ -530,7 +602,7 @@ impl Assembly {
|
||||||
format!("Updated regulator with subjects {:?}", regulator.subjects())
|
format!("Updated regulator with subjects {:?}", regulator.subjects())
|
||||||
));
|
));
|
||||||
|
|
||||||
if regulator.try_activate(&self_for_effect) {
|
if regulator.try_activate() {
|
||||||
self_for_effect.realize();
|
self_for_effect.realize();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -539,7 +611,7 @@ impl Assembly {
|
||||||
// print an updated list of regulators
|
// print an updated list of regulators
|
||||||
console::log_1(&JsValue::from("Regulators:"));
|
console::log_1(&JsValue::from("Regulators:"));
|
||||||
self.regulators.with_untracked(|regs| {
|
self.regulators.with_untracked(|regs| {
|
||||||
for (_, reg) in regs.into_iter() {
|
for reg in regs.into_iter() {
|
||||||
console::log_1(&JsValue::from(format!(
|
console::log_1(&JsValue::from(format!(
|
||||||
" {:?}: {}",
|
" {:?}: {}",
|
||||||
reg.subjects(),
|
reg.subjects(),
|
||||||
|
@ -563,7 +635,7 @@ impl Assembly {
|
||||||
pub fn realize(&self) {
|
pub fn realize(&self) {
|
||||||
// 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.iter().enumerate() {
|
||||||
elt.set_column_index(index);
|
elt.set_column_index(index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -571,12 +643,12 @@ impl Assembly {
|
||||||
// set up the constraint problem
|
// set up the constraint problem
|
||||||
let problem = self.elements.with_untracked(|elts| {
|
let problem = self.elements.with_untracked(|elts| {
|
||||||
let mut problem = ConstraintProblem::new(elts.len());
|
let mut problem = ConstraintProblem::new(elts.len());
|
||||||
for (_, elt) in elts {
|
for elt in elts {
|
||||||
elt.pose(&mut problem, elts);
|
elt.pose(&mut problem);
|
||||||
}
|
}
|
||||||
self.regulators.with_untracked(|regs| {
|
self.regulators.with_untracked(|regs| {
|
||||||
for (_, reg) in regs {
|
for reg in regs {
|
||||||
reg.pose(&mut problem, elts);
|
reg.pose(&mut problem);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
problem
|
problem
|
||||||
|
@ -618,7 +690,7 @@ 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()))
|
||||||
);
|
);
|
||||||
|
@ -654,17 +726,17 @@ impl Assembly {
|
||||||
// in the process, we find out how many matrix columns we'll need to
|
// in the process, we find out how many matrix columns we'll need to
|
||||||
// hold the deformation
|
// hold the deformation
|
||||||
let realized_dim = self.tangent.with(|tan| tan.assembly_dim());
|
let realized_dim = self.tangent.with(|tan| tan.assembly_dim());
|
||||||
let motion_dim = self.elements.update_silent(|elts| {
|
let motion_dim = {
|
||||||
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 = &elt_motion.element;
|
||||||
if moving_elt.column_index().is_none() {
|
if moving_elt.column_index().is_none() {
|
||||||
moving_elt.set_column_index(next_column_index);
|
moving_elt.set_column_index(next_column_index);
|
||||||
next_column_index += 1;
|
next_column_index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next_column_index
|
next_column_index
|
||||||
});
|
};
|
||||||
|
|
||||||
// project the element motions onto the tangent space of the solution
|
// project the element motions onto the tangent space of the solution
|
||||||
// variety and sum them to get a deformation of the whole assembly. the
|
// variety and sum them to get a deformation of the whole assembly. the
|
||||||
|
@ -675,9 +747,7 @@ impl Assembly {
|
||||||
for elt_motion in motion {
|
for elt_motion in motion {
|
||||||
// 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 = elt_motion.element.column_index().unwrap();
|
||||||
|elts| elts[elt_motion.key].column_index().unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
if column_index < realized_dim {
|
if column_index < realized_dim {
|
||||||
// this element had a column index when we started, so by
|
// this element had a column index when we started, so by
|
||||||
|
@ -690,12 +760,8 @@ impl Assembly {
|
||||||
// this element didn't have a column index when we started, so
|
// this element didn't have a column index when we started, so
|
||||||
// by invariant (2), it's unconstrained
|
// by invariant (2), it's unconstrained
|
||||||
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 = elt_motion.element.representation().with_untracked(
|
||||||
|elts| {
|
|rep| local_unif_to_std(rep.as_view())
|
||||||
elts[elt_motion.key].representation().with_untracked(
|
|
||||||
|rep| local_unif_to_std(rep.as_view())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
target_column += unif_to_std * elt_motion.velocity;
|
target_column += unif_to_std * elt_motion.velocity;
|
||||||
}
|
}
|
||||||
|
@ -705,7 +771,7 @@ impl Assembly {
|
||||||
// normalizations, so we restore those afterward
|
// normalizations, so we restore those afterward
|
||||||
/* KLUDGE */
|
/* KLUDGE */
|
||||||
// for now, we only restore the normalizations of spheres
|
// for now, we only restore the normalizations of spheres
|
||||||
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) => {
|
||||||
|
@ -747,7 +813,7 @@ mod tests {
|
||||||
fn unindexed_element_test() {
|
fn unindexed_element_test() {
|
||||||
let _ = create_root(|| {
|
let _ = create_root(|| {
|
||||||
let elt = Sphere::default("sphere".to_string(), 0);
|
let elt = Sphere::default("sphere".to_string(), 0);
|
||||||
elt.pose(&mut ConstraintProblem::new(1), &Slab::new());
|
elt.pose(&mut ConstraintProblem::new(1));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,18 +821,16 @@ 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 subjects = [0, 1].map(
|
||||||
let subjects = [0, 1].map(|k| {
|
|k| Rc::new(Sphere::default(format!("sphere{k}"), k)) as Rc<dyn Element>
|
||||||
elts.insert(
|
);
|
||||||
Rc::new(Sphere::default(format!("sphere{k}"), k))
|
subjects[0].set_column_index(0);
|
||||||
)
|
|
||||||
});
|
|
||||||
elts[subjects[0]].set_column_index(0);
|
|
||||||
InversiveDistanceRegulator {
|
InversiveDistanceRegulator {
|
||||||
subjects: subjects,
|
subjects: subjects,
|
||||||
measurement: create_memo(|| 0.0),
|
measurement: create_memo(|| 0.0),
|
||||||
set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap())
|
set_point: create_signal(SpecifiedValue::try_from("0.0".to_string()).unwrap()),
|
||||||
}.pose(&mut ConstraintProblem::new(2), &elts);
|
serial: InversiveDistanceRegulator::next_serial()
|
||||||
|
}.pose(&mut ConstraintProblem::new(2));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
use core::array;
|
use core::array;
|
||||||
use nalgebra::{DMatrix, DVector, Rotation3, Vector3};
|
use nalgebra::{DMatrix, DVector, Rotation3, Vector3};
|
||||||
|
use std::rc::Rc;
|
||||||
use sycamore::{prelude::*, motion::create_raf};
|
use sycamore::{prelude::*, motion::create_raf};
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
console,
|
console,
|
||||||
|
@ -16,7 +17,7 @@ use web_sys::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere}
|
assembly::{Element, ElementColor, ElementMotion, Point, Sphere}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- scene data ---
|
// --- scene data ---
|
||||||
|
@ -362,7 +363,7 @@ pub fn Display() -> View {
|
||||||
let scene_changed = create_signal(true);
|
let scene_changed = create_signal(true);
|
||||||
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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -548,7 +549,7 @@ pub fn Display() -> View {
|
||||||
// manipulate the assembly
|
// manipulate the assembly
|
||||||
if state.selection.with(|sel| sel.len() == 1) {
|
if state.selection.with(|sel| sel.len() == 1) {
|
||||||
let sel = state.selection.with(
|
let sel = state.selection.with(
|
||||||
|sel| *sel.into_iter().next().unwrap()
|
|sel| sel.into_iter().next().unwrap().clone()
|
||||||
);
|
);
|
||||||
let translate_x = translate_pos_x_val - translate_neg_x_val;
|
let translate_x = translate_pos_x_val - translate_neg_x_val;
|
||||||
let translate_y = translate_pos_y_val - translate_neg_y_val;
|
let translate_y = translate_pos_y_val - translate_neg_y_val;
|
||||||
|
@ -574,7 +575,7 @@ pub fn Display() -> View {
|
||||||
assembly_for_raf.deform(
|
assembly_for_raf.deform(
|
||||||
vec![
|
vec![
|
||||||
ElementMotion {
|
ElementMotion {
|
||||||
key: sel,
|
element: sel,
|
||||||
velocity: elt_motion.as_view()
|
velocity: elt_motion.as_view()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -615,8 +616,8 @@ pub fn Display() -> View {
|
||||||
|
|
||||||
// set up the scene
|
// set up the scene
|
||||||
state.assembly.elements.with_untracked(
|
state.assembly.elements.with_untracked(
|
||||||
|elts| for (key, elt) in elts {
|
|elts| for elt in elts {
|
||||||
let selected = state.selection.with(|sel| sel.contains(&key));
|
let selected = state.selection.with(|sel| sel.contains(elt));
|
||||||
elt.show(&mut scene, selected);
|
elt.show(&mut scene, selected);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -849,16 +850,16 @@ pub fn Display() -> View {
|
||||||
// find the nearest element along the pointer direction
|
// find the nearest element along the pointer direction
|
||||||
let (dir, pixel_size) = event_dir(&event);
|
let (dir, pixel_size) = event_dir(&event);
|
||||||
console::log_1(&JsValue::from(dir.to_string()));
|
console::log_1(&JsValue::from(dir.to_string()));
|
||||||
let mut clicked: Option<(ElementKey, f64)> = None;
|
let mut clicked: Option<(Rc<dyn Element>, f64)> = None;
|
||||||
for (key, elt) in state.assembly.elements.get_clone_untracked() {
|
for elt in state.assembly.elements.get_clone_untracked() {
|
||||||
match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world, pixel_size)) {
|
match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world, pixel_size)) {
|
||||||
Some(depth) => match clicked {
|
Some(depth) => match clicked {
|
||||||
Some((_, best_depth)) => {
|
Some((_, best_depth)) => {
|
||||||
if depth < best_depth {
|
if depth < best_depth {
|
||||||
clicked = Some((key, depth))
|
clicked = Some((elt, depth))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => clicked = Some((key, depth))
|
None => clicked = Some((elt, depth))
|
||||||
}
|
}
|
||||||
None => ()
|
None => ()
|
||||||
};
|
};
|
||||||
|
@ -866,7 +867,7 @@ pub fn Display() -> View {
|
||||||
|
|
||||||
// if we clicked something, select it
|
// if we clicked something, select it
|
||||||
match clicked {
|
match clicked {
|
||||||
Some((key, _)) => state.select(key, event.shift_key()),
|
Some((elt, _)) => state.select(&elt, event.shift_key()),
|
||||||
None => state.selection.update(|sel| sel.clear())
|
None => state.selection.update(|sel| sel.clear())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,42 +8,41 @@ mod specified;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
use rustc_hash::FxHashSet;
|
use std::{collections::BTreeSet, rc::Rc};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
use add_remove::AddRemove;
|
use add_remove::AddRemove;
|
||||||
use assembly::{Assembly, ElementKey};
|
use assembly::{Assembly, Element};
|
||||||
use display::Display;
|
use display::Display;
|
||||||
use outline::Outline;
|
use outline::Outline;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
assembly: Assembly,
|
assembly: Assembly,
|
||||||
selection: Signal<FxHashSet<ElementKey>>
|
selection: Signal<BTreeSet<Rc<dyn Element>>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
fn new() -> AppState {
|
fn new() -> AppState {
|
||||||
AppState {
|
AppState {
|
||||||
assembly: Assembly::new(),
|
assembly: Assembly::new(),
|
||||||
selection: create_signal(FxHashSet::default())
|
selection: create_signal(BTreeSet::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in single-selection mode, select the element with the given key. in
|
// in single-selection mode, select the given element. in multiple-selection
|
||||||
// multiple-selection mode, toggle whether the element with the given key
|
// mode, toggle whether the given element is selected
|
||||||
// is selected
|
fn select(&self, element: &Rc<dyn Element>, multi: bool) {
|
||||||
fn select(&self, key: ElementKey, multi: bool) {
|
|
||||||
if multi {
|
if multi {
|
||||||
self.selection.update(|sel| {
|
self.selection.update(|sel| {
|
||||||
if !sel.remove(&key) {
|
if !sel.remove(element) {
|
||||||
sel.insert(key);
|
sel.insert(element.clone());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.selection.update(|sel| {
|
self.selection.update(|sel| {
|
||||||
sel.clear();
|
sel.clear();
|
||||||
sel.insert(key);
|
sel.insert(element.clone());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,9 @@ use crate::{
|
||||||
AppState,
|
AppState,
|
||||||
assembly::{
|
assembly::{
|
||||||
Element,
|
Element,
|
||||||
ElementKey,
|
|
||||||
ElementRc,
|
|
||||||
HalfCurvatureRegulator,
|
HalfCurvatureRegulator,
|
||||||
InversiveDistanceRegulator,
|
InversiveDistanceRegulator,
|
||||||
Regulator,
|
Regulator
|
||||||
RegulatorKey
|
|
||||||
},
|
},
|
||||||
specified::SpecifiedValue
|
specified::SpecifiedValue
|
||||||
};
|
};
|
||||||
|
@ -92,20 +89,16 @@ fn RegulatorInput(regulator: Rc<dyn Regulator>) -> View {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait OutlineItem {
|
pub trait OutlineItem {
|
||||||
fn outline_item(self: Rc<Self>, element_key: ElementKey) -> View;
|
fn outline_item(self: Rc<Self>, element: &Rc<dyn Element>) -> View;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutlineItem for InversiveDistanceRegulator {
|
impl OutlineItem for InversiveDistanceRegulator {
|
||||||
fn outline_item(self: Rc<Self>, element_key: ElementKey) -> View {
|
fn outline_item(self: Rc<Self>, element: &Rc<dyn Element>) -> View {
|
||||||
let state = use_context::<AppState>();
|
let other_subject_label = if self.subjects[0] == element.clone() {
|
||||||
let other_subject = if self.subjects[0] == element_key {
|
self.subjects[1].label()
|
||||||
self.subjects[1]
|
|
||||||
} else {
|
} else {
|
||||||
self.subjects[0]
|
self.subjects[0].label()
|
||||||
};
|
}.clone();
|
||||||
let other_subject_label = state.assembly.elements.with(
|
|
||||||
|elts| elts[other_subject].label().clone()
|
|
||||||
);
|
|
||||||
view! {
|
view! {
|
||||||
li(class="regulator") {
|
li(class="regulator") {
|
||||||
div(class="regulator-label") { (other_subject_label) }
|
div(class="regulator-label") { (other_subject_label) }
|
||||||
|
@ -118,7 +111,7 @@ impl OutlineItem for InversiveDistanceRegulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutlineItem for HalfCurvatureRegulator {
|
impl OutlineItem for HalfCurvatureRegulator {
|
||||||
fn outline_item(self: Rc<Self>, _element_key: ElementKey) -> View {
|
fn outline_item(self: Rc<Self>, _element: &Rc<dyn Element>) -> View {
|
||||||
view! {
|
view! {
|
||||||
li(class="regulator") {
|
li(class="regulator") {
|
||||||
div(class="regulator-label") // for spacing
|
div(class="regulator-label") // for spacing
|
||||||
|
@ -130,23 +123,16 @@ impl OutlineItem for HalfCurvatureRegulator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// a list item that shows a regulator in an outline view of an element
|
|
||||||
#[component(inline_props)]
|
|
||||||
fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View {
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
let regulator = state.assembly.regulators.with(
|
|
||||||
|regs| regs[regulator_key].clone()
|
|
||||||
);
|
|
||||||
regulator.outline_item(element_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(element: Rc<dyn Element>) -> View {
|
||||||
let state = use_context::<AppState>();
|
let state = use_context::<AppState>();
|
||||||
let class = state.selection.map(
|
let class = {
|
||||||
move |sel| if sel.contains(&key) { "selected" } else { "" }
|
let element_for_class = element.clone();
|
||||||
);
|
state.selection.map(
|
||||||
|
move |sel| if sel.contains(&element_for_class) { "selected" } else { "" }
|
||||||
|
)
|
||||||
|
};
|
||||||
let label = element.label().clone();
|
let label = element.label().clone();
|
||||||
let representation = element.representation().clone();
|
let representation = element.representation().clone();
|
||||||
let rep_components = move || {
|
let rep_components = move || {
|
||||||
|
@ -161,14 +147,10 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
|
||||||
};
|
};
|
||||||
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
|
|regs| regs
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.sorted_by_key(
|
.sorted_by_key(|reg| reg.subjects().len())
|
||||||
|®_key| state.assembly.regulators.with(
|
|
||||||
|regs| regs[reg_key].subjects().len()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.collect()
|
.collect()
|
||||||
);
|
);
|
||||||
let details_node = create_node_ref();
|
let details_node = create_node_ref();
|
||||||
|
@ -178,10 +160,11 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
|
||||||
summary(
|
summary(
|
||||||
class=class.get(),
|
class=class.get(),
|
||||||
on:keydown={
|
on:keydown={
|
||||||
|
let element_for_handler = element.clone();
|
||||||
move |event: KeyboardEvent| {
|
move |event: KeyboardEvent| {
|
||||||
match event.key().as_str() {
|
match event.key().as_str() {
|
||||||
"Enter" => {
|
"Enter" => {
|
||||||
state.select(key, event.shift_key());
|
state.select(&element_for_handler, event.shift_key());
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
},
|
},
|
||||||
"ArrowRight" if regulated.get() => {
|
"ArrowRight" if regulated.get() => {
|
||||||
|
@ -208,19 +191,10 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
|
||||||
div(
|
div(
|
||||||
class="element",
|
class="element",
|
||||||
on:click={
|
on:click={
|
||||||
|
let state_for_handler = state.clone();
|
||||||
|
let element_for_handler = element.clone();
|
||||||
move |event: MouseEvent| {
|
move |event: MouseEvent| {
|
||||||
if event.shift_key() {
|
state_for_handler.select(&element_for_handler, event.shift_key());
|
||||||
state.selection.update(|sel| {
|
|
||||||
if !sel.remove(&key) {
|
|
||||||
sel.insert(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
state.selection.update(|sel| {
|
|
||||||
sel.clear();
|
|
||||||
sel.insert(key);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
event.stop_propagation();
|
event.stop_propagation();
|
||||||
event.prevent_default();
|
event.prevent_default();
|
||||||
}
|
}
|
||||||
|
@ -234,13 +208,8 @@ fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
|
||||||
ul(class="regulators") {
|
ul(class="regulators") {
|
||||||
Keyed(
|
Keyed(
|
||||||
list=regulator_list,
|
list=regulator_list,
|
||||||
view=move |reg_key| view! {
|
view=move |reg| reg.outline_item(&element),
|
||||||
RegulatorOutlineItem(
|
key=|reg| reg.serial()
|
||||||
regulator_key=reg_key,
|
|
||||||
element_key=key
|
|
||||||
)
|
|
||||||
},
|
|
||||||
key=|reg_key| reg_key.clone()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,12 +228,15 @@ pub fn Outline() -> View {
|
||||||
let state = use_context::<AppState>();
|
let state = use_context::<AppState>();
|
||||||
|
|
||||||
// list the elements alphabetically by ID
|
// list the elements alphabetically by ID
|
||||||
|
/* TO DO */
|
||||||
|
// this code is designed to generalize easily to other sort keys. if we only
|
||||||
|
// ever wanted to sort by ID, we could do that more simply using the
|
||||||
|
// `elements_by_id` index
|
||||||
let element_list = state.assembly.elements.map(
|
let element_list = state.assembly.elements.map(
|
||||||
|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 +250,10 @@ pub fn Outline() -> View {
|
||||||
) {
|
) {
|
||||||
Keyed(
|
Keyed(
|
||||||
list=element_list,
|
list=element_list,
|
||||||
view=|(key, ElementRc(elt))| view! {
|
view=|elt| view! {
|
||||||
ElementOutlineItem(key=key, element=elt)
|
ElementOutlineItem(element=elt)
|
||||||
},
|
},
|
||||||
key=|(_, ElementRc(elt))| elt.serial()
|
key=|elt| elt.serial()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue