forked from StudioInfinity/dyna3
feat: Points (#82)
Replaces the former sole Element entity by two, Sphere and Point, both implementing an Element trait. Adds Point display, uses the former Element display for Sphere. Adds a new "canned" configuration, and the ability to add, select, and nudge Point entities. Co-authored-by: Aaron Fenyes <aaron.fenyes@fareycircles.ooo> Reviewed-on: StudioInfinity/dyna3#82 Co-authored-by: Vectornaut <vectornaut@nobody@nowhere.net> Co-committed-by: Vectornaut <vectornaut@nobody@nowhere.net>
This commit is contained in:
parent
360ce12d8b
commit
a2478febc1
9 changed files with 815 additions and 330 deletions
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue