Compare commits

..

24 Commits

Author SHA1 Message Date
Aaron Fenyes
5332fda6e4 Move new test to avoid merge conflict 2024-11-11 15:41:42 -08:00
Aaron Fenyes
22a93bee28 Confirm that frozen entries are frozen exactly 2024-11-11 15:34:51 -08:00
Aaron Fenyes
5839882ed7 Name constraint Lorentz product more descriptively 2024-11-11 00:27:28 -08:00
Aaron Fenyes
a4ec52a4e7 Alias the type of an element's color 2024-11-11 00:04:48 -08:00
abb9d35335 Correct align environment in notes 2024-11-11 07:56:27 +00:00
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
Aaron Fenyes
9c191ae586 Polish log messages 2024-10-30 00:27:16 -07:00
Aaron Fenyes
9e31037e17 Spread web-sys imports over multiple lines 2024-10-30 00:19:44 -07:00
Aaron Fenyes
c2e3c64d4a Remove debug log from Lorentz product input 2024-10-30 00:16:34 -07:00
Aaron Fenyes
76ad4245d5 Factor out Lorentz product input 2024-10-29 23:43:41 -07:00
Aaron Fenyes
a46ef2c8d6 Work around data binding bug in number input
Setting `bind:value` or `bind:valueAsNumber` for a number input seems to
restrict what you can type in it. We work around this by switching to
text inputs for now. We should probably switch back to number inputs if
we can, though, because they let us take advantage of the browser's
parsing and validation.
2024-10-29 22:53:48 -07:00
Aaron Fenyes
e0880d2ad2 Make constraints editable 2024-10-29 22:32:00 -07:00
Aaron Fenyes
e5f4d523f9 Update the realization when a constraint is activated
Sycamore probably has a better way to do this, but this way works for
now.
2024-10-29 13:46:15 -07:00
Aaron Fenyes
a37c71153d Enforce constraints in the editor 2024-10-26 23:51:27 -07:00
Aaron Fenyes
ce33bbf418 Record optimization history 2024-10-26 01:07:17 -07:00
Aaron Fenyes
9f8632efb3 Port the Irisawa hexlet test to Rust
In the process, notice that the tolerance scale adjustment was ported
wrong, and correct it.
2024-10-25 21:43:53 -07:00
Aaron Fenyes
9fe03264ab Port the Gram matrix realization routine to Rust
Validate with the process inspection example tests, which print out
their results and optimization histories when run one at a time in
`--nocapture` mode.
2024-10-25 17:34:29 -07:00
Aaron Fenyes
e59d60bf77 Reorganize search state; remove unused variables 2024-10-25 17:17:49 -07:00
Aaron Fenyes
16df161fe7 Test alternate projection technique 2024-10-24 19:51:10 -07:00
7 changed files with 328 additions and 404 deletions

View File

@ -1,7 +1,7 @@
[package] [package]
name = "dyna3" name = "sketch-outline"
version = "0.1.0" version = "0.1.0"
authors = ["Aaron Fenyes", "Glen Whitney"] authors = ["Aaron"]
edition = "2021" edition = "2021"
[features] [features]

View File

@ -2,10 +2,8 @@
<html> <html>
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<title>dyna3</title> <title>Sketch outline</title>
<link data-trunk rel="css" href="main.css"/> <link data-trunk rel="css" href="main.css"/>
<link href="https://fonts.bunny.net/css?family=fira-sans:ital,wght@0,400;1,400&display=swap" rel="stylesheet">
<link href="https://fonts.bunny.net/css?family=noto-emoji:wght@400&text=%f0%9f%94%97%e2%9a%a0&display=swap" rel="stylesheet">
</head> </head>
<body></body> <body></body>
</html> </html>

View File

