Compare commits
5 commits
54b34e0582
...
f332f755e0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f332f755e0 | ||
![]() |
e19792d961 | ||
![]() |
0cfdd59e23 | ||
![]() |
f4e5c34fde | ||
a671a8273a |
9 changed files with 189 additions and 83 deletions
|
@ -46,6 +46,13 @@ features = [
|
||||||
dyna3 = { path = ".", default-features = false, features = ["dev"] }
|
dyna3 = { path = ".", default-features = false, features = ["dev"] }
|
||||||
wasm-bindgen-test = "0.3.34"
|
wasm-bindgen-test = "0.3.34"
|
||||||
|
|
||||||
|
# turn off spurious warnings about the custom config that Sycamore uses
|
||||||
|
#
|
||||||
|
# https://sycamore.dev/book/troubleshooting#unexpected-cfg-condition-name--sycamore-force-ssr
|
||||||
|
#
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(sycamore_force_ssr)"] }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s" # optimize for small code size
|
opt-level = "s" # optimize for small code size
|
||||||
debug = true # include debug symbols
|
debug = true # include debug symbols
|
||||||
|
|
|
@ -90,6 +90,10 @@ summary > div, .regulator {
|
||||||
padding-right: 8px;
|
padding-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.element > input {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.element-switch {
|
.element-switch {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use nalgebra::{DMatrix, DVector, DVectorView};
|
use nalgebra::{DMatrix, DVector, DVectorView};
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
collections::{BTreeMap, BTreeSet},
|
collections::{BTreeMap, BTreeSet},
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
|
@ -20,6 +19,8 @@ use crate::{
|
||||||
change_half_curvature,
|
change_half_curvature,
|
||||||
local_unif_to_std,
|
local_unif_to_std,
|
||||||
point,
|
point,
|
||||||
|
project_point_to_normalized,
|
||||||
|
project_sphere_to_normalized,
|
||||||
realize_gram,
|
realize_gram,
|
||||||
sphere,
|
sphere,
|
||||||
ConfigSubspace,
|
ConfigSubspace,
|
||||||
|
@ -101,11 +102,16 @@ pub trait Element: Serial + ProblemPoser + DisplayItem {
|
||||||
fn id(&self) -> &String;
|
fn id(&self) -> &String;
|
||||||
fn label(&self) -> &String;
|
fn label(&self) -> &String;
|
||||||
fn representation(&self) -> Signal<DVector<f64>>;
|
fn representation(&self) -> Signal<DVector<f64>>;
|
||||||
|
fn ghost(&self) -> Signal<bool>;
|
||||||
|
|
||||||
// the regulators the element is subject to. the assembly that owns the
|
// the regulators the element is subject to. the assembly that owns the
|
||||||
// element is responsible for keeping this set up to date
|
// element is responsible for keeping this set up to date
|
||||||
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>>;
|
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>>;
|
||||||
|
|
||||||
|
// project a representation vector for this kind of element onto its
|
||||||
|
// normalization variety
|
||||||
|
fn project_to_normalized(&self, rep: &mut DVector<f64>);
|
||||||
|
|
||||||
// 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
|
||||||
// been through a realization
|
// been through a realization
|
||||||
|
@ -154,6 +160,7 @@ 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 ghost: Signal<bool>,
|
||||||
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
||||||
serial: u64,
|
serial: u64,
|
||||||
column_index: Cell<Option<usize>>
|
column_index: Cell<Option<usize>>
|
||||||
|
@ -173,6 +180,7 @@ impl Sphere {
|
||||||
label: label,
|
label: label,
|
||||||
color: color,
|
color: color,
|
||||||
representation: create_signal(representation),
|
representation: create_signal(representation),
|
||||||
|
ghost: create_signal(false),
|
||||||
regulators: create_signal(BTreeSet::new()),
|
regulators: create_signal(BTreeSet::new()),
|
||||||
serial: Self::next_serial(),
|
serial: Self::next_serial(),
|
||||||
column_index: None.into()
|
column_index: None.into()
|
||||||
|
@ -210,10 +218,18 @@ impl Element for Sphere {
|
||||||
self.representation
|
self.representation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ghost(&self) -> Signal<bool> {
|
||||||
|
self.ghost
|
||||||
|
}
|
||||||
|
|
||||||
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
|
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
|
||||||
self.regulators
|
self.regulators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn project_to_normalized(&self, rep: &mut DVector<f64>) {
|
||||||
|
project_sphere_to_normalized(rep);
|
||||||
|
}
|
||||||
|
|
||||||
fn column_index(&self) -> Option<usize> {
|
fn column_index(&self) -> Option<usize> {
|
||||||
self.column_index.get()
|
self.column_index.get()
|
||||||
}
|
}
|
||||||
|
@ -244,6 +260,7 @@ 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 ghost: Signal<bool>,
|
||||||
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
pub regulators: Signal<BTreeSet<Rc<dyn Regulator>>>,
|
||||||
serial: u64,
|
serial: u64,
|
||||||
column_index: Cell<Option<usize>>
|
column_index: Cell<Option<usize>>
|
||||||
|
@ -263,6 +280,7 @@ impl Point {
|
||||||
label,
|
label,
|
||||||
color,
|
color,
|
||||||
representation: create_signal(representation),
|
representation: create_signal(representation),
|
||||||
|
ghost: create_signal(false),
|
||||||
regulators: create_signal(BTreeSet::new()),
|
regulators: create_signal(BTreeSet::new()),
|
||||||
serial: Self::next_serial(),
|
serial: Self::next_serial(),
|
||||||
column_index: None.into()
|
column_index: None.into()
|
||||||
|
@ -296,10 +314,18 @@ impl Element for Point {
|
||||||
self.representation
|
self.representation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ghost(&self) -> Signal<bool> {
|
||||||
|
self.ghost
|
||||||
|
}
|
||||||
|
|
||||||
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
|
fn regulators(&self) -> Signal<BTreeSet<Rc<dyn Regulator>>> {
|
||||||
self.regulators
|
self.regulators
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn project_to_normalized(&self, rep: &mut DVector<f64>) {
|
||||||
|
project_point_to_normalized(rep);
|
||||||
|
}
|
||||||
|
|
||||||
fn column_index(&self) -> Option<usize> {
|
fn column_index(&self) -> Option<usize> {
|
||||||
self.column_index.get()
|
self.column_index.get()
|
||||||
}
|
}
|
||||||
|
@ -598,9 +624,7 @@ impl Assembly {
|
||||||
create_effect(move || {
|
create_effect(move || {
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
// log the regulator update
|
// log the regulator update
|
||||||
console::log_1(&JsValue::from(
|
console_log!("Updated regulator with subjects {:?}", regulator.subjects());
|
||||||
format!("Updated regulator with subjects {:?}", regulator.subjects())
|
|
||||||
));
|
|
||||||
|
|
||||||
if regulator.try_activate() {
|
if regulator.try_activate() {
|
||||||
self_for_effect.realize();
|
self_for_effect.realize();
|
||||||
|
@ -609,10 +633,10 @@ impl Assembly {
|
||||||
|
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
// print an updated list of regulators
|
// print an updated list of regulators
|
||||||
console::log_1(&JsValue::from("Regulators:"));
|
console_log!("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!(
|
||||||
" {:?}: {}",
|
" {:?}: {}",
|
||||||
reg.subjects(),
|
reg.subjects(),
|
||||||
reg.set_point().with_untracked(
|
reg.set_point().with_untracked(
|
||||||
|
@ -625,7 +649,7 @@ impl Assembly {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)));
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -656,19 +680,11 @@ impl Assembly {
|
||||||
|
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
// log the Gram matrix
|
// log the Gram matrix
|
||||||
console::log_1(&JsValue::from("Gram matrix:"));
|
console_log!("Gram matrix:\n{}", problem.gram);
|
||||||
problem.gram.log_to_console();
|
|
||||||
|
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
// log the initial configuration matrix
|
// log the initial configuration matrix
|
||||||
console::log_1(&JsValue::from("Old configuration:"));
|
console_log!("Old configuration:{:>8.3}", problem.guess);
|
||||||
for j in 0..problem.guess.nrows() {
|
|
||||||
let mut row_str = String::new();
|
|
||||||
for k in 0..problem.guess.ncols() {
|
|
||||||
row_str.push_str(format!(" {:>8.3}", problem.guess[(j, k)]).as_str());
|
|
||||||
}
|
|
||||||
console::log_1(&JsValue::from(row_str));
|
|
||||||
}
|
|
||||||
|
|
||||||
// look for a configuration with the given Gram matrix
|
// look for a configuration with the given Gram matrix
|
||||||
let (config, tangent, success, history) = realize_gram(
|
let (config, tangent, success, history) = realize_gram(
|
||||||
|
@ -677,16 +693,14 @@ impl Assembly {
|
||||||
|
|
||||||
/* DEBUG */
|
/* DEBUG */
|
||||||
// report the outcome of the search
|
// report the outcome of the search
|
||||||
console::log_1(&JsValue::from(
|
if success {
|
||||||
if success {
|
console_log!("Target accuracy achieved!")
|
||||||
"Target accuracy achieved!"
|
} else {
|
||||||
} else {
|
console_log!("Failed to reach target accuracy")
|
||||||
"Failed to reach target accuracy"
|
}
|
||||||
}
|
console_log!("Steps: {}", history.scaled_loss.len() - 1);
|
||||||
));
|
console_log!("Loss: {}", *history.scaled_loss.last().unwrap());
|
||||||
console::log_2(&JsValue::from("Steps:"), &JsValue::from(history.scaled_loss.len() - 1));
|
console_log!("Tangent dimension: {}", tangent.dim());
|
||||||
console::log_2(&JsValue::from("Loss:"), &JsValue::from(*history.scaled_loss.last().unwrap()));
|
|
||||||
console::log_2(&JsValue::from("Tangent dimension:"), &JsValue::from(tangent.dim()));
|
|
||||||
|
|
||||||
if success {
|
if success {
|
||||||
// read out the solution
|
// read out the solution
|
||||||
|
@ -769,29 +783,17 @@ impl Assembly {
|
||||||
|
|
||||||
// step the assembly along the deformation. this changes the elements'
|
// step the assembly along the deformation. this changes the elements'
|
||||||
// normalizations, so we restore those afterward
|
// normalizations, so we restore those afterward
|
||||||
/* KLUDGE */
|
|
||||||
// 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) => {
|
||||||
// step the assembly along the deformation
|
// step the element along the deformation and then
|
||||||
|
// restore its normalization
|
||||||
*rep += motion_proj.column(column_index);
|
*rep += motion_proj.column(column_index);
|
||||||
|
elt.project_to_normalized(rep);
|
||||||
if elt.type_id() == TypeId::of::<Sphere>() {
|
|
||||||
// restore normalization by contracting toward the
|
|
||||||
// last coordinate axis
|
|
||||||
let q_sp = rep.fixed_rows::<3>(0).norm_squared();
|
|
||||||
let half_q_lt = -2.0 * rep[3] * rep[4];
|
|
||||||
let half_q_lt_sq = half_q_lt * half_q_lt;
|
|
||||||
let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt();
|
|
||||||
rep.fixed_rows_mut::<4>(0).scale_mut(1.0 / scaling);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
console::log_1(&JsValue::from(
|
console_log!("No velocity to unpack for fresh element \"{}\"", elt.id())
|
||||||
format!("No velocity to unpack for fresh element \"{}\"", elt.id())
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -808,6 +810,8 @@ impl Assembly {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use crate::engine;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")]
|
#[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")]
|
||||||
fn unindexed_element_test() {
|
fn unindexed_element_test() {
|
||||||
|
@ -833,4 +837,50 @@ mod tests {
|
||||||
}.pose(&mut ConstraintProblem::new(2));
|
}.pose(&mut ConstraintProblem::new(2));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn curvature_drift_test() {
|
||||||
|
const INITIAL_RADIUS: f64 = 0.25;
|
||||||
|
let _ = create_root(|| {
|
||||||
|
// set up an assembly containing a single sphere centered at the
|
||||||
|
// origin
|
||||||
|
let assembly = Assembly::new();
|
||||||
|
let sphere_id = "sphere0";
|
||||||
|
let _ = assembly.try_insert_element(
|
||||||
|
// we create the sphere by hand for two reasons: to choose the
|
||||||
|
// curvature (which can affect drift rate) and to make the test
|
||||||
|
// independent of `Sphere::default`
|
||||||
|
Sphere::new(
|
||||||
|
String::from(sphere_id),
|
||||||
|
String::from("Sphere 0"),
|
||||||
|
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
|
engine::sphere(0.0, 0.0, 0.0, INITIAL_RADIUS)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// nudge the sphere repeatedly along the `z` axis
|
||||||
|
const STEP_SIZE: f64 = 0.0025;
|
||||||
|
const STEP_CNT: usize = 400;
|
||||||
|
let sphere = assembly.elements_by_id.with(|elts_by_id| elts_by_id[sphere_id].clone());
|
||||||
|
let velocity = DVector::from_column_slice(&[0.0, 0.0, STEP_SIZE, 0.0]);
|
||||||
|
for _ in 0..STEP_CNT {
|
||||||
|
assembly.deform(
|
||||||
|
vec![
|
||||||
|
ElementMotion {
|
||||||
|
element: sphere.clone(),
|
||||||
|
velocity: velocity.as_view()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check how much the sphere's curvature has drifted
|
||||||
|
const INITIAL_HALF_CURV: f64 = 0.5 / INITIAL_RADIUS;
|
||||||
|
const DRIFT_TOL: f64 = 0.015;
|
||||||
|
let final_half_curv = sphere.representation().with_untracked(
|
||||||
|
|rep| rep[Sphere::CURVATURE_COMPONENT]
|
||||||
|
);
|
||||||
|
assert!((final_half_curv / INITIAL_HALF_CURV - 1.0).abs() < DRIFT_TOL);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -20,11 +20,23 @@ use crate::{
|
||||||
assembly::{Element, ElementColor, ElementMotion, Point, Sphere}
|
assembly::{Element, ElementColor, ElementMotion, Point, Sphere}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- color ---
|
||||||
|
|
||||||
|
const COLOR_SIZE: usize = 3;
|
||||||
|
type ColorWithOpacity = [f32; COLOR_SIZE + 1];
|
||||||
|
|
||||||
|
fn combine_channels(color: ElementColor, opacity: f32) -> ColorWithOpacity {
|
||||||
|
let mut color_with_opacity = [0.0; COLOR_SIZE + 1];
|
||||||
|
color_with_opacity[..COLOR_SIZE].copy_from_slice(&color);
|
||||||
|
color_with_opacity[COLOR_SIZE] = opacity;
|
||||||
|
color_with_opacity
|
||||||
|
}
|
||||||
|
|
||||||
// --- scene data ---
|
// --- scene data ---
|
||||||
|
|
||||||
struct SceneSpheres {
|
struct SceneSpheres {
|
||||||
representations: Vec<DVector<f64>>,
|
representations: Vec<DVector<f64>>,
|
||||||
colors: Vec<ElementColor>,
|
colors_with_opacity: Vec<ColorWithOpacity>,
|
||||||
highlights: Vec<f32>
|
highlights: Vec<f32>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +44,7 @@ impl SceneSpheres {
|
||||||
fn new() -> SceneSpheres{
|
fn new() -> SceneSpheres{
|
||||||
SceneSpheres {
|
SceneSpheres {
|
||||||
representations: Vec::new(),
|
representations: Vec::new(),
|
||||||
colors: Vec::new(),
|
colors_with_opacity: Vec::new(),
|
||||||
highlights: Vec::new()
|
highlights: Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,16 +53,16 @@ impl SceneSpheres {
|
||||||
self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer")
|
self.representations.len().try_into().expect("Number of spheres must fit in a 32-bit integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, representation: DVector<f64>, color: ElementColor, highlight: f32) {
|
fn push(&mut self, representation: DVector<f64>, color: ElementColor, opacity: f32, highlight: f32) {
|
||||||
self.representations.push(representation);
|
self.representations.push(representation);
|
||||||
self.colors.push(color);
|
self.colors_with_opacity.push(combine_channels(color, opacity));
|
||||||
self.highlights.push(highlight);
|
self.highlights.push(highlight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ScenePoints {
|
struct ScenePoints {
|
||||||
representations: Vec<DVector<f64>>,
|
representations: Vec<DVector<f64>>,
|
||||||
colors: Vec<ElementColor>,
|
colors_with_opacity: Vec<ColorWithOpacity>,
|
||||||
highlights: Vec<f32>,
|
highlights: Vec<f32>,
|
||||||
selections: Vec<f32>
|
selections: Vec<f32>
|
||||||
}
|
}
|
||||||
|
@ -59,15 +71,15 @@ impl ScenePoints {
|
||||||
fn new() -> ScenePoints {
|
fn new() -> ScenePoints {
|
||||||
ScenePoints {
|
ScenePoints {
|
||||||
representations: Vec::new(),
|
representations: Vec::new(),
|
||||||
colors: Vec::new(),
|
colors_with_opacity: Vec::new(),
|
||||||
highlights: Vec::new(),
|
highlights: Vec::new(),
|
||||||
selections: Vec::new()
|
selections: Vec::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push(&mut self, representation: DVector<f64>, color: ElementColor, highlight: f32, selected: bool) {
|
fn push(&mut self, representation: DVector<f64>, color: ElementColor, opacity: f32, highlight: f32, selected: bool) {
|
||||||
self.representations.push(representation);
|
self.representations.push(representation);
|
||||||
self.colors.push(color);
|
self.colors_with_opacity.push(combine_channels(color, opacity));
|
||||||
self.highlights.push(highlight);
|
self.highlights.push(highlight);
|
||||||
self.selections.push(if selected { 1.0 } else { 0.0 });
|
self.selections.push(if selected { 1.0 } else { 0.0 });
|
||||||
}
|
}
|
||||||
|
@ -98,11 +110,16 @@ pub trait DisplayItem {
|
||||||
|
|
||||||
impl DisplayItem for Sphere {
|
impl DisplayItem for Sphere {
|
||||||
fn show(&self, scene: &mut Scene, selected: bool) {
|
fn show(&self, scene: &mut Scene, selected: bool) {
|
||||||
const HIGHLIGHT: f32 = 0.2; /* SCAFFOLDING */
|
/* SCAFFOLDING */
|
||||||
|
const DEFAULT_OPACITY: f32 = 0.5;
|
||||||
|
const GHOST_OPACITY: f32 = 0.2;
|
||||||
|
const HIGHLIGHT: f32 = 0.2;
|
||||||
|
|
||||||
let representation = self.representation.get_clone_untracked();
|
let representation = self.representation.get_clone_untracked();
|
||||||
let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color };
|
let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color };
|
||||||
|
let opacity = if self.ghost.get() { GHOST_OPACITY } else { DEFAULT_OPACITY };
|
||||||
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
||||||
scene.spheres.push(representation, color, highlight);
|
scene.spheres.push(representation, color, opacity, highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method should be kept synchronized with `sphere_cast` in
|
// this method should be kept synchronized with `sphere_cast` in
|
||||||
|
@ -148,11 +165,15 @@ impl DisplayItem for Sphere {
|
||||||
|
|
||||||
impl DisplayItem for Point {
|
impl DisplayItem for Point {
|
||||||
fn show(&self, scene: &mut Scene, selected: bool) {
|
fn show(&self, scene: &mut Scene, selected: bool) {
|
||||||
const HIGHLIGHT: f32 = 0.5; /* SCAFFOLDING */
|
/* SCAFFOLDING */
|
||||||
|
const GHOST_OPACITY: f32 = 0.4;
|
||||||
|
const HIGHLIGHT: f32 = 0.5;
|
||||||
|
|
||||||
let representation = self.representation.get_clone_untracked();
|
let representation = self.representation.get_clone_untracked();
|
||||||
let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color };
|
let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color };
|
||||||
|
let opacity = if self.ghost.get() { GHOST_OPACITY } else { 1.0 };
|
||||||
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
||||||
scene.points.push(representation, color, highlight, selected);
|
scene.points.push(representation, color, opacity, highlight, selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* SCAFFOLDING */
|
/* SCAFFOLDING */
|
||||||
|
@ -365,6 +386,7 @@ pub fn Display() -> View {
|
||||||
state.assembly.elements.with(|elts| {
|
state.assembly.elements.with(|elts| {
|
||||||
for elt in elts {
|
for elt in elts {
|
||||||
elt.representation().track();
|
elt.representation().track();
|
||||||
|
elt.ghost().track();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
state.selection.track();
|
state.selection.track();
|
||||||
|
@ -395,7 +417,6 @@ pub fn Display() -> View {
|
||||||
const SHRINKING_SPEED: f64 = 0.15; // in length units per second
|
const SHRINKING_SPEED: f64 = 0.15; // in length units per second
|
||||||
|
|
||||||
// display parameters
|
// display parameters
|
||||||
const OPACITY: f32 = 0.5; /* SCAFFOLDING */
|
|
||||||
const LAYER_THRESHOLD: i32 = 0; /* DEBUG */
|
const LAYER_THRESHOLD: i32 = 0; /* DEBUG */
|
||||||
const DEBUG_MODE: i32 = 0; /* DEBUG */
|
const DEBUG_MODE: i32 = 0; /* DEBUG */
|
||||||
|
|
||||||
|
@ -469,7 +490,6 @@ pub fn Display() -> View {
|
||||||
);
|
);
|
||||||
let resolution_loc = ctx.get_uniform_location(&sphere_program, "resolution");
|
let resolution_loc = ctx.get_uniform_location(&sphere_program, "resolution");
|
||||||
let shortdim_loc = ctx.get_uniform_location(&sphere_program, "shortdim");
|
let shortdim_loc = ctx.get_uniform_location(&sphere_program, "shortdim");
|
||||||
let opacity_loc = ctx.get_uniform_location(&sphere_program, "opacity");
|
|
||||||
let layer_threshold_loc = ctx.get_uniform_location(&sphere_program, "layer_threshold");
|
let layer_threshold_loc = ctx.get_uniform_location(&sphere_program, "layer_threshold");
|
||||||
let debug_mode_loc = ctx.get_uniform_location(&sphere_program, "debug_mode");
|
let debug_mode_loc = ctx.get_uniform_location(&sphere_program, "debug_mode");
|
||||||
|
|
||||||
|
@ -654,9 +674,9 @@ pub fn Display() -> View {
|
||||||
sphere_lt_locs[n].as_ref(),
|
sphere_lt_locs[n].as_ref(),
|
||||||
v.rows(3, 2).as_slice()
|
v.rows(3, 2).as_slice()
|
||||||
);
|
);
|
||||||
ctx.uniform3fv_with_f32_array(
|
ctx.uniform4fv_with_f32_array(
|
||||||
sphere_color_locs[n].as_ref(),
|
sphere_color_locs[n].as_ref(),
|
||||||
&scene.spheres.colors[n]
|
&scene.spheres.colors_with_opacity[n]
|
||||||
);
|
);
|
||||||
ctx.uniform1f(
|
ctx.uniform1f(
|
||||||
sphere_highlight_locs[n].as_ref(),
|
sphere_highlight_locs[n].as_ref(),
|
||||||
|
@ -665,7 +685,6 @@ pub fn Display() -> View {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass the display parameters
|
// pass the display parameters
|
||||||
ctx.uniform1f(opacity_loc.as_ref(), OPACITY);
|
|
||||||
ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD);
|
ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD);
|
||||||
ctx.uniform1i(debug_mode_loc.as_ref(), DEBUG_MODE);
|
ctx.uniform1i(debug_mode_loc.as_ref(), DEBUG_MODE);
|
||||||
|
|
||||||
|
@ -703,7 +722,7 @@ pub fn Display() -> View {
|
||||||
// bind them to the corresponding attributes in the vertex
|
// bind them to the corresponding attributes in the vertex
|
||||||
// shader
|
// shader
|
||||||
bind_new_buffer_to_attribute(&ctx, point_position_attr, SPACE_DIM as i32, point_positions.as_slice());
|
bind_new_buffer_to_attribute(&ctx, point_position_attr, SPACE_DIM as i32, point_positions.as_slice());
|
||||||
bind_new_buffer_to_attribute(&ctx, point_color_attr, COLOR_SIZE as i32, scene.points.colors.concat().as_slice());
|
bind_new_buffer_to_attribute(&ctx, point_color_attr, (COLOR_SIZE + 1) as i32, scene.points.colors_with_opacity.concat().as_slice());
|
||||||
bind_new_buffer_to_attribute(&ctx, point_highlight_attr, 1 as i32, scene.points.highlights.as_slice());
|
bind_new_buffer_to_attribute(&ctx, point_highlight_attr, 1 as i32, scene.points.highlights.as_slice());
|
||||||
bind_new_buffer_to_attribute(&ctx, point_selection_attr, 1 as i32, scene.points.selections.as_slice());
|
bind_new_buffer_to_attribute(&ctx, point_selection_attr, 1 as i32, scene.points.selections.as_slice());
|
||||||
|
|
||||||
|
@ -851,7 +870,11 @@ pub fn Display() -> View {
|
||||||
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<(Rc<dyn Element>, f64)> = None;
|
let mut clicked: Option<(Rc<dyn Element>, f64)> = None;
|
||||||
for elt in state.assembly.elements.get_clone_untracked() {
|
let tangible_elts = state.assembly.elements
|
||||||
|
.get_clone_untracked()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|elt| !elt.ghost().get());
|
||||||
|
for elt in tangible_elts {
|
||||||
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)) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen};
|
use nalgebra::{Const, DMatrix, DVector, DVectorView, Dyn, SymmetricEigen};
|
||||||
|
use std::fmt::{Display, Error, Formatter};
|
||||||
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
||||||
|
|
||||||
// --- elements ---
|
// --- elements ---
|
||||||
|
@ -34,6 +35,21 @@ pub fn sphere_with_offset(dir_x: f64, dir_y: f64, dir_z: f64, off: f64, curv: f6
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// project a sphere's representation vector to the normalization variety by
|
||||||
|
// contracting toward the last coordinate axis
|
||||||
|
pub fn project_sphere_to_normalized(rep: &mut DVector<f64>) {
|
||||||
|
let q_sp = rep.fixed_rows::<3>(0).norm_squared();
|
||||||
|
let half_q_lt = -2.0 * rep[3] * rep[4];
|
||||||
|
let half_q_lt_sq = half_q_lt * half_q_lt;
|
||||||
|
let scaling = half_q_lt + (q_sp + half_q_lt_sq).sqrt();
|
||||||
|
rep.fixed_rows_mut::<4>(0).scale_mut(1.0 / scaling);
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalize a point's representation vector by scaling
|
||||||
|
pub fn project_point_to_normalized(rep: &mut DVector<f64>) {
|
||||||
|
rep.scale_mut(0.5 / rep[3]);
|
||||||
|
}
|
||||||
|
|
||||||
// given a sphere's representation vector, change the sphere's half-curvature to
|
// given a sphere's representation vector, change the sphere's half-curvature to
|
||||||
// `half-curv` and then restore normalization by contracting the representation
|
// `half-curv` and then restore normalization by contracting the representation
|
||||||
// vector toward the curvature axis
|
// vector toward the curvature axis
|
||||||
|
@ -94,15 +110,6 @@ impl PartialMatrix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DEBUG */
|
|
||||||
pub fn log_to_console(&self) {
|
|
||||||
for &MatrixEntry { index: (row, col), value } in self {
|
|
||||||
console::log_1(&JsValue::from(
|
|
||||||
format!(" {} {} {}", row, col, value)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn freeze(&self, a: &DMatrix<f64>) -> DMatrix<f64> {
|
fn freeze(&self, a: &DMatrix<f64>) -> DMatrix<f64> {
|
||||||
let mut result = a.clone();
|
let mut result = a.clone();
|
||||||
for &MatrixEntry { index, value } in self {
|
for &MatrixEntry { index, value } in self {
|
||||||
|
@ -128,6 +135,15 @@ impl PartialMatrix {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for PartialMatrix {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
|
||||||
|
for &MatrixEntry { index: (row, col), value } in self {
|
||||||
|
writeln!(f, " {row} {col} {value}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IntoIterator for PartialMatrix {
|
impl IntoIterator for PartialMatrix {
|
||||||
type Item = MatrixEntry;
|
type Item = MatrixEntry;
|
||||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||||
|
|
|
@ -202,7 +202,11 @@ fn ElementOutlineItem(element: Rc<dyn Element>) -> View {
|
||||||
) {
|
) {
|
||||||
div(class="element-label") { (label) }
|
div(class="element-label") { (label) }
|
||||||
div(class="element-representation") { (rep_components) }
|
div(class="element-representation") { (rep_components) }
|
||||||
div(class="status")
|
input(
|
||||||
|
r#type="checkbox",
|
||||||
|
bind:checked=element.ghost(),
|
||||||
|
on:click=|event: MouseEvent| event.stop_propagation()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ul(class="regulators") {
|
ul(class="regulators") {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
in vec3 point_color;
|
in vec4 point_color;
|
||||||
in float point_highlight;
|
in float point_highlight;
|
||||||
in float total_radius;
|
in float total_radius;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ void main() {
|
||||||
|
|
||||||
const float POINT_RADIUS = 4.;
|
const float POINT_RADIUS = 4.;
|
||||||
float border = smoothstep(POINT_RADIUS - 1., POINT_RADIUS, r);
|
float border = smoothstep(POINT_RADIUS - 1., POINT_RADIUS, r);
|
||||||
vec3 color = mix(point_color, vec3(1.), border * point_highlight);
|
float disk = 1. - smoothstep(total_radius - 1., total_radius, r);
|
||||||
outColor = vec4(color, 1. - smoothstep(total_radius - 1., total_radius, r));
|
vec4 color = mix(point_color, vec4(1.), border * point_highlight);
|
||||||
|
outColor = vec4(vec3(1.), disk) * color;
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
#version 300 es
|
#version 300 es
|
||||||
|
|
||||||
in vec4 position;
|
in vec4 position;
|
||||||
in vec3 color;
|
in vec4 color;
|
||||||
in float highlight;
|
in float highlight;
|
||||||
in float selected;
|
in float selected;
|
||||||
|
|
||||||
out vec3 point_color;
|
out vec4 point_color;
|
||||||
out float point_highlight;
|
out float point_highlight;
|
||||||
out float total_radius;
|
out float total_radius;
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ struct vecInv {
|
||||||
const int SPHERE_MAX = 200;
|
const int SPHERE_MAX = 200;
|
||||||
uniform int sphere_cnt;
|
uniform int sphere_cnt;
|
||||||
uniform vecInv sphere_list[SPHERE_MAX];
|
uniform vecInv sphere_list[SPHERE_MAX];
|
||||||
uniform vec3 color_list[SPHERE_MAX];
|
uniform vec4 color_list[SPHERE_MAX];
|
||||||
uniform float highlight_list[SPHERE_MAX];
|
uniform float highlight_list[SPHERE_MAX];
|
||||||
|
|
||||||
// view
|
// view
|
||||||
|
@ -25,7 +25,6 @@ uniform vec2 resolution;
|
||||||
uniform float shortdim;
|
uniform float shortdim;
|
||||||
|
|
||||||
// controls
|
// controls
|
||||||
uniform float opacity;
|
|
||||||
uniform int layer_threshold;
|
uniform int layer_threshold;
|
||||||
uniform bool debug_mode;
|
uniform bool debug_mode;
|
||||||
|
|
||||||
|
@ -69,7 +68,7 @@ struct Fragment {
|
||||||
vec4 color;
|
vec4 color;
|
||||||
};
|
};
|
||||||
|
|
||||||
Fragment sphere_shading(vecInv v, vec3 pt, vec3 base_color) {
|
Fragment sphere_shading(vecInv v, vec3 pt, vec4 base_color) {
|
||||||
// the expression for normal needs to be checked. it's supposed to give the
|
// the expression for normal needs to be checked. it's supposed to give the
|
||||||
// negative gradient of the lorentz product between the impact point vector
|
// negative gradient of the lorentz product between the impact point vector
|
||||||
// and the sphere vector with respect to the coordinates of the impact
|
// and the sphere vector with respect to the coordinates of the impact
|
||||||
|
@ -79,7 +78,7 @@ Fragment sphere_shading(vecInv v, vec3 pt, vec3 base_color) {
|
||||||
|
|
||||||
float incidence = dot(normal, light_dir);
|
float incidence = dot(normal, light_dir);
|
||||||
float illum = mix(0.4, 1.0, max(incidence, 0.0));
|
float illum = mix(0.4, 1.0, max(incidence, 0.0));
|
||||||
return Fragment(pt, normal, vec4(illum * base_color, opacity));
|
return Fragment(pt, normal, vec4(illum * base_color.rgb, base_color.a));
|
||||||
}
|
}
|
||||||
|
|
||||||
float intersection_dist(Fragment a, Fragment b) {
|
float intersection_dist(Fragment a, Fragment b) {
|
||||||
|
@ -192,10 +191,11 @@ void main() {
|
||||||
vec3 color = vec3(0.);
|
vec3 color = vec3(0.);
|
||||||
int layer = layer_cnt - 1;
|
int layer = layer_cnt - 1;
|
||||||
TaggedDepth hit = top_hits[layer];
|
TaggedDepth hit = top_hits[layer];
|
||||||
|
vec4 sphere_color = color_list[hit.id];
|
||||||
Fragment frag_next = sphere_shading(
|
Fragment frag_next = sphere_shading(
|
||||||
sphere_list[hit.id],
|
sphere_list[hit.id],
|
||||||
hit.depth * dir,
|
hit.depth * dir,
|
||||||
hit.dimming * color_list[hit.id]
|
vec4(hit.dimming * sphere_color.rgb, sphere_color.a)
|
||||||
);
|
);
|
||||||
float highlight_next = highlight_list[hit.id];
|
float highlight_next = highlight_list[hit.id];
|
||||||
--layer;
|
--layer;
|
||||||
|
@ -206,10 +206,11 @@ void main() {
|
||||||
|
|
||||||
// shade the next fragment
|
// shade the next fragment
|
||||||
hit = top_hits[layer];
|
hit = top_hits[layer];
|
||||||
|
sphere_color = color_list[hit.id];
|
||||||
frag_next = sphere_shading(
|
frag_next = sphere_shading(
|
||||||
sphere_list[hit.id],
|
sphere_list[hit.id],
|
||||||
hit.depth * dir,
|
hit.depth * dir,
|
||||||
hit.dimming * color_list[hit.id]
|
vec4(hit.dimming * sphere_color.rgb, sphere_color.a)
|
||||||
);
|
);
|
||||||
highlight_next = highlight_list[hit.id];
|
highlight_next = highlight_list[hit.id];
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue