Compare commits

...

6 Commits

Author SHA1 Message Date
Aaron Fenyes
a170492e3d Add engine conventions to inversive coordinates notes 2024-11-10 23:47:34 -08:00
Aaron Fenyes
b8ca1139d5 Explain what the Element::index field holds
Also, remind us to make the field private when that becomes possible.
2024-11-10 23:22:30 -08:00
Aaron Fenyes
ced001bbfe Alias the types of element and constraint keys
This will make it easier to change the key types if we change how we
store and access elements and constraints.
2024-11-10 22:55:58 -08:00
Aaron Fenyes
ed1890bffc Improve naming of constraint subjects 2024-11-10 19:36:40 -08:00
Aaron Fenyes
da008fd090 Write out representation in Element structure 2024-11-10 19:24:26 -08:00
Aaron Fenyes
933f05661d Only compile engine::point when it's used
This function will eventually be used in the application, but right now
it's only used in tests.
2024-11-10 16:31:29 -08:00
7 changed files with 73 additions and 42 deletions

View File

@ -11,7 +11,7 @@ fn load_gen_assemb(assembly: &Assembly) {
id: String::from("gemini_a"),
label: String::from("Castor"),
color: [1.00_f32, 0.25_f32, 0.00_f32],
rep: engine::sphere(0.5, 0.5, 0.0, 1.0),
representation: engine::sphere(0.5, 0.5, 0.0, 1.0),
constraints: BTreeSet::default(),
index: 0
}
@ -21,7 +21,7 @@ fn load_gen_assemb(assembly: &Assembly) {
id: String::from("gemini_b"),
label: String::from("Pollux"),
color: [0.00_f32, 0.25_f32, 1.00_f32],
rep: engine::sphere(-0.5, -0.5, 0.0, 1.0),
representation: engine::sphere(-0.5, -0.5, 0.0, 1.0),
constraints: BTreeSet::default(),
index: 0
}
@ -31,7 +31,7 @@ fn load_gen_assemb(assembly: &Assembly) {
id: String::from("ursa_major"),
label: String::from("Ursa major"),
color: [0.25_f32, 0.00_f32, 1.00_f32],
rep: engine::sphere(-0.5, 0.5, 0.0, 0.75),
representation: engine::sphere(-0.5, 0.5, 0.0, 0.75),
constraints: BTreeSet::default(),
index: 0
}
@ -41,7 +41,7 @@ fn load_gen_assemb(assembly: &Assembly) {
id: String::from("ursa_minor"),
label: String::from("Ursa minor"),
color: [0.25_f32, 1.00_f32, 0.00_f32],
rep: engine::sphere(0.5, -0.5, 0.0, 0.5),
representation: engine::sphere(0.5, -0.5, 0.0, 0.5),
constraints: BTreeSet::default(),
index: 0
}
@ -51,7 +51,7 @@ fn load_gen_assemb(assembly: &Assembly) {
id: String::from("moon_deimos"),
label: String::from("Deimos"),
color: [0.75_f32, 0.75_f32, 0.00_f32],
rep: engine::sphere(0.0, 0.15, 1.0, 0.25),
representation: engine::sphere(0.0, 0.15, 1.0, 0.25),
constraints: BTreeSet::default(),
index: 0
}
@ -61,7 +61,7 @@ fn load_gen_assemb(assembly: &Assembly) {
id: String::from("moon_phobos"),
label: String::from("Phobos"),
color: [0.00_f32, 0.75_f32, 0.50_f32],
rep: engine::sphere(0.0, -0.15, -1.0, 0.25),
representation: engine::sphere(0.0, -0.15, -1.0, 0.25),
constraints: BTreeSet::default(),
index: 0
}
@ -76,7 +76,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: "central".to_string(),
label: "Central".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(0.0, 0.0, 0.0, 1.0),
representation: engine::sphere(0.0, 0.0, 0.0, 1.0),
constraints: BTreeSet::default(),
index: 0
}
@ -86,7 +86,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: "assemb_plane".to_string(),
label: "Assembly plane".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
representation: engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0),
constraints: BTreeSet::default(),
index: 0
}
@ -96,7 +96,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: "side1".to_string(),
label: "Side 1".to_string(),
color: [1.00_f32, 0.00_f32, 0.25_f32],
rep: engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
representation: engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0),
constraints: BTreeSet::default(),
index: 0
}
@ -106,7 +106,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: "side2".to_string(),
label: "Side 2".to_string(),
color: [0.25_f32, 1.00_f32, 0.00_f32],
rep: engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0),
representation: engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0),
constraints: BTreeSet::default(),
index: 0
}
@ -116,7 +116,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: "side3".to_string(),
label: "Side 3".to_string(),
color: [0.00_f32, 0.25_f32, 1.00_f32],
rep: engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0),
representation: engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0),
constraints: BTreeSet::default(),
index: 0
}
@ -126,7 +126,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: "corner1".to_string(),
label: "Corner 1".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
representation: engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0),
constraints: BTreeSet::default(),
index: 0
}
@ -136,7 +136,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: "corner2".to_string(),
label: "Corner 2".to_string(),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
representation: engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
constraints: BTreeSet::default(),
index: 0
}
@ -146,7 +146,7 @@ fn load_low_curv_assemb(assembly: &Assembly) {
id: String::from("corner3"),
label: String::from("Corner 3"),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
representation: engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
constraints: BTreeSet::default(),
index: 0
}
@ -208,16 +208,16 @@ pub fn AddRemove() -> View {
},
on:click=|_| {
let state = use_context::<AppState>();
let args = state.selection.with(
let subjects = state.selection.with(
|sel| {
let arg_vec: Vec<_> = sel.into_iter().collect();
(arg_vec[0].clone(), arg_vec[1].clone())
let subject_vec: Vec<_> = sel.into_iter().collect();
(subject_vec[0].clone(), subject_vec[1].clone())
}
);
let rep = create_signal(0.0);
let active = create_signal(true);
state.assembly.insert_constraint(Constraint {
args: args,
subjects: subjects,
rep: rep,
rep_text: create_signal(String::new()),
rep_valid: create_signal(false),
@ -233,8 +233,8 @@ pub fn AddRemove() -> View {
for (_, cst) in csts.into_iter() {
console::log_5(
&JsValue::from(" "),
&JsValue::from(cst.args.0),
&JsValue::from(cst.args.1),
&JsValue::from(cst.subjects.0),
&JsValue::from(cst.subjects.1),
&JsValue::from(":"),
&JsValue::from(cst.rep.get_untracked())
);

View File

@ -7,21 +7,29 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
use crate::engine::{realize_gram, PartialMatrix};
// the types of the keys we use to access an assembly's elements and constraints
pub type ElementKey = usize;
pub type ConstraintKey = usize;
#[derive(Clone, PartialEq)]
pub struct Element {
pub id: String,
pub label: String,
pub color: [f32; 3],
pub rep: DVector<f64>,
pub constraints: BTreeSet<usize>,
pub representation: DVector<f64>,
pub constraints: BTreeSet<ConstraintKey>,
// internal properties, not reflected in any view
// the configuration matrix column index that was assigned to this element
// last time the assembly was realized
/* TO DO */
// this is public, as a kludge, because `Element` doesn't have a constructor
// yet. it should be made private as soon as the constructor is written
pub index: usize
}
#[derive(Clone)]
pub struct Constraint {
pub args: (usize, usize),
pub subjects: (ElementKey, ElementKey),
pub rep: Signal<f64>,
pub rep_text: Signal<String>,
pub rep_valid: Signal<bool>,
@ -36,7 +44,7 @@ pub struct Assembly {
pub constraints: Signal<Slab<Constraint>>,
// indexing
pub elements_by_id: Signal<FxHashMap<String, usize>>
pub elements_by_id: Signal<FxHashMap<String, ElementKey>>
}
impl Assembly {
@ -86,7 +94,7 @@ impl Assembly {
id: id,
label: format!("Sphere {}", id_num),
color: [0.75_f32, 0.75_f32, 0.75_f32],
rep: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]),
representation: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.5, -0.5]),
constraints: BTreeSet::default(),
index: 0
}
@ -94,11 +102,11 @@ impl Assembly {
}
pub fn insert_constraint(&self, constraint: Constraint) {
let args = constraint.args;
let subjects = constraint.subjects;
let key = self.constraints.update(|csts| csts.insert(constraint));
self.elements.update(|elts| {
elts[args.0].constraints.insert(key);
elts[args.1].constraints.insert(key);
elts[subjects.0].constraints.insert(key);
elts[subjects.1].constraints.insert(key);
});
}
@ -119,9 +127,9 @@ impl Assembly {
self.constraints.with_untracked(|csts| {
for (_, cst) in csts {
if cst.active.get_untracked() && cst.rep_valid.get_untracked() {
let args = cst.args;
let row = elts[args.0].index;
let col = elts[args.1].index;
let subjects = cst.subjects;
let row = elts[subjects.0].index;
let col = elts[subjects.1].index;
gram_to_be.push_sym(row, col, cst.rep.get_untracked());
}
}
@ -133,7 +141,7 @@ impl Assembly {
for (_, elt) in elts {
let index = elt.index;
gram_to_be.push_sym(index, index, 1.0);
guess_to_be.set_column(index, &elt.rep);
guess_to_be.set_column(index, &elt.representation);
}
(gram_to_be, guess_to_be)
@ -177,7 +185,7 @@ impl Assembly {
// read out the solution
self.elements.update(|elts| {
for (_, elt) in elts.iter_mut() {
elt.rep.set_column(0, &config.column(elt.index));
elt.representation.set_column(0, &config.column(elt.index));
}
});
}

View File

@ -297,7 +297,9 @@ pub fn Display() -> View {
// get the assembly
let elements = state.assembly.elements.get_clone();
let element_iter = (&elements).into_iter();
let reps_world: Vec<_> = element_iter.clone().map(|(_, elt)| &assembly_to_world * &elt.rep).collect();
let reps_world: Vec<_> = element_iter.clone().map(
|(_, elt)| &assembly_to_world * &elt.representation
).collect();
let colors: Vec<_> = element_iter.clone().map(|(key, elt)|
if state.selection.with(|sel| sel.contains(&key)) {
elt.color.map(|ch| 0.2 + 0.8*ch)

View File

@ -4,6 +4,7 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
// --- elements ---
#[cfg(test)]
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)])
}

View File

@ -8,14 +8,14 @@ use rustc_hash::FxHashSet;
use sycamore::prelude::*;
use add_remove::AddRemove;
use assembly::Assembly;
use assembly::{Assembly, ElementKey};
use display::Display;
use outline::Outline;
#[derive(Clone)]
struct AppState {
assembly: Assembly,
selection: Signal<FxHashSet<usize>>
selection: Signal<FxHashSet<ElementKey>>
}
impl AppState {

View File

@ -72,7 +72,7 @@ pub fn Outline() -> View {
}
});
let label = elt.label.clone();
let rep_components = elt.rep.iter().map(|u| {
let rep_components = elt.representation.iter().map(|u| {
let u_coord = u.to_string().replace("-", "\u{2212}");
View::from(div().children(u_coord))
}).collect::<Vec<_>>();
@ -150,14 +150,14 @@ pub fn Outline() -> View {
ul(class="constraints") {
Keyed(
list=elt.constraints.into_iter().collect::<Vec<_>>(),
view=move |c_key: usize| {
view=move |c_key| {
let c_state = use_context::<AppState>();
let assembly = &c_state.assembly;
let cst = assembly.constraints.with(|csts| csts[c_key].clone());
let other_arg = if cst.args.0 == key {
cst.args.1
let other_arg = if cst.subjects.0 == key {
cst.subjects.1
} else {
cst.args.0
cst.subjects.0
};
let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
view! {

View File

@ -41,3 +41,23 @@ I will have to work out formulas for the Euclidean distance between two entities
In this vein, it seems as though if J1 and J2 are the reps of two points, then Q(J1,J2) = d^2/2. So then the sphere centered at J1 through J2 is (J1-(2Q(J1,J2),0,0,0,0))/sqrt(2Q(J1,J2)). Ugh has a sqrt in it. Similarly for sphere centered at J3 through J2, (J3-(2Q(J3,J2),0000))/sqrt(2Q(J3,J2)). J1,J2,J3 are collinear if these spheres are tangent, i.e. if those vectors have Q-inner-product 1, which is to say Q(J1,J3) - Q(J1,J2) - Q(J3,J2) = 2sqrt(Q(J1,J2)Q(J2,J3)). But maybe that's not the simplest way of putting it. After all, we can just say that the cross-product of the two differences is 0; that has no square roots in it.
One conceivable way to canonicalize lines is to use the *perpendicular* plane that goes through the origin, that's uniquely defined, and anyway just amounts to I = (0,0,d) where d is the ordinary direction vector of the line; and a point J in that plane that the line goes through, which just amounts to J=(r^2,1,E) with Q(I,J) = 0, i.e. E\dot d = 0. It's also the point on the line closest to the origin. The reason that we don't usually use that point as the companion to the direction vector is that the resulting set of six coordinates is not homogeneous. But here that's not an issue, since we have our standard point coordinates and plane coordinates; and for a plane through the origin, only two of the direction coordinates are really free, and then we have the one dot-product relation, so only two of the point coordinates are really free, giving us the correct dimensionality of 4 for the set of lines. So in some sense this says that we could take naively as coordinates for a line the projection of the unit direction vector to the xy plane and the projection of the line's closest point to the origin to the xy plane. That doesn't seem to have any weird gimbal locks or discontinuities or anything. And with these coordinates, you can test if the point E=x,y,z is on the line (dx,dy,cx,cy) by extending (dx,dy) to d via dz = sqrt(1-dx^2 - dy^2), extending (cx,cy) to c by determining cz via d\dot c = 0, and then checking if d\cross(E-c) = 0. And you can see if two lines are parallel just by checking if they have the same direction vector, and if not, you can see if they are coplanar by projecting both of their closest points perpendicularly onto the line in the direction of the cross product of their directions, and if the projections match they are coplanar.
#### Engine Conventions
The coordinate conventions used in the engine are different from the ones used in these notes. Marking the engine vectors and coordinates with $'$, we have
$$I' = (x', y', z', b', c'),$$
where
$$\begin{align*}
x' & = x & b' & = b/2 \\
y' & = y & c' & = c/2. \\
z' & = z
\end{align*}$$
The engine uses the quadratic form $Q' = -Q$, which is expressed in engine coordinates as
$$Q'(I'_1, I'_2) = x'_1 x'_2 + y'_1 y'_2 + z'_1 z'_2 - 2(b'_1c'_2 + c'_1 b'_2).$$
In the `engine` module, the matrix of $Q'$ is encoded in the lazy static variable `Q`.
In the engine's coordinate conventions, a sphere with radius $r > 0$ centered on $P = (P_x, P_y, P_z)$ is represented by the vector
$$I'_s = \left(\frac{P_x}{r}, \frac{P_y}{r}, \frac{P_z}{r}, \frac1{2r}, \frac{\|P\|^2 - r^2}{2r}\right),$$
which has the normalization $Q'(I'_s, I'_s) = 1$. The point $P$ is represented by the vector
$$I'_P = \left(P_x, P_y, P_z, \frac{1}{2}, \frac{\|P\|^2}{2}\right).$$
In the `engine` module, these formulas are encoded in the `sphere` and `point` functions.