@ -1,20 +1,7 @@
:root {
--text: #fcfcfc; /* almost white */
--text-bright: white;
--text-invalid: #f58fc2; /* bright pink */
--border: #555; /* light gray */
--border-focus: #aaa; /* bright gray */
--border-invalid: #70495c; /* dusky pink */
--selection-highlight: #444; /* medium gray */
--page-background: #222; /* dark gray */
--display-background: #020202; /* almost black */
}
body { body {
margin: 0px; margin: 0px;
color: var(--text); color: #fcfcfc;
background-color: var(--page-background); background-color: #222;
font-family: 'Fira Sans', sans-serif;
} }
/* sidebar */ /* sidebar */
@ -29,7 +16,7 @@ body {
padding: 0px; padding: 0px;
border-width: 0px 1px 0px 0px; border-width: 0px 1px 0px 0px;
border-style: solid; border-style: solid;
border-color: var(--border); border-color: #555;
} }
/* add-remove */ /* add-remove */
@ -46,15 +33,6 @@ body {
font-size: large; font-size: large;
} }
/* KLUDGE */
/*
for convenience, we're using emoji as temporary icons for some buttons. these
buttons need to be displayed in an emoji font
*/
#add-remove > button.emoji {
font-family: 'Noto Emoji', sans-serif;
}
/* outline */ /* outline */
#outline { #outline {
@ -73,103 +51,81 @@ summary {
} }
summary.selected { summary.selected {
color: var(--text-bright); color: #fff;
background-color: var(--selection-highlight); background-color: #444;
} }
summary > div, .constraint { summary > div, .cst {
padding-top: 4px; padding-top: 4px;
padding-bottom: 4px; padding-bottom: 4px;
} }
.element, .constraint { .elt, .cst {
display: flex; display: flex;
flex-grow: 1; flex-grow: 1;
padding-left: 8px; padding-left: 8px;
padding-right: 8px; padding-right: 8px;
} }
.element-switch { .elt-switch {
width: 18px; width: 18px;
padding-left: 2px; padding-left: 2px;
text-align: center; text-align: center;
} }
details:has(li) .element-switch::after { details:has(li) .elt-switch::after {
content: '▸'; content: '▸';
} }
details[open]:has(li) .element-switch::after { details[open]:has(li) .elt-switch::after {
content: '▾'; content: '▾';
} }
.element-label { .elt-label {
flex-grow: 1; flex-grow: 1;
} }
.constraint-label { .cst-label {
flex-grow: 1; flex-grow: 1;
} }
.element-representation { .elt-rep {
display: flex; display: flex;
} }
.element-representation > div { .elt-rep > div {
padding: 2px 0px 0px 0px; padding: 2px 0px 0px 0px;
font-size: 10pt; font-size: 10pt;
font-variant-numeric: tabular-nums; text-align: center;
text-align: right;
width: 56px; width: 56px;
} }
.constraint { .cst {
font-style: italic; font-style: italic;
} }
.constraint.invalid { .cst > input[type=checkbox] {
color: var(--text-invalid);
}
.constraint > input[type=checkbox] {
margin: 0px 8px 0px 0px; margin: 0px 8px 0px 0px;
} }
.constraint > input[type=text] { .cst > input[type=text] {
color: inherit; color: #fcfcfc;
background-color: inherit; background-color: inherit;
border: 1px solid var(--border); border: 1px solid #555;
border-radius: 2px; border-radius: 2px;
} }
.constraint.invalid > input[type=text] {
border-color: var(--border-invalid);
}
.status {
width: 20px;
padding-left: 4px;
text-align: center;
font-family: 'Noto Emoji';
font-style: normal;
}
.invalid > .status::after, details:has(.invalid):not([open]) .status::after {
content: '⚠';
color: var(--text-invalid);
}
/* display */ /* display */
canvas { canvas {
float: left; float: left;
margin-left: 20px; margin-left: 20px;
margin-top: 20px; margin-top: 20px;
background-color: var(--display-background); background-color: #020202;
border: 1px solid var(--border); border: 1px solid #555;
border-radius: 16px; border-radius: 16px;
} }
canvas:focus { canvas:focus {
border-color: var(--border-focus); border-color: #aaa;
} }

