dyna3/app-proto/src/display.rs

603 lines
23 KiB
Rust
Raw Normal View History

use core::array;
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
use nalgebra::{DMatrix, DVector, Rotation3, Vector3};
use sycamore::{prelude::*, motion::create_raf};
use web_sys::{
console,
window,
Element,
KeyboardEvent,
MouseEvent,
WebGl2RenderingContext,
WebGlProgram,
WebGlShader,
WebGlUniformLocation,
wasm_bindgen::{JsCast, JsValue}
};
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
use crate::{AppState, assembly::{ElementKey, ElementMotion}};
fn compile_shader(
context: &WebGl2RenderingContext,
shader_type: u32,
source: &str,
) -> WebGlShader {
let shader = context.create_shader(shader_type).unwrap();
context.shader_source(&shader, source);
context.compile_shader(&shader);
shader
}
fn get_uniform_array_locations<const N: usize>(
context: &WebGl2RenderingContext,
program: &WebGlProgram,
var_name: &str,
member_name_opt: Option<&str>
) -> [Option<WebGlUniformLocation>; N] {
array::from_fn(|n| {
let name = match member_name_opt {
Some(member_name) => format!("{var_name}[{n}].{member_name}"),
None => format!("{var_name}[{n}]")
};
context.get_uniform_location(&program, name.as_str())
})
}
// load the given data into the vertex input of the given name
fn bind_vertex_attrib(
context: &WebGl2RenderingContext,
index: u32,
size: i32,
data: &[f32]
) {
// create a data buffer and bind it to ARRAY_BUFFER
let buffer = context.create_buffer().unwrap();
context.bind_buffer(WebGl2RenderingContext::ARRAY_BUFFER, Some(&buffer));
// 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
unsafe {
context.buffer_data_with_array_buffer_view(
WebGl2RenderingContext::ARRAY_BUFFER,
&js_sys::Float32Array::view(&data),
WebGl2RenderingContext::STATIC_DRAW,
);
}
// 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
);
}
// 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();
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`
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
)
}
#[component]
pub fn Display() -> View {
let state = use_context::<AppState>();
// canvas
let display = create_node_ref();
// viewpoint
let assembly_to_world = create_signal(DMatrix::<f64>::identity(5, 5));
// navigation
let pitch_up = create_signal(0.0);
let pitch_down = create_signal(0.0);
let yaw_right = create_signal(0.0);
let yaw_left = create_signal(0.0);
let roll_ccw = create_signal(0.0);
let roll_cw = create_signal(0.0);
let zoom_in = create_signal(0.0);
let zoom_out = create_signal(0.0);
let turntable = create_signal(false); /* BENCHMARKING */
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// manipulation
let translate_neg_x = create_signal(0.0);
let translate_pos_x = create_signal(0.0);
let translate_neg_y = create_signal(0.0);
let translate_pos_y = create_signal(0.0);
let translate_neg_z = create_signal(0.0);
let translate_pos_z = create_signal(0.0);
// change listener
let scene_changed = create_signal(true);
create_effect(move || {
state.assembly.elements.with(|elts| {
for (_, elt) in elts {
elt.representation.track();
}
});
state.selection.track();
scene_changed.set(true);
});
/* INSTRUMENTS */
const SAMPLE_PERIOD: i32 = 60;
let mut last_sample_time = 0.0;
let mut frames_since_last_sample = 0;
let mean_frame_interval = create_signal(0.0);
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
let assembly_for_raf = state.assembly.clone();
on_mount(move || {
// timing
let mut last_time = 0.0;
// viewpoint
const ROT_SPEED: f64 = 0.4; // in radians per second
const ZOOM_SPEED: f64 = 0.15; // multiplicative rate per second
const TURNTABLE_SPEED: f64 = 0.1; /* BENCHMARKING */
let mut orientation = DMatrix::<f64>::identity(5, 5);
let mut rotation = DMatrix::<f64>::identity(5, 5);
let mut location_z: f64 = 5.0;
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// manipulation
const TRANSLATION_SPEED: f64 = 0.15; // in length units per second
// 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 */
/* INSTRUMENTS */
let performance = window().unwrap().performance().unwrap();
// get the display canvas
let canvas = display.get().unchecked_into::<web_sys::HtmlCanvasElement>();
let ctx = canvas
.get_context("webgl2")
.unwrap()
.unwrap()
.dyn_into::<WebGl2RenderingContext>()
.unwrap();
// compile and attach the vertex and fragment shaders
let vertex_shader = compile_shader(
&ctx,
WebGl2RenderingContext::VERTEX_SHADER,
include_str!("identity.vert"),
);
let fragment_shader = compile_shader(
&ctx,
WebGl2RenderingContext::FRAGMENT_SHADER,
include_str!("inversive.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
// uniforms to a fragment shader. the OpenGL ES 3.0 standard
// requires this maximum to be at least 224, as discussed in the
// documentation of the GL_MAX_FRAGMENT_UNIFORM_VECTORS parameter
// here:
//
// https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glGet.xhtml
//
// there are also other size limits. for example, on Aaron's
// machine, the the length of a float or genType array seems to be
// capped at 1024 elements
console::log_2(
&ctx.get_parameter(WebGl2RenderingContext::MAX_FRAGMENT_UNIFORM_VECTORS).unwrap(),
&JsValue::from("uniform vectors available")
);
// find indices of vertex attributes and 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_sp_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &program, "sphere_list", Some("sp")
);
let sphere_lt_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &program, "sphere_list", Some("lt")
);
let color_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &program, "color_list", None
);
let highlight_locs = get_uniform_array_locations::<SPHERE_MAX>(
&ctx, &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");
// 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
const VERTEX_CNT: usize = 6;
let positions: [f32; 3*VERTEX_CNT] = [
// northwest triangle
-1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, 1.0, 0.0,
// southeast triangle
-1.0, -1.0, 0.0,
1.0, 1.0, 0.0,
1.0, -1.0, 0.0
];
bind_vertex_attrib(&ctx, position_index, 3, &positions);
// set up a repainting routine
let (_, start_animation_loop, _) = create_raf(move || {
// get the time step
let time = performance.now();
let time_step = 0.001*(time - last_time);
last_time = time;
// get the navigation state
let pitch_up_val = pitch_up.get();
let pitch_down_val = pitch_down.get();
let yaw_right_val = yaw_right.get();
let yaw_left_val = yaw_left.get();
let roll_ccw_val = roll_ccw.get();
let roll_cw_val = roll_cw.get();
let zoom_in_val = zoom_in.get();
let zoom_out_val = zoom_out.get();
let turntable_val = turntable.get(); /* BENCHMARKING */
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// get the manipulation state
let translate_neg_x_val = translate_neg_x.get();
let translate_pos_x_val = translate_pos_x.get();
let translate_neg_y_val = translate_neg_y.get();
let translate_pos_y_val = translate_pos_y.get();
let translate_neg_z_val = translate_neg_z.get();
let translate_pos_z_val = translate_pos_z.get();
// update the assembly's orientation
let ang_vel = {
let pitch = pitch_up_val - pitch_down_val;
let yaw = yaw_right_val - yaw_left_val;
let roll = roll_ccw_val - roll_cw_val;
if pitch != 0.0 || yaw != 0.0 || roll != 0.0 {
ROT_SPEED * Vector3::new(-pitch, yaw, roll).normalize()
} else {
Vector3::zeros()
}
} /* BENCHMARKING */ + if turntable_val {
Vector3::new(0.0, TURNTABLE_SPEED, 0.0)
} else {
Vector3::zeros()
};
let mut rotation_sp = rotation.fixed_view_mut::<3, 3>(0, 0);
rotation_sp.copy_from(
Rotation3::from_scaled_axis(time_step * ang_vel).matrix()
);
orientation = &rotation * &orientation;
// update the assembly's location
let zoom = zoom_out_val - zoom_in_val;
location_z *= (time_step * ZOOM_SPEED * zoom).exp();
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// manipulate the assembly
if state.selection.with(|sel| sel.len() == 1) {
let sel = state.selection.with(
|sel| *sel.into_iter().next().unwrap()
);
let rep = state.assembly.elements.with_untracked(
|elts| elts[sel].representation.get_clone_untracked()
);
let translate_x = translate_pos_x_val - translate_neg_x_val;
let translate_y = translate_pos_y_val - translate_neg_y_val;
let translate_z = translate_pos_z_val - translate_neg_z_val;
if translate_x != 0.0 || translate_y != 0.0 || translate_z != 0.0 {
let vel_field = {
let u = Vector3::new(translate_x, translate_y, translate_z).normalize();
DMatrix::from_column_slice(5, 5, &[
0.0, 0.0, 0.0, 0.0, u[0],
0.0, 0.0, 0.0, 0.0, u[1],
0.0, 0.0, 0.0, 0.0, u[2],
2.0*u[0], 2.0*u[1], 2.0*u[2], 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0
])
};
let elt_motion: DVector<f64> = time_step * TRANSLATION_SPEED * vel_field * rep;
assembly_for_raf.deform(
vec![
ElementMotion {
key: sel,
velocity: elt_motion.as_view()
}
]
);
scene_changed.set(true);
}
}
if scene_changed.get() {
/* INSTRUMENTS */
// measure mean frame interval
frames_since_last_sample += 1;
if frames_since_last_sample >= SAMPLE_PERIOD {
mean_frame_interval.set((time - last_sample_time) / (SAMPLE_PERIOD as f64));
last_sample_time = time;
frames_since_last_sample = 0;
}
// find the map from assembly space to world space
let location = {
let u = -location_z;
DMatrix::from_column_slice(5, 5, &[
1.0, 0.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0, u,
0.0, 0.0, 2.0*u, 1.0, u*u,
0.0, 0.0, 0.0, 0.0, 1.0
])
};
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 the resolution
let width = canvas.width() as f32;
let height = canvas.height() as f32;
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(
sphere_sp_locs[n].as_ref(),
v[0] as f32, v[1] as f32, v[2] as f32
);
ctx.uniform2f(
sphere_lt_locs[n].as_ref(),
v[3] as f32, v[4] as f32
);
ctx.uniform3fv_with_f32_array(
color_locs[n].as_ref(),
&colors[n]
);
ctx.uniform1f(
highlight_locs[n].as_ref(),
highlights[n]
);
}
// pass the display parameters
ctx.uniform1f(opacity_loc.as_ref(), OPACITY);
ctx.uniform1i(layer_threshold_loc.as_ref(), LAYER_THRESHOLD);
ctx.uniform1i(debug_mode_loc.as_ref(), DEBUG_MODE);
// draw the scene
ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32);
// update the viewpoint
assembly_to_world.set(asm_to_world);
// clear the scene change flag
scene_changed.set(
pitch_up_val != 0.0
|| pitch_down_val != 0.0
|| yaw_left_val != 0.0
|| yaw_right_val != 0.0
|| roll_cw_val != 0.0
|| roll_ccw_val != 0.0
|| zoom_in_val != 0.0
|| zoom_out_val != 0.0
|| turntable_val /* BENCHMARKING */
);
} else {
frames_since_last_sample = 0;
mean_frame_interval.set(-1.0);
}
});
start_animation_loop();
});
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
let set_nav_signal = move |event: &KeyboardEvent, value: f64| {
let mut navigating = true;
let shift = event.shift_key();
match event.key().as_str() {
"ArrowUp" if shift => zoom_in.set(value),
"ArrowDown" if shift => zoom_out.set(value),
"ArrowUp" => pitch_up.set(value),
"ArrowDown" => pitch_down.set(value),
"ArrowRight" if shift => roll_cw.set(value),
"ArrowLeft" if shift => roll_ccw.set(value),
"ArrowRight" => yaw_right.set(value),
"ArrowLeft" => yaw_left.set(value),
_ => navigating = false
};
if navigating {
scene_changed.set(true);
event.prevent_default();
}
};
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
let set_manip_signal = move |event: &KeyboardEvent, value: f64| {
let mut manipulating = true;
let shift = event.shift_key();
match event.key().as_str() {
"d" | "D" => translate_pos_x.set(value),
"a" | "A" => translate_neg_x.set(value),
"w" | "W" if shift => translate_neg_z.set(value),
"s" | "S" if shift => translate_pos_z.set(value),
"w" | "W" => translate_pos_y.set(value),
"s" | "S" => translate_neg_y.set(value),
_ => manipulating = false
};
if manipulating {
event.prevent_default();
}
};
view! {
/* TO DO */
// switch back to integer-valued parameters when that becomes possible
// again
canvas(
ref=display,
width="600",
height="600",
tabindex="0",
on:keydown=move |event: KeyboardEvent| {
if event.key() == "Shift" {
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// swap navigation inputs
roll_cw.set(yaw_right.get());
roll_ccw.set(yaw_left.get());
zoom_in.set(pitch_up.get());
zoom_out.set(pitch_down.get());
yaw_right.set(0.0);
yaw_left.set(0.0);
pitch_up.set(0.0);
pitch_down.set(0.0);
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// swap manipulation inputs
translate_pos_z.set(translate_neg_y.get());
translate_neg_z.set(translate_pos_y.get());
translate_pos_y.set(0.0);
translate_neg_y.set(0.0);
} else {
if event.key() == "Enter" { /* BENCHMARKING */
turntable.set_fn(|turn| !turn);
scene_changed.set(true);
}
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
set_nav_signal(&event, 1.0);
set_manip_signal(&event, 1.0);
}
},
on:keyup=move |event: KeyboardEvent| {
if event.key() == "Shift" {
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// swap navigation inputs
yaw_right.set(roll_cw.get());
yaw_left.set(roll_ccw.get());
pitch_up.set(zoom_in.get());
pitch_down.set(zoom_out.get());
roll_cw.set(0.0);
roll_ccw.set(0.0);
zoom_in.set(0.0);
zoom_out.set(0.0);
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
// swap manipulation inputs
translate_pos_y.set(translate_neg_z.get());
translate_neg_y.set(translate_pos_z.get());
translate_pos_z.set(0.0);
translate_neg_z.set(0.0);
} else {
Manipulate the assembly (#29) feat: Find tangent space of solution variety, use for perturbations ### Tangent space #### Implementation The structure `engine::ConfigSubspace` represents a subspace of the configuration vector space $\operatorname{Hom}(\mathbb{R}^n, \mathbb{R}^5)$. It holds a basis for the subspace which is orthonormal with respect to the Euclidean inner product. The method `ConfigSubspace::symmetric_kernel` takes an endomorphism of the configuration vector space, which must be symmetric with respect to the Euclidean inner product, and returns its approximate kernel in the form of a `ConfigSubspace`. At the end of `engine::realize_gram`, we use the computed Hessian to find the tangent space of the solution variety, and we return it alongside the realization. Since altering the constraints can change the tangent space without changing the solution, we compute the tangent space even when the guess passed to the realization routine is already a solution. After `Assembly::realize` calls `engine::realize_gram`, it saves the returned tangent space in the assembly's `tangent` signal. The basis vectors are stored in configuration matrix format, ordered according to the elements' column indices. To help maintain consistency between the storage layout of the tangent space and the elements' column indices, we switch the column index data type from `usize` to `Option<usize>` and enforce the following invariants: 1. If an element has a column index, its tangent motions can be found in that column of the tangent space basis matrices. 2. If an element is affected by a constraint, it has a column index. The comments in `assembly.rs` state the invariants and describe how they're enforced. #### Automated testing The test `engine::tests::tangent_test` builds a simple assembly with a known tangent space, runs the realization routine, and checks the returned tangent space against a hand-computed basis. #### Limitations The method `ConfigSubspace::symmetric_kernel` approximates the kernel by taking all the eigenspaces whose eigenvalues are smaller than a hard-coded threshold size. We may need a more flexible system eventually. ### Deformation #### Implementation The main purpose of this implementation is to confirm that deformation works as we'd hoped. The code is messy, and the deformation routine has at least one numerical quirk. For simplicity, the keyboard commands that manipulate the assembly are handled by the display, just like the keyboard commands that control the camera. Deformation happens at the beginning of the animation loop. The function `Assembly::deform` works like this: 1. Take a list of element motions 2. Project them onto the tangent space of the solution variety 3. Sum them to get a deformation $v$ of the whole assembly 4. Step the assembly along the "mass shell" geodesic tangent to $v$ * This step stays on the solution variety to first order 5. Call `realize` to bring the assembly back onto the solution variety #### Manual testing To manipulate the assembly: 1. Select a sphere 2. Make sure the display has focus 3. Hold the following keys: * **A**/**D** for $x$ translation * **W**/**S** for $y$ translation * **shift**+**W**/**S** for $z$ translation #### Limitations Because the manipulation commands are handled by the display, you can only manipulate the assembly when the display has focus. Since our test assemblies only include spheres, we assume in `Assembly::deform` that every element is a sphere. When the tangent space is zero, `Assembly::deform` does nothing except print "The assembly is rigid" to the console. During a deformation, the curvature and co-curvature components of a sphere's vector representation can exhibit weird discontinuous "swaps" that don't visibly affect how the sphere is drawn. *[I'll write more about this in an issue.]* Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: https://code.studioinfinity.org/glen/dyna3/pulls/29 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
2024-12-30 22:53:07 +00:00
set_nav_signal(&event, 0.0);
set_manip_signal(&event, 0.0);
}
},
on:blur=move |_| {
pitch_up.set(0.0);
pitch_down.set(0.0);
yaw_right.set(0.0);
yaw_left.set(0.0);
roll_ccw.set(0.0);
roll_cw.set(0.0);
},
on:click=move |event: MouseEvent| {
// find the nearest element along the pointer direction
let dir = 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)) {
Some(depth) => match clicked {
Some((_, best_depth)) => {
if depth < best_depth {
clicked = Some((key, depth))
}
},
None => clicked = Some((key, depth))
}
None => ()
};
}
// if we clicked something, select it
match clicked {
Some((key, _)) => state.select(key, event.shift_key()),
None => state.selection.update(|sel| sel.clear())
};
}
)
}
}