Points #82
9 changed files with 815 additions and 330 deletions
|
@ -42,9 +42,7 @@ body {
|
|||
}
|
||||
|
||||
#add-remove > button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
/* KLUDGE */
|
||||
glen marked this conversation as resolved
Outdated
|
||||
|
@ -53,7 +51,9 @@ body {
|
|||
buttons need to be displayed in an emoji font
|
||||
*/
|
||||
#add-remove > button.emoji {
|
||||
width: 32px;
|
||||
font-family: 'Noto Emoji', sans-serif;
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
/* outline */
|
||||
|
|
|
@ -1,58 +1,59 @@
|
|||
use std::{f64::consts::FRAC_1_SQRT_2, rc::Rc};
|
||||
use sycamore::prelude::*;
|
||||
use web_sys::{console, wasm_bindgen::JsValue};
|
||||
|
||||
use crate::{
|
||||
engine,
|
||||
AppState,
|
||||
assembly::{Assembly, Element, InversiveDistanceRegulator}
|
||||
assembly::{Assembly, InversiveDistanceRegulator, Point, Sphere}
|
||||
};
|
||||
|
||||
/* 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) {
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("gemini_a"),
|
||||
String::from("Castor"),
|
||||
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||
engine::sphere(0.5, 0.5, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("gemini_b"),
|
||||
String::from("Pollux"),
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
engine::sphere(-0.5, -0.5, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("ursa_major"),
|
||||
String::from("Ursa major"),
|
||||
[0.25_f32, 0.00_f32, 1.00_f32],
|
||||
engine::sphere(-0.5, 0.5, 0.0, 0.75)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("ursa_minor"),
|
||||
String::from("Ursa minor"),
|
||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||
engine::sphere(0.5, -0.5, 0.0, 0.5)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("moon_deimos"),
|
||||
String::from("Deimos"),
|
||||
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||
engine::sphere(0.0, 0.15, 1.0, 0.25)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("moon_phobos"),
|
||||
String::from("Phobos"),
|
||||
[0.00_f32, 0.75_f32, 0.50_f32],
|
||||
|
@ -66,64 +67,64 @@ fn load_gen_assemb(assembly: &Assembly) {
|
|||
// built a more formal test assembly system
|
||||
fn load_low_curv_assemb(assembly: &Assembly) {
|
||||
let a = 0.75_f64.sqrt();
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"central".to_string(),
|
||||
"Central".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"assemb_plane".to_string(),
|
||||
"Assembly plane".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side1".to_string(),
|
||||
"Side 1".to_string(),
|
||||
[1.00_f32, 0.00_f32, 0.25_f32],
|
||||
engine::sphere_with_offset(1.0, 0.0, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side2".to_string(),
|
||||
"Side 2".to_string(),
|
||||
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||
engine::sphere_with_offset(-0.5, a, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"side3".to_string(),
|
||||
"Side 3".to_string(),
|
||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||
engine::sphere_with_offset(-0.5, -a, 0.0, 1.0, 0.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"corner1".to_string(),
|
||||
"Corner 1".to_string(),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
"corner2".to_string(),
|
||||
"Corner 2".to_string(),
|
||||
[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)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_sphere(
|
||||
Element::new(
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
String::from("corner3"),
|
||||
String::from("Corner 3"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
|
@ -132,6 +133,49 @@ fn load_low_curv_assemb(assembly: &Assembly) {
|
|||
);
|
||||
}
|
||||
|
||||
fn load_pointed_assemb(assembly: &Assembly) {
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point_front"),
|
||||
format!("Front point"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::point(0.0, 0.0, FRAC_1_SQRT_2)
|
||||
)
|
||||
);
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point_back"),
|
||||
format!("Back point"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
engine::point(0.0, 0.0, -FRAC_1_SQRT_2)
|
||||
)
|
||||
);
|
||||
for index_x in 0..=1 {
|
||||
for index_y in 0..=1 {
|
||||
let x = index_x as f64 - 0.5;
|
||||
let y = index_y as f64 - 0.5;
|
||||
|
||||
let _ = assembly.try_insert_element(
|
||||
Sphere::new(
|
||||
format!("sphere{index_x}{index_y}"),
|
||||
format!("Sphere {index_x}{index_y}"),
|
||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||
engine::sphere(x, y, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
|
||||
let _ = assembly.try_insert_element(
|
||||
Point::new(
|
||||
format!("point{index_x}{index_y}"),
|
||||
format!("Point {index_x}{index_y}"),
|
||||
[0.5*(1.0 + x) as f32, 0.5*(1.0 + y) as f32, 0.5*(1.0 - x*y) as f32],
|
||||
engine::point(x, y, 0.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn AddRemove() -> View {
|
||||
/* DEBUG */
|
||||
|
@ -157,6 +201,7 @@ pub fn AddRemove() -> View {
|
|||
match name.as_str() {
|
||||
"general" => load_gen_assemb(assembly),
|
||||
"low-curv" => load_low_curv_assemb(assembly),
|
||||
"pointed" => load_pointed_assemb(assembly),
|
||||
_ => ()
|
||||
};
|
||||
});
|
||||
|
@ -167,9 +212,15 @@ pub fn AddRemove() -> View {
|
|||
button(
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.insert_new_sphere();
|
||||
state.assembly.insert_element_default::<Sphere>();
|
||||
}
|
||||
) { "+" }
|
||||
) { "Add sphere" }
|
||||
button(
|
||||
on:click=|_| {
|
||||
let state = use_context::<AppState>();
|
||||
state.assembly.insert_element_default::<Point>();
|
||||
}
|
||||
) { "Add point" }
|
||||
button(
|
||||
class="emoji", /* KLUDGE */ // for convenience, we're using an emoji as a temporary icon for this button
|
||||
disabled={
|
||||
|
@ -190,7 +241,7 @@ pub fn AddRemove() -> View {
|
|||
.unwrap()
|
||||
);
|
||||
state.assembly.insert_regulator(
|
||||
InversiveDistanceRegulator::new(subjects, &state.assembly)
|
||||
Rc::new(InversiveDistanceRegulator::new(subjects, &state.assembly))
|
||||
);
|
||||
state.selection.update(|sel| sel.clear());
|
||||
}
|
||||
|
@ -198,6 +249,7 @@ pub fn AddRemove() -> View {
|
|||
select(bind:value=assembly_name) { /* DEBUG */ // example assembly chooser
|
||||
option(value="general") { "General" }
|
||||
option(value="low-curv") { "Low-curvature" }
|
||||
option(value="pointed") { "Pointed" }
|
||||
option(value="empty") { "Empty" }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
use nalgebra::{DMatrix, DVector, DVectorView, Vector3};
|
||||
use nalgebra::{DMatrix, DVector, DVectorView};
|
||||
use rustc_hash::FxHashMap;
|
||||
use slab::Slab;
|
||||
use std::{collections::BTreeSet, rc::Rc, sync::atomic::{AtomicU64, Ordering}};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::Cell,
|
||||
collections::BTreeSet,
|
||||
rc::Rc,
|
||||
sync::atomic::{AtomicU64, Ordering}
|
||||
};
|
||||
use sycamore::prelude::*;
|
||||
use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
||||
|
||||
use crate::{
|
||||
display::DisplayItem,
|
||||
engine::{
|
||||
Q,
|
||||
change_half_curvature,
|
||||
local_unif_to_std,
|
||||
point,
|
||||
realize_gram,
|
||||
sphere,
|
||||
ConfigSubspace,
|
||||
|
@ -33,31 +41,87 @@ pub type ElementColor = [f32; 3];
|
|||
static NEXT_ELEMENT_SERIAL: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
pub trait ProblemPoser {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>);
|
||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>);
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct 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
|
||||
fn serial(&self) -> u64;
|
||||
|
||||
// take the next serial number, panicking if that was the last one left
|
||||
fn next_serial() -> u64 where Self: Sized {
|
||||
// the technique we use to panic on overflow is taken from _Rust Atomics
|
||||
// and Locks_, by Mara Bos
|
||||
//
|
||||
// https://marabos.nl/atomics/atomics.html#example-handle-overflow
|
||||
//
|
||||
NEXT_ELEMENT_SERIAL.fetch_update(
|
||||
Ordering::SeqCst, Ordering::SeqCst,
|
||||
|serial| serial.checked_add(1)
|
||||
).expect("Out of serial numbers for elements")
|
||||
}
|
||||
|
||||
// the configuration matrix column index that was assigned to the element
|
||||
// last time the assembly was realized, or `None` if the element has never
|
||||
// been through a realization
|
||||
fn column_index(&self) -> Option<usize>;
|
||||
|
||||
// assign the element a configuration matrix column index. this method must
|
||||
// be used carefully to preserve invariant (1), described in the comment on
|
||||
// the `tangent` field of the `Assembly` structure
|
||||
fn set_column_index(&self, index: usize);
|
||||
}
|
||||
|
||||
// the `Element` trait needs to be dyn-compatible, so its method signatures can
|
||||
// only use `Self` in the type of the receiver. that means `Element` can't
|
||||
// implement `PartialEq`. if you need partial equivalence for `Element` trait
|
||||
// objects, use this wrapper
|
||||
#[derive(Clone)]
|
||||
pub struct ElementRc(pub Rc<dyn Element>);
|
||||
|
||||
impl PartialEq for ElementRc {
|
||||
fn eq(&self, ElementRc(other): &Self) -> bool {
|
||||
let ElementRc(rc) = self;
|
||||
Rc::ptr_eq(rc, &other)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Sphere {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub color: ElementColor,
|
||||
pub representation: Signal<DVector<f64>>,
|
||||
|
||||
// the regulators this element is subject to. the assembly that owns the
|
||||
// element is responsible for keeping this set up to date
|
||||
pub regulators: Signal<BTreeSet<RegulatorKey>>,
|
||||
|
||||
// a serial number, assigned by `Element::new`, that uniquely identifies
|
||||
// each element
|
||||
pub serial: u64,
|
||||
|
||||
// the configuration matrix column index that was assigned to this element
|
||||
// last time the assembly was realized, or `None` if the element has never
|
||||
// been through a realization
|
||||
column_index: Option<usize>
|
||||
column_index: Cell<Option<usize>>
|
||||
}
|
||||
|
||||
impl Element {
|
||||
impl Sphere {
|
||||
const CURVATURE_COMPONENT: usize = 3;
|
||||
|
||||
pub fn new(
|
||||
|
@ -65,83 +129,161 @@ impl Element {
|
|||
label: String,
|
||||
color: ElementColor,
|
||||
representation: DVector<f64>
|
||||
) -> Element {
|
||||
// take the next serial number, panicking if that was the last number we
|
||||
// had left. the technique we use to panic on overflow is taken from
|
||||
// _Rust Atomics and Locks_, by Mara Bos
|
||||
//
|
||||
// https://marabos.nl/atomics/atomics.html#example-handle-overflow
|
||||
//
|
||||
let serial = NEXT_ELEMENT_SERIAL.fetch_update(
|
||||
Ordering::SeqCst, Ordering::SeqCst,
|
||||
|serial| serial.checked_add(1)
|
||||
).expect("Out of serial numbers for elements");
|
||||
|
||||
Element {
|
||||
) -> Sphere {
|
||||
Sphere {
|
||||
id: id,
|
||||
label: label,
|
||||
color: color,
|
||||
representation: create_signal(representation),
|
||||
regulators: create_signal(BTreeSet::default()),
|
||||
serial: serial,
|
||||
column_index: None
|
||||
}
|
||||
}
|
||||
|
||||
// the smallest positive depth, represented as a multiple of `dir`, where
|
||||
// the line generated by `dir` hits the element (which is assumed to be a
|
||||
// sphere). returns `None` if the line misses the sphere. this function
|
||||
// should be kept synchronized with `sphere_cast` in `inversive.frag`, which
|
||||
// does essentially the same thing on the GPU side
|
||||
pub fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>) -> Option<f64> {
|
||||
// if `a/b` is less than this threshold, we approximate
|
||||
// `a*u^2 + b*u + c` by the linear function `b*u + c`
|
||||
const DEG_THRESHOLD: f64 = 1e-9;
|
||||
|
||||
let rep = self.representation.with_untracked(|rep| assembly_to_world * rep);
|
||||
let a = -rep[3] * dir.norm_squared();
|
||||
let b = rep.rows_range(..3).dot(&dir);
|
||||
let c = -rep[4];
|
||||
|
||||
let adjust = 4.0*a*c/(b*b);
|
||||
if adjust < 1.0 {
|
||||
// as long as `b` is non-zero, the linear approximation of
|
||||
//
|
||||
// a*u^2 + b*u + c
|
||||
//
|
||||
// at `u = 0` will reach zero at a finite depth `u_lin`. the root of
|
||||
// the quadratic adjacent to `u_lin` is stored in `lin_root`. if
|
||||
// both roots have the same sign, `lin_root` will be the one closer
|
||||
// to `u = 0`
|
||||
let square_rect_ratio = 1.0 + (1.0 - adjust).sqrt();
|
||||
let lin_root = -(2.0*c)/b / square_rect_ratio;
|
||||
if a.abs() > DEG_THRESHOLD * b.abs() {
|
||||
if lin_root > 0.0 {
|
||||
Some(lin_root)
|
||||
} else {
|
||||
let other_root = -b/(2.*a) * square_rect_ratio;
|
||||
(other_root > 0.0).then_some(other_root)
|
||||
}
|
||||
} else {
|
||||
(lin_root > 0.0).then_some(lin_root)
|
||||
}
|
||||
} else {
|
||||
// the line through `dir` misses the sphere completely
|
||||
None
|
||||
serial: Self::next_serial(),
|
||||
column_index: None.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ProblemPoser for Element {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Element>) {
|
||||
let index = self.column_index.expect(
|
||||
format!("Element \"{}\" should be indexed before writing problem data", self.id).as_str()
|
||||
impl Element for Sphere {
|
||||
fn default_id() -> String {
|
||||
"sphere".to_string()
|
||||
}
|
||||
|
||||
fn default(id: String, id_num: u64) -> Sphere {
|
||||
Sphere::new(
|
||||
id,
|
||||
format!("Sphere {id_num}"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
sphere(0.0, 0.0, 0.0, 1.0)
|
||||
)
|
||||
}
|
||||
|
||||
fn default_regulators(key: ElementKey, assembly: &Assembly) -> Vec<Rc<dyn Regulator>> {
|
||||
vec![Rc::new(HalfCurvatureRegulator::new(key, assembly))]
|
||||
}
|
||||
|
||||
fn id(&self) -> &String {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn label(&self) -> &String {
|
||||
&self.label
|
||||
}
|
||||
|
||||
fn representation(&self) -> Signal<DVector<f64>> {
|
||||
self.representation
|
||||
}
|
||||
|
||||
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
|
||||
self.regulators
|
||||
}
|
||||
|
||||
fn serial(&self) -> u64 {
|
||||
self.serial
|
||||
}
|
||||
|
||||
fn column_index(&self) -> Option<usize> {
|
||||
self.column_index.get()
|
||||
}
|
||||
|
||||
fn set_column_index(&self, index: usize) {
|
||||
self.column_index.set(Some(index));
|
||||
}
|
||||
}
|
||||
|
||||
impl ProblemPoser for Sphere {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
|
||||
let index = self.column_index().expect(
|
||||
format!("Sphere \"{}\" should be indexed before writing problem data", self.id).as_str()
|
||||
);
|
||||
problem.gram.push_sym(index, index, 1.0);
|
||||
problem.guess.set_column(index, &self.representation.get_clone_untracked());
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Point {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub color: ElementColor,
|
||||
pub representation: Signal<DVector<f64>>,
|
||||
pub regulators: Signal<BTreeSet<RegulatorKey>>,
|
||||
pub serial: u64,
|
||||
column_index: Cell<Option<usize>>
|
||||
}
|
||||
|
||||
impl Point {
|
||||
const WEIGHT_COMPONENT: usize = 3;
|
||||
|
||||
pub fn new(
|
||||
id: String,
|
||||
label: String,
|
||||
color: ElementColor,
|
||||
representation: DVector<f64>
|
||||
) -> Point {
|
||||
Point {
|
||||
id,
|
||||
label,
|
||||
color,
|
||||
representation: create_signal(representation),
|
||||
regulators: create_signal(BTreeSet::default()),
|
||||
serial: Self::next_serial(),
|
||||
column_index: None.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Point {
|
||||
fn default_id() -> String {
|
||||
"point".to_string()
|
||||
}
|
||||
|
||||
fn default(id: String, id_num: u64) -> Point {
|
||||
Point::new(
|
||||
id,
|
||||
format!("Point {id_num}"),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
point(0.0, 0.0, 0.0)
|
||||
)
|
||||
}
|
||||
|
||||
fn id(&self) -> &String {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn label(&self) -> &String {
|
||||
&self.label
|
||||
}
|
||||
|
||||
fn representation(&self) -> Signal<DVector<f64>> {
|
||||
self.representation
|
||||
}
|
||||
|
||||
fn regulators(&self) -> Signal<BTreeSet<RegulatorKey>> {
|
||||
self.regulators
|
||||
}
|
||||
|
||||
fn serial(&self) -> u64 {
|
||||
self.serial
|
||||
}
|
||||
|
||||
fn column_index(&self) -> Option<usize> {
|
||||
self.column_index.get()
|
||||
}
|
||||
|
||||
fn set_column_index(&self, index: usize) {
|
||||
self.column_index.set(Some(index));
|
||||
}
|
||||
}
|
||||
|
||||
impl ProblemPoser for Point {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, _elts: &Slab<Rc<dyn Element>>) {
|
||||
let index = self.column_index().expect(
|
||||
format!("Point \"{}\" should be indexed before writing problem data", self.id).as_str()
|
||||
);
|
||||
problem.gram.push_sym(index, index, 0.0);
|
||||
problem.frozen.push(Point::WEIGHT_COMPONENT, index, 0.5);
|
||||
problem.guess.set_column(index, &self.representation.get_clone_untracked());
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Regulator: ProblemPoser + OutlineItem {
|
||||
fn subjects(&self) -> Vec<ElementKey>;
|
||||
fn measurement(&self) -> ReadSignal<f64>;
|
||||
|
@ -168,7 +310,7 @@ impl InversiveDistanceRegulator {
|
|||
pub fn new(subjects: [ElementKey; 2], assembly: &Assembly) -> InversiveDistanceRegulator {
|
||||
let measurement = assembly.elements.map(
|
||||
move |elts| {
|
||||
let representations = subjects.map(|subj| elts[subj].representation);
|
||||
let representations = subjects.map(|subj| elts[subj].representation());
|
||||
representations[0].with(|rep_0|
|
||||
representations[1].with(|rep_1|
|
||||
rep_0.dot(&(&*Q * rep_1))
|
||||
|
@ -198,11 +340,11 @@ impl Regulator for InversiveDistanceRegulator {
|
|||
}
|
||||
|
||||
impl ProblemPoser for InversiveDistanceRegulator {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>) {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) {
|
||||
self.set_point.with_untracked(|set_pt| {
|
||||
if let Some(val) = set_pt.value {
|
||||
let [row, col] = self.subjects.map(
|
||||
|subj| elts[subj].column_index.expect(
|
||||
|subj| elts[subj].column_index().expect(
|
||||
"Subjects should be indexed before inversive distance regulator writes problem data"
|
||||
)
|
||||
);
|
||||
|
@ -221,8 +363,8 @@ pub struct HalfCurvatureRegulator {
|
|||
impl HalfCurvatureRegulator {
|
||||
pub fn new(subject: ElementKey, assembly: &Assembly) -> HalfCurvatureRegulator {
|
||||
let measurement = assembly.elements.map(
|
||||
move |elts| elts[subject].representation.with(
|
||||
|rep| rep[Element::CURVATURE_COMPONENT]
|
||||
move |elts| elts[subject].representation().with(
|
||||
|rep| rep[Sphere::CURVATURE_COMPONENT]
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -249,7 +391,7 @@ impl Regulator for HalfCurvatureRegulator {
|
|||
match self.set_point.with(|set_pt| set_pt.value) {
|
||||
Some(half_curv) => {
|
||||
let representation = assembly.elements.with_untracked(
|
||||
|elts| elts[self.subject].representation
|
||||
|elts| elts[self.subject].representation()
|
||||
);
|
||||
representation.update(
|
||||
|rep| change_half_curvature(rep, half_curv)
|
||||
|
@ -262,13 +404,13 @@ impl Regulator for HalfCurvatureRegulator {
|
|||
}
|
||||
|
||||
impl ProblemPoser for HalfCurvatureRegulator {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Element>) {
|
||||
fn pose(&self, problem: &mut ConstraintProblem, elts: &Slab<Rc<dyn Element>>) {
|
||||
self.set_point.with_untracked(|set_pt| {
|
||||
if let Some(val) = set_pt.value {
|
||||
let col = elts[self.subject].column_index.expect(
|
||||
let col = elts[self.subject].column_index().expect(
|
||||
"Subject should be indexed before half-curvature regulator writes problem data"
|
||||
);
|
||||
problem.frozen.push(Element::CURVATURE_COMPONENT, col, val);
|
||||
problem.frozen.push(Sphere::CURVATURE_COMPONENT, col, val);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -286,7 +428,7 @@ type AssemblyMotion<'a> = Vec<ElementMotion<'a>>;
|
|||
#[derive(Clone)]
|
||||
pub struct Assembly {
|
||||
// elements and regulators
|
||||
pub elements: Signal<Slab<Element>>,
|
||||
pub elements: Signal<Slab<Rc<dyn Element>>>,
|
||||
pub regulators: Signal<Slab<Rc<dyn Regulator>>>,
|
||||
|
||||
// solution variety tangent space. the basis vectors are stored in
|
||||
|
@ -317,66 +459,61 @@ impl Assembly {
|
|||
|
||||
// --- inserting elements and regulators ---
|
||||
|
||||
// insert a sphere 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
|
||||
// same identifier will get kicked out of the `elements_by_id` index
|
||||
fn insert_sphere_unchecked(&self, elt: Element) -> ElementKey {
|
||||
// insert the sphere
|
||||
let id = elt.id.clone();
|
||||
let key = self.elements.update(|elts| elts.insert(elt));
|
||||
fn insert_element_unchecked<T: Element + 'static>(&self, elt: T) -> ElementKey {
|
||||
// insert the element
|
||||
let id = elt.id().clone();
|
||||
let key = self.elements.update(|elts| elts.insert(Rc::new(elt)));
|
||||
self.elements_by_id.update(|elts_by_id| elts_by_id.insert(id, key));
|
||||
|
||||
// regulate the sphere's curvature
|
||||
self.insert_regulator(HalfCurvatureRegulator::new(key, &self));
|
||||
// create and insert the element's default regulators
|
||||
for reg in T::default_regulators(key, &self) {
|
||||
self.insert_regulator(reg);
|
||||
}
|
||||
|
||||
key
|
||||
}
|
||||
|
||||
pub fn try_insert_sphere(&self, elt: Element) -> Option<ElementKey> {
|
||||
pub fn try_insert_element(&self, elt: impl Element + 'static) -> Option<ElementKey> {
|
||||
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 {
|
||||
Some(self.insert_sphere_unchecked(elt))
|
||||
Some(self.insert_element_unchecked(elt))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert_new_sphere(&self) {
|
||||
pub fn insert_element_default<T: Element + 'static>(&self) {
|
||||
// find the next unused identifier in the default sequence
|
||||
let default_id = T::default_id();
|
||||
let mut id_num = 1;
|
||||
let mut id = format!("sphere{}", id_num);
|
||||
let mut id = format!("{default_id}{id_num}");
|
||||
while self.elements_by_id.with_untracked(
|
||||
|elts_by_id| elts_by_id.contains_key(&id)
|
||||
) {
|
||||
id_num += 1;
|
||||
id = format!("sphere{}", id_num);
|
||||
id = format!("{default_id}{id_num}");
|
||||
}
|
||||
|
||||
// create and insert a sphere
|
||||
let _ = self.insert_sphere_unchecked(
|
||||
Element::new(
|
||||
id,
|
||||
format!("Sphere {}", id_num),
|
||||
[0.75_f32, 0.75_f32, 0.75_f32],
|
||||
sphere(0.0, 0.0, 0.0, 1.0)
|
||||
)
|
||||
);
|
||||
// create and insert the default example of `T`
|
||||
let _ = self.insert_element_unchecked(T::default(id, id_num));
|
||||
}
|
||||
|
||||
pub fn insert_regulator<T: Regulator + 'static>(&self, regulator: T) {
|
||||
pub fn insert_regulator(&self, regulator: Rc<dyn Regulator>) {
|
||||
// add the regulator to the assembly's regulator list
|
||||
let regulator_rc = Rc::new(regulator);
|
||||
let key = self.regulators.update(
|
||||
|regs| regs.insert(regulator_rc.clone())
|
||||
|regs| regs.insert(regulator.clone())
|
||||
);
|
||||
|
||||
// add the regulator to each subject's regulator list
|
||||
let subjects = regulator_rc.subjects();
|
||||
let subjects = regulator.subjects();
|
||||
let subject_regulators: Vec<_> = self.elements.with_untracked(
|
||||
|elts| subjects.into_iter().map(
|
||||
|subj| elts[subj].regulators
|
||||
|subj| elts[subj].regulators()
|
||||
).collect()
|
||||
);
|
||||
for regulators in subject_regulators {
|
||||
|
@ -390,10 +527,10 @@ impl Assembly {
|
|||
/* DEBUG */
|
||||
// log the regulator update
|
||||
console::log_1(&JsValue::from(
|
||||
format!("Updated regulator with subjects {:?}", regulator_rc.subjects())
|
||||
format!("Updated regulator with subjects {:?}", regulator.subjects())
|
||||
));
|
||||
|
||||
if regulator_rc.try_activate(&self_for_effect) {
|
||||
if regulator.try_activate(&self_for_effect) {
|
||||
self_for_effect.realize();
|
||||
}
|
||||
});
|
||||
|
@ -427,7 +564,7 @@ impl Assembly {
|
|||
// index the elements
|
||||
self.elements.update_silent(|elts| {
|
||||
for (index, (_, elt)) in elts.into_iter().enumerate() {
|
||||
elt.column_index = Some(index);
|
||||
elt.set_column_index(index);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -482,8 +619,8 @@ impl Assembly {
|
|||
if success {
|
||||
// read out the solution
|
||||
for (_, elt) in self.elements.get_clone_untracked() {
|
||||
elt.representation.update(
|
||||
|rep| rep.set_column(0, &config.column(elt.column_index.unwrap()))
|
||||
elt.representation().update(
|
||||
|rep| rep.set_column(0, &config.column(elt.column_index().unwrap()))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -521,8 +658,8 @@ impl Assembly {
|
|||
let mut next_column_index = realized_dim;
|
||||
for elt_motion in motion.iter() {
|
||||
let moving_elt = &mut elts[elt_motion.key];
|
||||
if moving_elt.column_index.is_none() {
|
||||
moving_elt.column_index = Some(next_column_index);
|
||||
if moving_elt.column_index().is_none() {
|
||||
moving_elt.set_column_index(next_column_index);
|
||||
next_column_index += 1;
|
||||
}
|
||||
}
|
||||
|
@ -539,7 +676,7 @@ impl Assembly {
|
|||
// we can unwrap the column index because we know that every moving
|
||||
// element has one at this point
|
||||
let column_index = self.elements.with_untracked(
|
||||
|elts| elts[elt_motion.key].column_index.unwrap()
|
||||
|elts| elts[elt_motion.key].column_index().unwrap()
|
||||
);
|
||||
|
||||
if column_index < realized_dim {
|
||||
|
@ -555,7 +692,7 @@ impl Assembly {
|
|||
let mut target_column = motion_proj.column_mut(column_index);
|
||||
let unif_to_std = self.elements.with_untracked(
|
||||
|elts| {
|
||||
elts[elt_motion.key].representation.with_untracked(
|
||||
elts[elt_motion.key].representation().with_untracked(
|
||||
|rep| local_unif_to_std(rep.as_view())
|
||||
)
|
||||
}
|
||||
|
@ -567,26 +704,27 @@ impl Assembly {
|
|||
// step the assembly along the deformation. this changes the elements'
|
||||
// normalizations, so we restore those afterward
|
||||
/* KLUDGE */
|
||||
// since our test assemblies only include spheres, we assume that every
|
||||
// element is on the 1 mass shell
|
||||
// for now, we only restore the normalizations of spheres
|
||||
for (_, elt) in self.elements.get_clone_untracked() {
|
||||
elt.representation.update_silent(|rep| {
|
||||
match elt.column_index {
|
||||
elt.representation().update_silent(|rep| {
|
||||
match elt.column_index() {
|
||||
Some(column_index) => {
|
||||
// step the assembly along the deformation
|
||||
*rep += motion_proj.column(column_index);
|
||||
|
||||
// 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);
|
||||
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 => {
|
||||
console::log_1(&JsValue::from(
|
||||
format!("No velocity to unpack for fresh element \"{}\"", elt.id)
|
||||
format!("No velocity to unpack for fresh element \"{}\"", elt.id())
|
||||
))
|
||||
}
|
||||
};
|
||||
|
@ -602,20 +740,14 @@ impl Assembly {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::engine;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Element \"sphere\" should be indexed before writing problem data")]
|
||||
#[should_panic(expected = "Sphere \"sphere\" should be indexed before writing problem data")]
|
||||
fn unindexed_element_test() {
|
||||
let _ = create_root(|| {
|
||||
Element::new(
|
||||
"sphere".to_string(),
|
||||
"Sphere".to_string(),
|
||||
[1.0_f32, 1.0_f32, 1.0_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||
).pose(&mut ConstraintProblem::new(1), &Slab::new());
|
||||
let elt = Sphere::default("sphere".to_string(), 0);
|
||||
elt.pose(&mut ConstraintProblem::new(1), &Slab::new());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -623,18 +755,13 @@ mod tests {
|
|||
#[should_panic(expected = "Subjects should be indexed before inversive distance regulator writes problem data")]
|
||||
fn unindexed_subject_test_inversive_distance() {
|
||||
let _ = create_root(|| {
|
||||
let mut elts = Slab::new();
|
||||
let mut elts = Slab::<Rc<dyn Element>>::new();
|
||||
let subjects = [0, 1].map(|k| {
|
||||
elts.insert(
|
||||
Element::new(
|
||||
format!("sphere{k}"),
|
||||
format!("Sphere {k}"),
|
||||
[1.0_f32, 1.0_f32, 1.0_f32],
|
||||
engine::sphere(0.0, 0.0, 0.0, 1.0)
|
||||
)
|
||||
Rc::new(Sphere::default(format!("sphere{k}"), k))
|
||||
)
|
||||
});
|
||||
elts[subjects[0]].column_index = Some(0);
|
||||
elts[subjects[0]].set_column_index(0);
|
||||
InversiveDistanceRegulator {
|
||||
subjects: subjects,
|
||||
measurement: create_memo(|| 0.0),
|
||||
|
|
|
@ -4,17 +4,185 @@ use sycamore::{prelude::*, motion::create_raf};
|
|||
use web_sys::{
|
||||
console,
|
||||
window,
|
||||
Element,
|
||||
KeyboardEvent,
|
||||
MouseEvent,
|
||||
WebGl2RenderingContext,
|
||||
WebGlBuffer,
|
||||
WebGlProgram,
|
||||
WebGlShader,
|
||||
WebGlUniformLocation,
|
||||
wasm_bindgen::{JsCast, JsValue}
|
||||
};
|
||||
|
||||
use crate::{AppState, assembly::{ElementKey, ElementMotion}};
|
||||
use crate::{
|
||||
AppState,
|
||||
assembly::{ElementKey, ElementColor, ElementMotion, Point, Sphere}
|
||||
};
|
||||
|
||||
// --- scene data ---
|
||||
|
||||
struct SceneSpheres {
|
||||
representations: Vec<DVector<f64>>,
|
||||
colors: Vec<ElementColor>,
|
||||
highlights: Vec<f32>
|
||||
}
|
||||
|
||||
impl SceneSpheres {
|
||||
fn new() -> SceneSpheres{
|
||||
SceneSpheres {
|
||||
representations: Vec::new(),
|
||||
colors: Vec::new(),
|
||||
highlights: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn len_i32(&self) -> i32 {
|
||||
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) {
|
||||
self.representations.push(representation);
|
||||
self.colors.push(color);
|
||||
self.highlights.push(highlight);
|
||||
}
|
||||
}
|
||||
|
||||
struct ScenePoints {
|
||||
representations: Vec<DVector<f64>>,
|
||||
colors: Vec<ElementColor>,
|
||||
highlights: Vec<f32>,
|
||||
selections: Vec<f32>
|
||||
}
|
||||
|
||||
impl ScenePoints {
|
||||
fn new() -> ScenePoints {
|
||||
ScenePoints {
|
||||
representations: Vec::new(),
|
||||
colors: Vec::new(),
|
||||
highlights: Vec::new(),
|
||||
selections: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
fn push(&mut self, representation: DVector<f64>, color: ElementColor, highlight: f32, selected: bool) {
|
||||
self.representations.push(representation);
|
||||
self.colors.push(color);
|
||||
self.highlights.push(highlight);
|
||||
self.selections.push(if selected { 1.0 } else { 0.0 });
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Scene {
|
||||
spheres: SceneSpheres,
|
||||
points: ScenePoints
|
||||
}
|
||||
|
||||
impl Scene {
|
||||
fn new() -> Scene {
|
||||
Scene {
|
||||
spheres: SceneSpheres::new(),
|
||||
points: ScenePoints::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DisplayItem {
|
||||
fn show(&self, scene: &mut Scene, selected: bool);
|
||||
|
||||
// the smallest positive depth, represented as a multiple of `dir`, where
|
||||
// the line generated by `dir` hits the element. returns `None` if the line
|
||||
// misses the element
|
||||
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, pixel_size: f64) -> Option<f64>;
|
||||
}
|
||||
|
||||
impl DisplayItem for Sphere {
|
||||
fn show(&self, scene: &mut Scene, selected: bool) {
|
||||
const HIGHLIGHT: f32 = 0.2; /* SCAFFOLDING */
|
||||
let representation = self.representation.get_clone_untracked();
|
||||
let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color };
|
||||
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
||||
scene.spheres.push(representation, color, highlight);
|
||||
}
|
||||
|
||||
// this method should be kept synchronized with `sphere_cast` in
|
||||
// `spheres.frag`, which does essentially the same thing on the GPU side
|
||||
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, _pixel_size: f64) -> Option<f64> {
|
||||
// if `a/b` is less than this threshold, we approximate
|
||||
// `a*u^2 + b*u + c` by the linear function `b*u + c`
|
||||
const DEG_THRESHOLD: f64 = 1e-9;
|
||||
|
||||
let rep = self.representation.with_untracked(|rep| assembly_to_world * rep);
|
||||
let a = -rep[3] * dir.norm_squared();
|
||||
let b = rep.rows_range(..3).dot(&dir);
|
||||
let c = -rep[4];
|
||||
|
||||
let adjust = 4.0*a*c/(b*b);
|
||||
if adjust < 1.0 {
|
||||
// as long as `b` is non-zero, the linear approximation of
|
||||
//
|
||||
// a*u^2 + b*u + c
|
||||
//
|
||||
// at `u = 0` will reach zero at a finite depth `u_lin`. the root of
|
||||
// the quadratic adjacent to `u_lin` is stored in `lin_root`. if
|
||||
// both roots have the same sign, `lin_root` will be the one closer
|
||||
// to `u = 0`
|
||||
let square_rect_ratio = 1.0 + (1.0 - adjust).sqrt();
|
||||
let lin_root = -(2.0*c)/b / square_rect_ratio;
|
||||
if a.abs() > DEG_THRESHOLD * b.abs() {
|
||||
if lin_root > 0.0 {
|
||||
Some(lin_root)
|
||||
} else {
|
||||
let other_root = -b/(2.*a) * square_rect_ratio;
|
||||
(other_root > 0.0).then_some(other_root)
|
||||
}
|
||||
} else {
|
||||
(lin_root > 0.0).then_some(lin_root)
|
||||
}
|
||||
} else {
|
||||
// the line through `dir` misses the sphere completely
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DisplayItem for Point {
|
||||
fn show(&self, scene: &mut Scene, selected: bool) {
|
||||
const HIGHLIGHT: f32 = 0.5; /* SCAFFOLDING */
|
||||
let representation = self.representation.get_clone_untracked();
|
||||
let color = if selected { self.color.map(|channel| 0.2 + 0.8*channel) } else { self.color };
|
||||
let highlight = if selected { 1.0 } else { HIGHLIGHT };
|
||||
scene.points.push(representation, color, highlight, selected);
|
||||
}
|
||||
|
||||
/* SCAFFOLDING */
|
||||
fn cast(&self, dir: Vector3<f64>, assembly_to_world: &DMatrix<f64>, pixel_size: f64) -> Option<f64> {
|
||||
let rep = self.representation.with_untracked(|rep| assembly_to_world * rep);
|
||||
if rep[2] < 0.0 {
|
||||
// this constant should be kept synchronized with `point.frag`
|
||||
const POINT_RADIUS_PX: f64 = 4.0;
|
||||
|
||||
// find the radius of the point in screen projection units
|
||||
let point_radius_proj = POINT_RADIUS_PX * pixel_size;
|
||||
|
||||
// find the squared distance between the screen projections of the
|
||||
// ray and the point
|
||||
let dir_proj = -dir.fixed_rows::<2>(0) / dir[2];
|
||||
let rep_proj = -rep.fixed_rows::<2>(0) / rep[2];
|
||||
let dist_sq = (dir_proj - rep_proj).norm_squared();
|
||||
|
||||
// if the ray hits the point, return its depth
|
||||
if dist_sq < point_radius_proj * point_radius_proj {
|
||||
Some(rep[2] / dir[2])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- WebGL utilities ---
|
||||
|
||||
fn compile_shader(
|
||||
context: &WebGl2RenderingContext,
|
||||
|
@ -27,6 +195,45 @@ fn compile_shader(
|
|||
shader
|
||||
}
|
||||
|
||||
fn set_up_program(
|
||||
context: &WebGl2RenderingContext,
|
||||
vertex_shader_source: &str,
|
||||
fragment_shader_source: &str
|
||||
) -> WebGlProgram {
|
||||
// compile the shaders
|
||||
let vertex_shader = compile_shader(
|
||||
&context,
|
||||
WebGl2RenderingContext::VERTEX_SHADER,
|
||||
vertex_shader_source,
|
||||
);
|
||||
let fragment_shader = compile_shader(
|
||||
&context,
|
||||
WebGl2RenderingContext::FRAGMENT_SHADER,
|
||||
fragment_shader_source,
|
||||
);
|
||||
|
||||
// create the program and attach the shaders
|
||||
let program = context.create_program().unwrap();
|
||||
context.attach_shader(&program, &vertex_shader);
|
||||
context.attach_shader(&program, &fragment_shader);
|
||||
context.link_program(&program);
|
||||
|
||||
/* DEBUG */
|
||||
// report whether linking succeeded
|
||||
let link_status = context
|
||||
.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
|
||||
.as_bool()
|
||||
.unwrap();
|
||||
let link_msg = if link_status {
|
||||
"Linked successfully"
|
||||
} else {
|
||||
"Linking failed"
|
||||
};
|
||||
console::log_1(&JsValue::from(link_msg));
|
||||
|
||||
program
|
||||
}
|
||||
|
||||
fn get_uniform_array_locations<const N: usize>(
|
||||
context: &WebGl2RenderingContext,
|
||||
program: &WebGlProgram,
|
||||
|
@ -42,22 +249,39 @@ fn get_uniform_array_locations<const N: usize>(
|
|||
})
|
||||
}
|
||||
|
||||
// load the given data into the vertex input of the given name
|
||||
fn bind_vertex_attrib(
|
||||
// bind the given vertex buffer object to the given vertex attribute
|
||||
fn bind_to_attribute(
|
||||
context: &WebGl2RenderingContext,
|
||||
index: u32,
|
||||
size: i32,
|
||||
data: &[f32]
|
||||
attr_index: u32,
|
||||
attr_size: i32,
|
||||
buffer: &Option<WebGlBuffer>
|
||||
) {
|
||||
// create a data buffer and bind it to ARRAY_BUFFER
|
||||
let buffer = context.create_buffer().unwrap();
|
||||
context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
|
||||
context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref());
|
||||
context.vertex_attrib_pointer_with_i32(
|
||||
attr_index,
|
||||
attr_size,
|
||||
WebGl2RenderingContext::FLOAT,
|
||||
false, // don't normalize
|
||||
0, // zero stride
|
||||
0, // zero offset
|
||||
);
|
||||
}
|
||||
|
||||
// load the given data into a new vertex buffer object
|
||||
fn load_new_buffer(
|
||||
context: &WebGl2RenderingContext,
|
||||
data: &[f32]
|
||||
) -> Option<WebGlBuffer> {
|
||||
// create a buffer and bind it to ARRAY_BUFFER
|
||||
let buffer = context.create_buffer();
|
||||
context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, buffer.as_ref());
|
||||
|
||||
// load the given data into the buffer. the function `Float32Array::view`
|
||||
// creates a raw view into our module's `WebAssembly.Memory` buffer.
|
||||
// allocating more memory will change the buffer, invalidating the view.
|
||||
// that means we have to make sure we don't allocate any memory until the
|
||||
// view is dropped
|
||||
// load the given data into the buffer. this block is unsafe because
|
||||
// `Float32Array::view` creates a raw view into our module's
|
||||
// `WebAssembly.Memory` buffer. allocating more memory will change the
|
||||
// buffer, invalidating the view, so we have to make sure we don't allocate
|
||||
// any memory until the view is dropped. we're okay here because the view is
|
||||
// used as soon as it's created
|
||||
unsafe {
|
||||
context.buffer_data_with_array_buffer_view(
|
||||
WebGl2RenderingContext::ARRAY_BUFFER,
|
||||
|
@ -66,42 +290,43 @@ fn bind_vertex_attrib(
|
|||
);
|
||||
}
|
||||
|
||||
// allow the target attribute to be used
|
||||
context.enable_vertex_attrib_array(index);
|
||||
|
||||
// take whatever's bound to ARRAY_BUFFER---here, the data buffer created
|
||||
// above---and bind it to the target attribute
|
||||
//
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer
|
||||
//
|
||||
context.vertex_attrib_pointer_with_i32(
|
||||
index,
|
||||
size,
|
||||
WebGl2RenderingContext::FLOAT,
|
||||
false, // don't normalize
|
||||
0, // zero stride
|
||||
0, // zero offset
|
||||
);
|
||||
buffer
|
||||
}
|
||||
|
||||
fn bind_new_buffer_to_attribute(
|
||||
context: &WebGl2RenderingContext,
|
||||
attr_index: u32,
|
||||
attr_size: i32,
|
||||
data: &[f32]
|
||||
) {
|
||||
let buffer = load_new_buffer(context, data);
|
||||
bind_to_attribute(context, attr_index, attr_size, &buffer);
|
||||
}
|
||||
|
||||
// the direction in camera space that a mouse event is pointing along
|
||||
fn event_dir(event: &MouseEvent) -> Vector3<f64> {
|
||||
let target: Element = event.target().unwrap().unchecked_into();
|
||||
fn event_dir(event: &MouseEvent) -> (Vector3<f64>, f64) {
|
||||
let target: web_sys::Element = event.target().unwrap().unchecked_into();
|
||||
let rect = target.get_bounding_client_rect();
|
||||
let width = rect.width();
|
||||
let height = rect.height();
|
||||
let shortdim = width.min(height);
|
||||
|
||||
// this constant should be kept synchronized with `inversive.frag`
|
||||
// this constant should be kept synchronized with `spheres.frag` and
|
||||
// `point.vert`
|
||||
const FOCAL_SLOPE: f64 = 0.3;
|
||||
|
||||
Vector3::new(
|
||||
FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim,
|
||||
FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim,
|
||||
-1.0
|
||||
(
|
||||
Vector3::new(
|
||||
FOCAL_SLOPE * (2.0*(f64::from(event.client_x()) - rect.left()) - width) / shortdim,
|
||||
FOCAL_SLOPE * (2.0*(rect.bottom() - f64::from(event.client_y())) - height) / shortdim,
|
||||
-1.0
|
||||
),
|
||||
FOCAL_SLOPE * 2.0 / shortdim
|
||||
)
|
||||
}
|
||||
|
||||
// --- display component ---
|
||||
|
||||
#[component]
|
||||
pub fn Display() -> View {
|
||||
let state = use_context::<AppState>();
|
||||
|
@ -138,7 +363,7 @@ pub fn Display() -> View {
|
|||
create_effect(move || {
|
||||
state.assembly.elements.with(|elts| {
|
||||
for (_, elt) in elts {
|
||||
elt.representation.track();
|
||||
elt.representation().track();
|
||||
}
|
||||
});
|
||||
state.selection.track();
|
||||
|
@ -170,7 +395,6 @@ pub fn Display() -> View {
|
|||
|
||||
// display parameters
|
||||
const OPACITY: f32 = 0.5; /* SCAFFOLDING */
|
||||
const HIGHLIGHT: f32 = 0.2; /* SCAFFOLDING */
|
||||
const LAYER_THRESHOLD: i32 = 0; /* DEBUG */
|
||||
const DEBUG_MODE: i32 = 0; /* DEBUG */
|
||||
|
||||
|
@ -186,32 +410,26 @@ pub fn Display() -> View {
|
|||
.dyn_into::<WebGl2RenderingContext>()
|
||||
.unwrap();
|
||||
|
||||
// compile and attach the vertex and fragment shaders
|
||||
let vertex_shader = compile_shader(
|
||||
// disable depth testing
|
||||
ctx.disable(WebGl2RenderingContext::DEPTH_TEST);
|
||||
|
||||
// set blend mode
|
||||
ctx.enable(WebGl2RenderingContext::BLEND);
|
||||
ctx.blend_func(WebGl2RenderingContext::SRC_ALPHA, WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// set up the sphere rendering program
|
||||
let sphere_program = set_up_program(
|
||||
&ctx,
|
||||
WebGl2RenderingContext::VERTEX_SHADER,
|
||||
include_str!("identity.vert"),
|
||||
include_str!("spheres.frag")
|
||||
);
|
||||
let fragment_shader = compile_shader(
|
||||
|
||||
// set up the point rendering program
|
||||
let point_program = set_up_program(
|
||||
&ctx,
|
||||
WebGl2RenderingContext::FRAGMENT_SHADER,
|
||||
include_str!("inversive.frag"),
|
||||
include_str!("point.vert"),
|
||||
include_str!("point.frag")
|
||||
);
|
||||
let program = ctx.create_program().unwrap();
|
||||
ctx.attach_shader(&program, &vertex_shader);
|
||||
ctx.attach_shader(&program, &fragment_shader);
|
||||
ctx.link_program(&program);
|
||||
let link_status = ctx
|
||||
.get_program_parameter(&program, WebGl2RenderingContext::LINK_STATUS)
|
||||
.as_bool()
|
||||
.unwrap();
|
||||
let link_msg = if link_status {
|
||||
"Linked successfully"
|
||||
} else {
|
||||
"Linking failed"
|
||||
};
|
||||
console::log_1(&JsValue::from(link_msg));
|
||||
ctx.use_program(Some(&program));
|
||||
|
||||
/* DEBUG */
|
||||
// print the maximum number of vectors that can be passed as
|
||||
|
@ -230,35 +448,33 @@ pub fn Display() -> View {
|
|||
&JsValue::from("uniform vectors available")
|
||||
);
|
||||
|
||||
// find indices of vertex attributes and uniforms
|
||||
// find the sphere program's vertex attribute
|
||||
let viewport_position_attr = ctx.get_attrib_location(&sphere_program, "position") as u32;
|
||||
|
||||
// find the sphere program's uniforms
|
||||
const SPHERE_MAX: usize = 200;
|
||||
let position_index = ctx.get_attrib_location(&program, "position") as u32;
|
||||
let sphere_cnt_loc = ctx.get_uniform_location(&program, "sphere_cnt");
|
||||
let sphere_cnt_loc = ctx.get_uniform_location(&sphere_program, "sphere_cnt");
|
||||
let sphere_sp_locs = get_uniform_array_locations::<SPHERE_MAX>(
|
||||
&ctx, &program, "sphere_list", Some("sp")
|
||||
&ctx, &sphere_program, "sphere_list", Some("sp")
|
||||
);
|
||||
let sphere_lt_locs = get_uniform_array_locations::<SPHERE_MAX>(
|
||||
&ctx, &program, "sphere_list", Some("lt")
|
||||
&ctx, &sphere_program, "sphere_list", Some("lt")
|
||||
);
|
||||
let color_locs = get_uniform_array_locations::<SPHERE_MAX>(
|
||||
&ctx, &program, "color_list", None
|
||||
let sphere_color_locs = get_uniform_array_locations::<SPHERE_MAX>(
|
||||
&ctx, &sphere_program, "color_list", None
|
||||
);
|
||||
let highlight_locs = get_uniform_array_locations::<SPHERE_MAX>(
|
||||
&ctx, &program, "highlight_list", None
|
||||
let sphere_highlight_locs = get_uniform_array_locations::<SPHERE_MAX>(
|
||||
&ctx, &sphere_program, "highlight_list", None
|
||||
);
|
||||
let resolution_loc = ctx.get_uniform_location(&program, "resolution");
|
||||
let shortdim_loc = ctx.get_uniform_location(&program, "shortdim");
|
||||
let opacity_loc = ctx.get_uniform_location(&program, "opacity");
|
||||
let layer_threshold_loc = ctx.get_uniform_location(&program, "layer_threshold");
|
||||
let debug_mode_loc = ctx.get_uniform_location(&program, "debug_mode");
|
||||
let resolution_loc = ctx.get_uniform_location(&sphere_program, "resolution");
|
||||
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 debug_mode_loc = ctx.get_uniform_location(&sphere_program, "debug_mode");
|
||||
|
||||
// create a vertex array and bind it to the graphics context
|
||||
let vertex_array = ctx.create_vertex_array().unwrap();
|
||||
ctx.bind_vertex_array(Some(&vertex_array));
|
||||
|
||||
// set the vertex positions
|
||||
// load the viewport vertex positions into a new vertex buffer object
|
||||
const VERTEX_CNT: usize = 6;
|
||||
let positions: [f32; 3*VERTEX_CNT] = [
|
||||
let viewport_positions: [f32; 3*VERTEX_CNT] = [
|
||||
// northwest triangle
|
||||
-1.0, -1.0, 0.0,
|
||||
-1.0, 1.0, 0.0,
|
||||
|
@ -268,7 +484,13 @@ pub fn Display() -> View {
|
|||
1.0, 1.0, 0.0,
|
||||
1.0, -1.0, 0.0
|
||||
];
|
||||
bind_vertex_attrib(&ctx, position_index, 3, &positions);
|
||||
let viewport_position_buffer = load_new_buffer(&ctx, &viewport_positions);
|
||||
|
||||
// find the point program's vertex attributes
|
||||
let point_position_attr = ctx.get_attrib_location(&point_program, "position") as u32;
|
||||
let point_color_attr = ctx.get_attrib_location(&point_program, "color") as u32;
|
||||
let point_highlight_attr = ctx.get_attrib_location(&point_program, "highlight") as u32;
|
||||
let point_selection_attr = ctx.get_attrib_location(&point_program, "selected") as u32;
|
||||
|
||||
// set up a repainting routine
|
||||
let (_, start_animation_loop, _) = create_raf(move || {
|
||||
|
@ -362,6 +584,9 @@ pub fn Display() -> View {
|
|||
}
|
||||
|
||||
if scene_changed.get() {
|
||||
const SPACE_DIM: usize = 3;
|
||||
const COLOR_SIZE: usize = 3;
|
||||
|
||||
/* INSTRUMENTS */
|
||||
// measure mean frame interval
|
||||
frames_since_last_sample += 1;
|
||||
|
@ -371,6 +596,10 @@ pub fn Display() -> View {
|
|||
frames_since_last_sample = 0;
|
||||
}
|
||||
|
||||
// --- get the assembly ---
|
||||
|
||||
let mut scene = Scene::new();
|
||||
|
||||
// find the map from assembly space to world space
|
||||
let location = {
|
||||
let u = -location_z;
|
||||
|
@ -384,41 +613,27 @@ pub fn Display() -> View {
|
|||
};
|
||||
let asm_to_world = &location * &orientation;
|
||||
|
||||
// get the assembly
|
||||
let (
|
||||
elt_cnt,
|
||||
reps_world,
|
||||
colors,
|
||||
highlights
|
||||
) = state.assembly.elements.with(|elts| {
|
||||
(
|
||||
// number of elements
|
||||
elts.len() as i32,
|
||||
|
||||
// representation vectors in world coordinates
|
||||
elts.iter().map(
|
||||
|(_, elt)| elt.representation.with(|rep| &asm_to_world * rep)
|
||||
).collect::<Vec<_>>(),
|
||||
|
||||
// colors
|
||||
elts.iter().map(|(key, elt)| {
|
||||
if state.selection.with(|sel| sel.contains(&key)) {
|
||||
elt.color.map(|ch| 0.2 + 0.8*ch)
|
||||
} else {
|
||||
elt.color
|
||||
}
|
||||
}).collect::<Vec<_>>(),
|
||||
|
||||
// highlight levels
|
||||
elts.iter().map(|(key, _)| {
|
||||
if state.selection.with(|sel| sel.contains(&key)) {
|
||||
1.0_f32
|
||||
} else {
|
||||
HIGHLIGHT
|
||||
}
|
||||
}).collect::<Vec<_>>()
|
||||
)
|
||||
});
|
||||
// set up the scene
|
||||
state.assembly.elements.with_untracked(
|
||||
|elts| for (key, elt) in elts {
|
||||
let selected = state.selection.with(|sel| sel.contains(&key));
|
||||
elt.show(&mut scene, selected);
|
||||
}
|
||||
);
|
||||
let sphere_cnt = scene.spheres.len_i32();
|
||||
|
||||
// --- draw the spheres ---
|
||||
|
||||
// use the sphere rendering program
|
||||
ctx.use_program(Some(&sphere_program));
|
||||
|
||||
// enable the sphere program's vertex attribute
|
||||
ctx.enable_vertex_attrib_array(viewport_position_attr);
|
||||
|
||||
// write the spheres in world coordinates
|
||||
let sphere_reps_world: Vec<_> = scene.spheres.representations.into_iter().map(
|
||||
|rep| (&asm_to_world * rep).cast::<f32>()
|
||||
).collect();
|
||||
|
||||
// set the resolution
|
||||
let width = canvas.width() as f32;
|
||||
|
@ -426,25 +641,25 @@ pub fn Display() -> View {
|
|||
ctx.uniform2f(resolution_loc.as_ref(), width, height);
|
||||
ctx.uniform1f(shortdim_loc.as_ref(), width.min(height));
|
||||
|
||||
// pass the assembly
|
||||
ctx.uniform1i(sphere_cnt_loc.as_ref(), elt_cnt);
|
||||
for n in 0..reps_world.len() {
|
||||
let v = &reps_world[n];
|
||||
ctx.uniform3f(
|
||||
// pass the scene data
|
||||
ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_cnt);
|
||||
for n in 0..sphere_reps_world.len() {
|
||||
let v = &sphere_reps_world[n];
|
||||
ctx.uniform3fv_with_f32_array(
|
||||
sphere_sp_locs[n].as_ref(),
|
||||
v[0] as f32, v[1] as f32, v[2] as f32
|
||||
v.rows(0, 3).as_slice()
|
||||
);
|
||||
ctx.uniform2f(
|
||||
ctx.uniform2fv_with_f32_array(
|
||||
sphere_lt_locs[n].as_ref(),
|
||||
v[3] as f32, v[4] as f32
|
||||
v.rows(3, 2).as_slice()
|
||||
);
|
||||
ctx.uniform3fv_with_f32_array(
|
||||
color_locs[n].as_ref(),
|
||||
&colors[n]
|
||||
sphere_color_locs[n].as_ref(),
|
||||
&scene.spheres.colors[n]
|
||||
);
|
||||
ctx.uniform1f(
|
||||
highlight_locs[n].as_ref(),
|
||||
highlights[n]
|
||||
sphere_highlight_locs[n].as_ref(),
|
||||
scene.spheres.highlights[n]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -453,9 +668,56 @@ pub fn Display() -> View {
|
|||
ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD);
|
||||
ctx.uniform1i(debug_mode_loc.as_ref(), DEBUG_MODE);
|
||||
|
||||
// bind the viewport vertex position buffer to the position
|
||||
// attribute in the vertex shader
|
||||
bind_to_attribute(&ctx, viewport_position_attr, SPACE_DIM as i32, &viewport_position_buffer);
|
||||
|
||||
// draw the scene
|
||||
ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32);
|
||||
|
||||
// disable the sphere program's vertex attribute
|
||||
ctx.disable_vertex_attrib_array(viewport_position_attr);
|
||||
|
||||
// --- draw the points ---
|
||||
|
||||
if !scene.points.representations.is_empty() {
|
||||
// use the point rendering program
|
||||
ctx.use_program(Some(&point_program));
|
||||
|
||||
// enable the point program's vertex attributes
|
||||
ctx.enable_vertex_attrib_array(point_position_attr);
|
||||
ctx.enable_vertex_attrib_array(point_color_attr);
|
||||
ctx.enable_vertex_attrib_array(point_highlight_attr);
|
||||
ctx.enable_vertex_attrib_array(point_selection_attr);
|
||||
|
||||
// write the points in world coordinates
|
||||
let asm_to_world_sp = asm_to_world.rows(0, SPACE_DIM);
|
||||
let point_positions = DMatrix::from_columns(
|
||||
&scene.points.representations.into_iter().map(
|
||||
|rep| &asm_to_world_sp * rep
|
||||
).collect::<Vec<_>>().as_slice()
|
||||
).cast::<f32>();
|
||||
|
||||
// load the point positions and colors into new buffers and
|
||||
// bind them to the corresponding attributes in the vertex
|
||||
// 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_color_attr, COLOR_SIZE as i32, scene.points.colors.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_selection_attr, 1 as i32, scene.points.selections.as_slice());
|
||||
|
||||
// draw the scene
|
||||
ctx.draw_arrays(WebGl2RenderingContext::POINTS, 0, point_positions.ncols() as i32);
|
||||
|
||||
// disable the point program's vertex attributes
|
||||
ctx.disable_vertex_attrib_array(point_position_attr);
|
||||
ctx.disable_vertex_attrib_array(point_color_attr);
|
||||
ctx.disable_vertex_attrib_array(point_highlight_attr);
|
||||
ctx.disable_vertex_attrib_array(point_selection_attr);
|
||||
}
|
||||
|
||||
// --- update the display state ---
|
||||
|
||||
// update the viewpoint
|
||||
assembly_to_world.set(asm_to_world);
|
||||
|
||||
|
@ -585,11 +847,11 @@ pub fn Display() -> View {
|
|||
},
|
||||
on:click=move |event: MouseEvent| {
|
||||
// find the nearest element along the pointer direction
|
||||
let dir = event_dir(&event);
|
||||
let (dir, pixel_size) = event_dir(&event);
|
||||
console::log_1(&JsValue::from(dir.to_string()));
|
||||
let mut clicked: Option<(ElementKey, f64)> = None;
|
||||
for (key, elt) in state.assembly.elements.get_clone_untracked() {
|
||||
match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world)) {
|
||||
match assembly_to_world.with(|asm_to_world| elt.cast(dir, asm_to_world, pixel_size)) {
|
||||
Some(depth) => match clicked {
|
||||
Some((_, best_depth)) => {
|
||||
if depth < best_depth {
|
||||
|
|
|
@ -4,7 +4,6 @@ use web_sys::{console, wasm_bindgen::JsValue}; /* DEBUG */
|
|||
|
||||
// --- elements ---
|
||||
|
||||
#[cfg(feature = "dev")]
|
||||
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)])
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ use web_sys::{
|
|||
|
||||
use crate::{
|
||||
AppState,
|
||||
assembly,
|
||||
assembly::{
|
||||
Element,
|
||||
ElementKey,
|
||||
ElementRc,
|
||||
HalfCurvatureRegulator,
|
||||
InversiveDistanceRegulator,
|
||||
Regulator,
|
||||
|
@ -103,7 +104,7 @@ impl OutlineItem for InversiveDistanceRegulator {
|
|||
self.subjects[0]
|
||||
};
|
||||
let other_subject_label = state.assembly.elements.with(
|
||||
|elts| elts[other_subject].label.clone()
|
||||
|elts| elts[other_subject].label().clone()
|
||||
);
|
||||
view! {
|
||||
li(class="regulator") {
|
||||
|
@ -141,14 +142,15 @@ fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) ->
|
|||
|
||||
// a list item that shows an element in an outline view of an assembly
|
||||
#[component(inline_props)]
|
||||
fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
||||
fn ElementOutlineItem(key: ElementKey, element: Rc<dyn Element>) -> View {
|
||||
let state = use_context::<AppState>();
|
||||
let class = state.selection.map(
|
||||
move |sel| if sel.contains(&key) { "selected" } else { "" }
|
||||
);
|
||||
let label = element.label.clone();
|
||||
let label = element.label().clone();
|
||||
let representation = element.representation().clone();
|
||||
let rep_components = move || {
|
||||
element.representation.with(
|
||||
representation.with(
|
||||
|rep| rep.iter().map(
|
||||
|u| {
|
||||
let u_str = format!("{:.3}", u).replace("-", "\u{2212}");
|
||||
|
@ -157,8 +159,8 @@ fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View {
|
|||
).collect::<Vec<_>>()
|
||||
)
|
||||
};
|
||||
let regulated = element.regulators.map(|regs| regs.len() > 0);
|
||||
let regulator_list = element.regulators.map(
|
||||
let regulated = element.regulators().map(|regs| regs.len() > 0);
|
||||
let regulator_list = element.regulators().map(
|
||||
move |elt_reg_keys| elt_reg_keys
|
||||
.clone()
|
||||
.into_iter()
|
||||
|
@ -261,7 +263,8 @@ pub fn Outline() -> View {
|
|||
|elts| elts
|
||||
.clone()
|
||||
.into_iter()
|
||||
.sorted_by_key(|(_, elt)| elt.id.clone())
|
||||
.sorted_by_key(|(_, elt)| elt.id().clone())
|
||||
.map(|(key, elt)| (key, ElementRc(elt)))
|
||||
.collect()
|
||||
);
|
||||
|
||||
|
@ -275,10 +278,10 @@ pub fn Outline() -> View {
|
|||
) {
|
||||
Keyed(
|
||||
list=element_list,
|
||||
view=|(key, elt)| view! {
|
||||
view=|(key, ElementRc(elt))| view! {
|
||||
ElementOutlineItem(key=key, element=elt)
|
||||
},
|
||||
key=|(_, elt)| elt.serial
|
||||
key=|(_, ElementRc(elt))| elt.serial()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
18
app-proto/src/point.frag
Normal file
18
app-proto/src/point.frag
Normal file
|
@ -0,0 +1,18 @@
|
|||
#version 300 es
|
||||
|
||||
precision highp float;
|
||||
|
||||
in vec3 point_color;
|
||||
in float point_highlight;
|
||||
in float total_radius;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
float r = total_radius * length(2.*gl_PointCoord - vec2(1.));
|
||||
|
||||
const float POINT_RADIUS = 4.;
|
||||
float border = smoothstep(POINT_RADIUS - 1., POINT_RADIUS, r);
|
||||
vec3 color = mix(point_color, vec3(1.), border * point_highlight);
|
||||
outColor = vec4(color, 1. - smoothstep(total_radius - 1., total_radius, r));
|
||||
}
|
24
app-proto/src/point.vert
Normal file
24
app-proto/src/point.vert
Normal file
|
@ -0,0 +1,24 @@
|
|||
#version 300 es
|
||||
|
||||
in vec4 position;
|
||||
in vec3 color;
|
||||
in float highlight;
|
||||
in float selected;
|
||||
|
||||
out vec3 point_color;
|
||||
out float point_highlight;
|
||||
out float total_radius;
|
||||
|
||||
// camera
|
||||
const float focal_slope = 0.3;
|
||||
|
||||
void main() {
|
||||
total_radius = 5. + 0.5*selected;
|
||||
|
||||
float depth = -focal_slope * position.z;
|
||||
gl_Position = vec4(position.xy / depth, 0., 1.);
|
||||
gl_PointSize = 2.*total_radius;
|
||||
|
||||
point_color = color;
|
||||
point_highlight = highlight;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue
Sorry I am on a mobile device and so will have to make individual comments instead of a single review, polluting your inbox. Anyhow, why are we leaving this css block in place now that it is empty? It is easy to restore in the future if we need it again. Not a fan of "dead code".
Whoops, I forgot to remove those before committing. Removed in commit
35689e3
.