View File

@ -1,130 +1,155 @@
use std::collections::BTreeSet; /* DEBUG */
use sycamore::prelude::*; use sycamore::prelude::*;
use web_sys::{console, wasm_bindgen::JsValue}; use web_sys::{console, wasm_bindgen::JsValue};
use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}}; use crate::{engine, AppState, assembly::{Assembly, Constraint, Element}};
/* DEBUG */ /* DEBUG */
// load an example assembly for testing. this code will be removed once we've
// built a more formal test assembly system
fn load_gen_assemb(assembly: &Assembly) { fn load_gen_assemb(assembly: &Assembly) {
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
String::from("gemini_a"), id: String::from("gemini_a"),
String::from("Castor"), label: String::from("Castor"),
[1.00_f32, 0.25_f32, 0.00_f32], color: [1.00_f32, 0.25_f32, 0.00_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
String::from("gemini_b"), id: String::from("gemini_b"),
String::from("Pollux"), label: String::from("Pollux"),
[0.00_f32, 0.25_f32, 1.00_f32], color: [0.00_f32, 0.25_f32, 1.00_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
String::from("ursa_major"), id: String::from("ursa_major"),
String::from("Ursa major"), label: String::from("Ursa major"),
[0.25_f32, 0.00_f32, 1.00_f32], color: [0.25_f32, 0.00_f32, 1.00_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
String::from("ursa_minor"), id: String::from("ursa_minor"),
String::from("Ursa minor"), label: String::from("Ursa minor"),
[0.25_f32, 1.00_f32, 0.00_f32], color: [0.25_f32, 1.00_f32, 0.00_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
String::from("moon_deimos"), id: String::from("moon_deimos"),
String::from("Deimos"), label: String::from("Deimos"),
[0.75_f32, 0.75_f32, 0.00_f32], color: [0.75_f32, 0.75_f32, 0.00_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
String::from("moon_phobos"), id: String::from("moon_phobos"),
String::from("Phobos"), label: String::from("Phobos"),
[0.00_f32, 0.75_f32, 0.50_f32], color: [0.00_f32, 0.75_f32, 0.50_f32],
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
}
); );
} }
/* DEBUG */ /* DEBUG */
// load an example assembly for testing. this code will be removed once we've
// built a more formal test assembly system
fn load_low_curv_assemb(assembly: &Assembly) { fn load_low_curv_assemb(assembly: &Assembly) {
let a = 0.75_f64.sqrt(); let a = 0.75_f64.sqrt();
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
"central".to_string(), id: "central".to_string(),
"Central".to_string(), label: "Central".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], color: [0.75_f32, 0.75_f32, 0.75_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
"assemb_plane".to_string(), id: "assemb_plane".to_string(),
"Assembly plane".to_string(), label: "Assembly plane".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], color: [0.75_f32, 0.75_f32, 0.75_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
"side1".to_string(), id: "side1".to_string(),
"Side 1".to_string(), label: "Side 1".to_string(),
[1.00_f32, 0.00_f32, 0.25_f32], color: [1.00_f32, 0.00_f32, 0.25_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
"side2".to_string(), id: "side2".to_string(),
"Side 2".to_string(), label: "Side 2".to_string(),
[0.25_f32, 1.00_f32, 0.00_f32], color: [0.25_f32, 1.00_f32, 0.00_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
"side3".to_string(), id: "side3".to_string(),
"Side 3".to_string(), label: "Side 3".to_string(),
[0.00_f32, 0.25_f32, 1.00_f32], color: [0.00_f32, 0.25_f32, 1.00_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
"corner1".to_string(), id: "corner1".to_string(),
"Corner 1".to_string(), label: "Corner 1".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], color: [0.75_f32, 0.75_f32, 0.75_f32],
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
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
"corner2".to_string(), id: "corner2".to_string(),
"Corner 2".to_string(), label: "Corner 2".to_string(),
[0.75_f32, 0.75_f32, 0.75_f32], color: [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0) representation: engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0),
) constraints: BTreeSet::default(),
index: 0
}
); );
let _ = assembly.try_insert_element( let _ = assembly.try_insert_element(
Element::new( Element {
String::from("corner3"), id: String::from("corner3"),
String::from("Corner 3"), label: String::from("Corner 3"),
[0.75_f32, 0.75_f32, 0.75_f32], color: [0.75_f32, 0.75_f32, 0.75_f32],
engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0) representation: engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0),
) constraints: BTreeSet::default(),
index: 0
}
); );
} }
@ -177,7 +202,6 @@ pub fn AddRemove() -> View {
} }
) { "+" } ) { "+" }
button( button(
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
disabled={ disabled={
let state = use_context::<AppState>(); let state = use_context::<AppState>();
state.selection.with(|sel| sel.len() != 2) state.selection.with(|sel| sel.len() != 2)
@ -191,15 +215,15 @@ pub fn AddRemove() -> View {
} }
); );
let lorentz_prod = create_signal(0.0); let lorentz_prod = create_signal(0.0);
let lorentz_prod_valid = create_signal(false);
let active = create_signal(true); let active = create_signal(true);
state.assembly.insert_constraint(Constraint { state.assembly.insert_constraint(Constraint {
subjects: subjects, subjects: subjects,
lorentz_prod: lorentz_prod, lorentz_prod: lorentz_prod,
lorentz_prod_text: create_signal(String::new()), lorentz_prod_text: create_signal(String::new()),
lorentz_prod_valid: lorentz_prod_valid, lorentz_prod_valid: create_signal(false),
active: active, active: active,
}); });
state.assembly.realize();
state.selection.update(|sel| sel.clear()); state.selection.update(|sel| sel.clear());
/* DEBUG */ /* DEBUG */
@ -217,23 +241,23 @@ pub fn AddRemove() -> View {
} }
}); });
// update the realization when the constraint becomes active // update the realization when the constraint activated, or
// and valid, or is edited while active and valid // edited while active
create_effect(move || { create_effect(move || {
console::log_1(&JsValue::from(
format!("Constraint ({}, {}) updated", subjects.0, subjects.1)
));
lorentz_prod.track(); lorentz_prod.track();
if active.get() && lorentz_prod_valid.get() { console::log_2(
&JsValue::from("Lorentz product updated to"),
&JsValue::from(lorentz_prod.get_untracked())
);
if active.get() {
state.assembly.realize(); state.assembly.realize();
} }
}); });
} }
) { "🔗" } ) { "🔗" }
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser select(bind:value=assembly_name) { /* DEBUG */
option(value="general") { "General" } option(value="general") { "General" }
option(value="low-curv") { "Low-curvature" } option(value="low-curv") { "Low-curvature" }
option(value="empty") { "Empty" }
} }
} }
} }

View File

@ -18,33 +18,17 @@ pub struct Element {
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: DVector<f64>,
pub constraints: Signal<BTreeSet<ConstraintKey>>, pub constraints: BTreeSet<ConstraintKey>,
// the configuration matrix column index that was assigned to this element // the configuration matrix column index that was assigned to this element
// last time the assembly was realized // last time the assembly was realized
column_index: usize /* 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
} }
impl Element {
pub fn new(
id: String,
label: String,
color: ElementColor,
representation: DVector<f64>
) -> Element {
Element {
id: id,
label: label,
color: color,
representation: create_signal(representation),
constraints: create_signal(BTreeSet::default()),
column_index: 0
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct Constraint { pub struct Constraint {
pub subjects: (ElementKey, ElementKey), pub subjects: (ElementKey, ElementKey),
@ -108,23 +92,24 @@ impl Assembly {
// create and insert a new element // create and insert a new element
self.insert_element_unchecked( self.insert_element_unchecked(
Element::new( Element {
id, id: id,
format!("Sphere {}", id_num), label: format!("Sphere {}", id_num),
[0.75_f32, 0.75_f32, 0.75_f32], color: [0.75_f32, 0.75_f32, 0.75_f32],
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
}
); );
} }
pub fn insert_constraint(&self, constraint: Constraint) { pub fn insert_constraint(&self, constraint: Constraint) {
let subjects = constraint.subjects; let subjects = constraint.subjects;
let key = self.constraints.update(|csts| csts.insert(constraint)); let key = self.constraints.update(|csts| csts.insert(constraint));
let subject_constraints = self.elements.with( self.elements.update(|elts| {
|elts| (elts[subjects.0].constraints, elts[subjects.1].constraints) elts[subjects.0].constraints.insert(key);
); elts[subjects.1].constraints.insert(key);
subject_constraints.0.update(|csts| csts.insert(key)); });
subject_constraints.1.update(|csts| csts.insert(key));
} }
// --- realization --- // --- realization ---
@ -133,7 +118,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 = index; elt.index = index;
} }
}); });
@ -145,8 +130,8 @@ impl Assembly {
for (_, cst) in csts { for (_, cst) in csts {
if cst.active.get_untracked() && cst.lorentz_prod_valid.get_untracked() { if cst.active.get_untracked() && cst.lorentz_prod_valid.get_untracked() {
let subjects = cst.subjects; let subjects = cst.subjects;
let row = elts[subjects.0].column_index; let row = elts[subjects.0].index;
let col = elts[subjects.1].column_index; let col = elts[subjects.1].index;
gram_to_be.push_sym(row, col, cst.lorentz_prod.get_untracked()); gram_to_be.push_sym(row, col, cst.lorentz_prod.get_untracked());
} }
} }
@ -156,9 +141,9 @@ impl Assembly {
// Gram matrix // Gram matrix
let mut guess_to_be = DMatrix::<f64>::zeros(5, elts.len()); let mut guess_to_be = DMatrix::<f64>::zeros(5, elts.len());
for (_, elt) in elts { for (_, elt) in elts {
let index = elt.column_index; let index = elt.index;
gram_to_be.push_sym(index, index, 1.0); gram_to_be.push_sym(index, index, 1.0);
guess_to_be.set_column(index, &elt.representation.get_clone_untracked()); guess_to_be.set_column(index, &elt.representation);
} }
(gram_to_be, guess_to_be) (gram_to_be, guess_to_be)
@ -200,11 +185,11 @@ impl Assembly {
if success { if success {
// read out the solution // read out the solution
for (_, elt) in self.elements.get_clone_untracked() { self.elements.update(|elts| {
elt.representation.update( for (_, elt) in elts.iter_mut() {
|rep| rep.set_column(0, &config.column(elt.column_index)) elt.representation.set_column(0, &config.column(elt.index));
); }
} });
} }
} }
} }

View File

@ -103,11 +103,7 @@ pub fn Display() -> View {
// change listener // change listener
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.track();
for (_, elt) in elts {
elt.representation.track();
}
});
state.selection.track(); state.selection.track();
scene_changed.set(true); scene_changed.set(true);
}); });
@ -299,40 +295,25 @@ pub fn Display() -> View {
let assembly_to_world = &location * &orientation; let assembly_to_world = &location * &orientation;
// get the assembly // get the assembly
let ( let elements = state.assembly.elements.get_clone();
elt_cnt, let element_iter = (&elements).into_iter();
reps_world, let reps_world: Vec<_> = element_iter.clone().map(
colors, |(_, elt)| &assembly_to_world * &elt.representation
highlights ).collect();
) = state.assembly.elements.with(|elts| { let colors: Vec<_> = element_iter.clone().map(|(key, elt)|
(
// number of elements
elts.len() as i32,
// representation vectors in world coordinates
elts.iter().map(
|(_, elt)| elt.representation.with(|rep| &assembly_to_world * rep)
).collect::<Vec<_>>(),
// colors
elts.iter().map(|(key, elt)| {
if state.selection.with(|sel| sel.contains(&key)) { if state.selection.with(|sel| sel.contains(&key)) {
elt.color.map(|ch| 0.2 + 0.8*ch) elt.color.map(|ch| 0.2 + 0.8*ch)
} else { } else {
elt.color elt.color
} }
}).collect::<Vec<_>>(), ).collect();
let highlights: Vec<_> = element_iter.map(|(key, _)|
// highlight levels
elts.iter().map(|(key, _)| {
if state.selection.with(|sel| sel.contains(&key)) { if state.selection.with(|sel| sel.contains(&key)) {
1.0_f32 1.0_f32
} else { } else {
HIGHLIGHT HIGHLIGHT
} }
}).collect::<Vec<_>>() ).collect();
)
});
// set the resolution // set the resolution
let width = canvas.width() as f32; let width = canvas.width() as f32;
@ -341,7 +322,7 @@ pub fn Display() -> View {
ctx.uniform1f(shortdim_loc.as_ref(), width.min(height)); ctx.uniform1f(shortdim_loc.as_ref(), width.min(height));
// pass the assembly // pass the assembly
ctx.uniform1i(sphere_cnt_loc.as_ref(), elt_cnt); ctx.uniform1i(sphere_cnt_loc.as_ref(), elements.len() as i32);
for n in 0..reps_world.len() { for n in 0..reps_world.len() {
let v = &reps_world[n]; let v = &reps_world[n];
ctx.uniform3f( ctx.uniform3f(

View File

@ -1,6 +1,7 @@
use itertools::Itertools; use itertools::Itertools;
use sycamore::prelude::*; use sycamore::{prelude::*, web::tags::div};
use web_sys::{ use web_sys::{
Element,
Event, Event,
HtmlInputElement, HtmlInputElement,
KeyboardEvent, KeyboardEvent,
@ -8,7 +9,7 @@ use web_sys::{
wasm_bindgen::JsCast wasm_bindgen::JsCast
}; };
use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}}; use crate::{AppState, assembly::Constraint};
// an editable view of the Lorentz product representing a constraint // an editable view of the Lorentz product representing a constraint
#[component(inline_props)] #[component(inline_props)]
@ -31,48 +32,51 @@ fn LorentzProductInput(constraint: Constraint) -> View {
} }
} }
// a list item that shows a constraint in an outline view of an element // a component that lists the elements of the current assembly, showing the
#[component(inline_props)] // constraints on each element as a collapsible sub-list. its implementation
fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View { // is based on Kate Morley's HTML + CSS tree views:
//
// https://iamkate.com/code/tree-views/
//
#[component]
pub fn Outline() -> View {
// sort the elements alphabetically by ID
let elements_sorted = create_memo(|| {
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let assembly = &state.assembly; state.assembly.elements
let constraint = assembly.constraints.with(|csts| csts[constraint_key].clone()); .get_clone()
let other_subject = if constraint.subjects.0 == element_key { .into_iter()
constraint.subjects.1 .sorted_by_key(|(_, elt)| elt.id.clone())
} else { .collect()
constraint.subjects.0 });
};
let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone());
let class = constraint.lorentz_prod_valid.map(
|&lorentz_prod_valid| if lorentz_prod_valid { "constraint" } else { "constraint invalid" }
);
view! {
li(class=class.get()) {
input(r#type="checkbox", bind:checked=constraint.active)
div(class="constraint-label") { (other_subject_label) }
LorentzProductInput(constraint=constraint)
div(class="status")
}
}
}
// a list item that shows an element in an outline view of an assembly view! {
#[component(inline_props)] ul(
fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { id="outline",
on:click={
let state = use_context::<AppState>(); let state = use_context::<AppState>();
let class = state.selection.map( move |_| state.selection.update(|sel| sel.clear())
move |sel| if sel.contains(&key) { "selected" } else { "" } }
); ) {
let label = element.label.clone(); Keyed(
let rep_components = element.representation.map( list=elements_sorted,
|rep| rep.iter().map( view=|(key, elt)| {
|u| format!("{:.3}", u).replace("-", "\u{2212}") let state = use_context::<AppState>();
).collect() let class = create_memo({
); move || {
let constrained = element.constraints.map(|csts| csts.len() > 0); if state.selection.with(|sel| sel.contains(&key)) {
let constraint_list = element.constraints.map( "selected"
|csts| csts.clone().into_iter().collect() } else {
); ""
}
}
});
let label = elt.label.clone();
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<_>>();
let constrained = elt.constraints.len() > 0;
let details_node = create_node_ref(); let details_node = create_node_ref();
view! { view! {
li { li {
@ -97,16 +101,16 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
} }
event.prevent_default(); event.prevent_default();
}, },
"ArrowRight" if constrained.get() => { "ArrowRight" if constrained => {
let _ = details_node let _ = details_node
.get() .get()
.unchecked_into::<web_sys::Element>() .unchecked_into::<Element>()
.set_attribute("open", ""); .set_attribute("open", "");
}, },
"ArrowLeft" => { "ArrowLeft" => {
let _ = details_node let _ = details_node
.get() .get()
.unchecked_into::<web_sys::Element>() .unchecked_into::<Element>()
.remove_attribute("open"); .remove_attribute("open");
}, },
_ => () _ => ()
@ -115,11 +119,11 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
} }
) { ) {
div( div(
class="element-switch", class="elt-switch",
on:click=|event: MouseEvent| event.stop_propagation() on:click=|event: MouseEvent| event.stop_propagation()
) )
div( div(
class="element", class="elt",
on:click={ on:click={
move |event: MouseEvent| { move |event: MouseEvent| {
if event.shift_key() { if event.shift_key() {
@ -139,68 +143,44 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
} }
} }
) { ) {
div(class="element-label") { (label) } div(class="elt-label") { (label) }
div(class="element-representation") { div(class="elt-rep") { (rep_components) }
Indexed(
list=rep_components,
view=|coord_str| view! {
div { (coord_str) }
}
)
}
div(class="status")
} }
} }
ul(class="constraints") { ul(class="constraints") {
Keyed( Keyed(
list=constraint_list, list=elt.constraints.into_iter().collect::<Vec<_>>(),
view=move |cst_key| view! { view=move |c_key| {
ConstraintOutlineItem( let c_state = use_context::<AppState>();
constraint_key=cst_key, let assembly = &c_state.assembly;
element_key=key let cst = assembly.constraints.with(|csts| csts[c_key].clone());
) let other_arg = if cst.subjects.0 == key {
}, cst.subjects.1
key=|cst_key| cst_key.clone() } else {
) cst.subjects.0
} };
} let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
}
}
}
// a component that lists the elements of the current assembly, showing the
// constraints on each element as a collapsible sub-list. its implementation
// is based on Kate Morley's HTML + CSS tree views:
//
// https://iamkate.com/code/tree-views/
//
#[component]
pub fn Outline() -> View {
let state = use_context::<AppState>();
// list the elements alphabetically by ID
let element_list = state.assembly.elements.map(
|elts| elts
.clone()
.into_iter()
.sorted_by_key(|(_, elt)| elt.id.clone())
.collect()
);
view! { view! {
ul( li(class="cst") {
id="outline", input(r#type="checkbox", bind:checked=cst.active)
on:click={ div(class="cst-label") { (other_arg_label) }
let state = use_context::<AppState>(); LorentzProductInput(constraint=cst)
move |_| state.selection.update(|sel| sel.clear()) }
} }
) {
Keyed(
list=element_list,
view=|(key, elt)| view! {
ElementOutlineItem(key=key, element=elt)
}, },
key=|(key, _)| key.clone() key=|c_key| c_key.clone()
)
}
}
}
}
},
key=|(key, elt)| (
key.clone(),
elt.id.clone(),
elt.label.clone(),
elt.constraints.clone()
)
) )
} }
} }