forked from glen/dyna3

Remove GPU code and uniforms that were used as scaffolding during initial development, but have now been replaced by CPU analogues.
744 lines
No EOL
29 KiB
Rust
744 lines
No EOL
29 KiB
Rust
// based on the WebGL example in the `wasm-bindgen` guide
|
|
//
|
|
// https://rustwasm.github.io/wasm-bindgen/examples/webgl.html
|
|
//
|
|
// and this StackOverflow answer by wangdq
|
|
//
|
|
// https://stackoverflow.com/a/39684775
|
|
//
|
|
|
|
use core::array;
|
|
use nalgebra::{DMatrix, DVector, Rotation3, Vector3};
|
|
use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}};
|
|
use web_sys::{
|
|
console,
|
|
window,
|
|
KeyboardEvent,
|
|
WebGl2RenderingContext,
|
|
WebGlProgram,
|
|
WebGlShader,
|
|
WebGlUniformLocation
|
|
};
|
|
|
|
mod engine;
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
fn push_gen_construction(
|
|
sphere_vec: &mut Vec<DVector<f64>>,
|
|
color_vec: &mut Vec<[f32; 3]>,
|
|
construction_to_world: &DMatrix<f64>,
|
|
ctrl_x: f64,
|
|
ctrl_y: f64,
|
|
radius_x: f64,
|
|
radius_y: f64
|
|
) {
|
|
// push spheres
|
|
sphere_vec.push(construction_to_world * engine::sphere(0.5, 0.5, ctrl_x, radius_x));
|
|
sphere_vec.push(construction_to_world * engine::sphere(-0.5, -0.5, ctrl_y, radius_y));
|
|
sphere_vec.push(construction_to_world * engine::sphere(-0.5, 0.5, 0.0, 0.75));
|
|
sphere_vec.push(construction_to_world * engine::sphere(0.5, -0.5, 0.0, 0.5));
|
|
sphere_vec.push(construction_to_world * engine::sphere(0.0, 0.15, 1.0, 0.25));
|
|
sphere_vec.push(construction_to_world * engine::sphere(0.0, -0.15, -1.0, 0.25));
|
|
|
|
// push colors
|
|
color_vec.push([1.00_f32, 0.25_f32, 0.00_f32]);
|
|
color_vec.push([0.00_f32, 0.25_f32, 1.00_f32]);
|
|
color_vec.push([0.25_f32, 0.00_f32, 1.00_f32]);
|
|
color_vec.push([0.25_f32, 1.00_f32, 0.00_f32]);
|
|
color_vec.push([0.75_f32, 0.75_f32, 0.00_f32]);
|
|
color_vec.push([0.00_f32, 0.75_f32, 0.50_f32]);
|
|
}
|
|
|
|
fn push_low_curv_construction(
|
|
sphere_vec: &mut Vec<DVector<f64>>,
|
|
color_vec: &mut Vec<[f32; 3]>,
|
|
construction_to_world: &DMatrix<f64>,
|
|
off1: f64,
|
|
off2: f64,
|
|
off3: f64,
|
|
curv1: f64,
|
|
curv2: f64,
|
|
curv3: f64,
|
|
) {
|
|
// push spheres
|
|
let a = 0.75_f64.sqrt();
|
|
sphere_vec.push(construction_to_world * engine::sphere(0.0, 0.0, 0.0, 1.0));
|
|
sphere_vec.push(construction_to_world * engine::sphere_with_offset(0.0, 0.0, 1.0, 0.0, 0.0));
|
|
sphere_vec.push(construction_to_world * engine::sphere_with_offset(1.0, 0.0, 0.0, off1, curv1));
|
|
sphere_vec.push(construction_to_world * engine::sphere_with_offset(-0.5, a, 0.0, off2, curv2));
|
|
sphere_vec.push(construction_to_world * engine::sphere_with_offset(-0.5, -a, 0.0, off3, curv3));
|
|
sphere_vec.push(construction_to_world * engine::sphere(-4.0/3.0, 0.0, 0.0, 1.0/3.0));
|
|
sphere_vec.push(construction_to_world * engine::sphere(2.0/3.0, -4.0/3.0 * a, 0.0, 1.0/3.0));
|
|
sphere_vec.push(construction_to_world * engine::sphere(2.0/3.0, 4.0/3.0 * a, 0.0, 1.0/3.0));
|
|
|
|
// push colors
|
|
color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]);
|
|
color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]);
|
|
color_vec.push([1.00_f32, 0.00_f32, 0.25_f32]);
|
|
color_vec.push([0.25_f32, 1.00_f32, 0.00_f32]);
|
|
color_vec.push([0.00_f32, 0.25_f32, 1.00_f32]);
|
|
color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]);
|
|
color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]);
|
|
color_vec.push([0.75_f32, 0.75_f32, 0.75_f32]);
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
enum Tab {
|
|
GenTab,
|
|
LowCurvTab
|
|
}
|
|
|
|
fn main() {
|
|
// set up a config option that forwards panic messages to `console.error`
|
|
#[cfg(feature = "console_error_panic_hook")]
|
|
console_error_panic_hook::set_once();
|
|
|
|
sycamore::render(|| {
|
|
// tab selection
|
|
let tab_selection = create_signal(Tab::GenTab);
|
|
|
|
// 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);
|
|
|
|
// controls for general example
|
|
let gen_controls = create_node_ref();
|
|
let ctrl_x = create_signal(0.0);
|
|
let ctrl_y = create_signal(0.0);
|
|
let radius_x = create_signal(1.0);
|
|
let radius_y = create_signal(1.0);
|
|
|
|
// controls for low-curvature example
|
|
let low_curv_controls = create_node_ref();
|
|
let curv1 = create_signal(0.0);
|
|
let curv2 = create_signal(0.0);
|
|
let curv3 = create_signal(0.0);
|
|
let off1 = create_signal(1.0);
|
|
let off2 = create_signal(1.0);
|
|
let off3 = create_signal(1.0);
|
|
|
|
// shared controls
|
|
let opacity = create_signal(0.5);
|
|
let highlight = create_signal(0.2);
|
|
let turntable = create_signal(false);
|
|
let layer_threshold = create_signal(0.0); /* DEBUG */
|
|
let debug_mode = create_signal(false); /* DEBUG */
|
|
|
|
/* 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);
|
|
|
|
// display
|
|
let display = create_node_ref();
|
|
|
|
// change listener
|
|
let scene_changed = create_signal(true);
|
|
create_effect(move || {
|
|
// track tab selection
|
|
tab_selection.track();
|
|
|
|
// track controls for general example
|
|
ctrl_x.track();
|
|
ctrl_y.track();
|
|
radius_x.track();
|
|
radius_y.track();
|
|
|
|
// track controls for low-curvature example
|
|
curv1.track();
|
|
curv2.track();
|
|
curv3.track();
|
|
off1.track();
|
|
off2.track();
|
|
off3.track();
|
|
|
|
// track shared controls
|
|
opacity.track();
|
|
highlight.track();
|
|
turntable.track();
|
|
layer_threshold.track();
|
|
debug_mode.track();
|
|
|
|
scene_changed.set(true);
|
|
});
|
|
|
|
on_mount(move || {
|
|
// tab listener
|
|
create_effect(move || {
|
|
// get the control panel nodes
|
|
let gen_controls_node = gen_controls.get::<DomNode>();
|
|
let low_curv_controls_node = low_curv_controls.get::<DomNode>();
|
|
|
|
// hide all the control panels
|
|
gen_controls_node.add_class("hidden");
|
|
low_curv_controls_node.add_class("hidden");
|
|
|
|
// show the selected control panel
|
|
match tab_selection.get() {
|
|
Tab::GenTab => gen_controls_node.remove_class("hidden"),
|
|
Tab::LowCurvTab => low_curv_controls_node.remove_class("hidden")
|
|
}
|
|
});
|
|
|
|
// create list of construction elements
|
|
const SPHERE_MAX: usize = 200;
|
|
let mut sphere_vec = Vec::<DVector<f64>>::new();
|
|
let mut color_vec = Vec::<[f32; 3]>::new();
|
|
|
|
// timing
|
|
let mut last_time = 0.0;
|
|
|
|
// scene parameters
|
|
const ROT_SPEED: f64 = 0.4; // in radians per second
|
|
const TURNTABLE_SPEED: f64 = 0.1; // in radians per second
|
|
const ZOOM_SPEED: f64 = 0.15;
|
|
let mut orientation = DMatrix::<f64>::identity(5, 5);
|
|
let mut rotation = DMatrix::<f64>::identity(5, 5);
|
|
let mut location_z: f64 = 5.0;
|
|
|
|
/* INSTRUMENTS */
|
|
let performance = window().unwrap().performance().unwrap();
|
|
|
|
// get the display canvas
|
|
let canvas = display
|
|
.get::<DomNode>()
|
|
.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
|
|
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 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 highlight_loc = ctx.get_uniform_location(&program, "highlight");
|
|
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();
|
|
|
|
// update the construction'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;
|
|
let ang_vel_from_keyboard =
|
|
if pitch != 0.0 || yaw != 0.0 || roll != 0.0 {
|
|
ROT_SPEED * Vector3::new(-pitch, yaw, roll).normalize()
|
|
} else {
|
|
Vector3::zeros()
|
|
};
|
|
let ang_vel_from_turntable =
|
|
if turntable_val {
|
|
Vector3::new(0.0, TURNTABLE_SPEED, 0.0)
|
|
} else {
|
|
Vector3::zeros()
|
|
};
|
|
ang_vel_from_keyboard + ang_vel_from_turntable
|
|
};
|
|
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 construction's location
|
|
let zoom = zoom_out_val - zoom_in_val;
|
|
location_z *= (time_step * ZOOM_SPEED * zoom).exp();
|
|
|
|
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 construction 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 construction_to_world = &location * &orientation;
|
|
|
|
// update the construction
|
|
sphere_vec.clear();
|
|
color_vec.clear();
|
|
match tab_selection.get() {
|
|
Tab::GenTab => push_gen_construction(
|
|
&mut sphere_vec,
|
|
&mut color_vec,
|
|
&construction_to_world,
|
|
ctrl_x.get(), ctrl_y.get(),
|
|
radius_x.get(), radius_y.get()
|
|
),
|
|
Tab::LowCurvTab => push_low_curv_construction(
|
|
&mut sphere_vec,
|
|
&mut color_vec,
|
|
&construction_to_world,
|
|
off1.get(), off2.get(), off3.get(),
|
|
curv1.get(), curv2.get(), curv3.get(),
|
|
)
|
|
};
|
|
|
|
// 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 construction
|
|
ctx.uniform1i(sphere_cnt_loc.as_ref(), sphere_vec.len() as i32);
|
|
for n in 0..sphere_vec.len() {
|
|
let v = &sphere_vec[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(),
|
|
&color_vec[n]
|
|
);
|
|
}
|
|
|
|
// pass the control parameters
|
|
ctx.uniform1f(opacity_loc.as_ref(), opacity.get() as f32);
|
|
ctx.uniform1f(highlight_loc.as_ref(), highlight.get() as f32);
|
|
ctx.uniform1i(layer_threshold_loc.as_ref(), layer_threshold.get() as i32);
|
|
ctx.uniform1i(debug_mode_loc.as_ref(), debug_mode.get() as i32);
|
|
|
|
// draw the scene
|
|
ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32);
|
|
|
|
// clear 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
|
|
);
|
|
} else {
|
|
frames_since_last_sample = 0;
|
|
mean_frame_interval.set(-1.0);
|
|
}
|
|
});
|
|
start_animation_loop();
|
|
});
|
|
|
|
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();
|
|
}
|
|
};
|
|
|
|
view! {
|
|
div(id="app") {
|
|
div(class="tab-pane") {
|
|
label {
|
|
"General"
|
|
input(
|
|
type="radio",
|
|
name="tab",
|
|
prop:checked=tab_selection.get() == Tab::GenTab,
|
|
on:click=move |_| tab_selection.set(Tab::GenTab)
|
|
)
|
|
}
|
|
label {
|
|
"Low curvature"
|
|
input(
|
|
type="radio",
|
|
name="tab",
|
|
prop:checked=tab_selection.get() == Tab::LowCurvTab,
|
|
on:change=move |_| tab_selection.set(Tab::LowCurvTab)
|
|
)
|
|
}
|
|
}
|
|
div { "Mean frame interval: " (mean_frame_interval.get()) " ms" }
|
|
canvas(
|
|
ref=display,
|
|
width=600,
|
|
height=600,
|
|
tabindex=0,
|
|
on:keydown=move |event: KeyboardEvent| {
|
|
if event.key() == "Shift" {
|
|
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);
|
|
} else {
|
|
set_nav_signal(event, 1.0);
|
|
}
|
|
},
|
|
on:keyup=move |event: KeyboardEvent| {
|
|
if event.key() == "Shift" {
|
|
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);
|
|
} else {
|
|
set_nav_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);
|
|
}
|
|
)
|
|
div(ref=gen_controls) {
|
|
label(class="control") {
|
|
span { "Sphere 0 depth" }
|
|
input(
|
|
type="range",
|
|
min=-1.0,
|
|
max=1.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=ctrl_x
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 1 depth" }
|
|
input(
|
|
type="range",
|
|
min=-1.0,
|
|
max=1.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=ctrl_y
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 0 radius" }
|
|
input(
|
|
type="range",
|
|
min=0.5,
|
|
max=1.5,
|
|
step=0.001,
|
|
bind:valueAsNumber=radius_x
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 1 radius" }
|
|
input(
|
|
type="range",
|
|
min=0.5,
|
|
max=1.5,
|
|
step=0.001,
|
|
bind:valueAsNumber=radius_y
|
|
)
|
|
}
|
|
}
|
|
div(ref=low_curv_controls) {
|
|
label(class="control") {
|
|
span { "Sphere 1 offset" }
|
|
input(
|
|
type="range",
|
|
min=-1.0,
|
|
max=1.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=off1
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 2 offset" }
|
|
input(
|
|
type="range",
|
|
min=-1.0,
|
|
max=1.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=off2
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 3 offset" }
|
|
input(
|
|
type="range",
|
|
min=-1.0,
|
|
max=1.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=off3
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 1 curvature" }
|
|
input(
|
|
type="range",
|
|
min=0.0,
|
|
max=2.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=curv1
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 2 curvature" }
|
|
input(
|
|
type="range",
|
|
min=0.0,
|
|
max=2.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=curv2
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Sphere 3 curvature" }
|
|
input(
|
|
type="range",
|
|
min=0.0,
|
|
max=2.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=curv3
|
|
)
|
|
}
|
|
}
|
|
label(class="control") {
|
|
span { "Opacity" }
|
|
input(
|
|
type="range",
|
|
max=1.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=opacity
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Highlight" }
|
|
input(
|
|
type="range",
|
|
max=1.0,
|
|
step=0.001,
|
|
bind:valueAsNumber=highlight
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Turntable" }
|
|
input(
|
|
type="checkbox",
|
|
bind:checked=turntable
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Layer threshold" }
|
|
input(
|
|
type="range",
|
|
max=5.0,
|
|
step=1.0,
|
|
bind:valueAsNumber=layer_threshold
|
|
)
|
|
}
|
|
label(class="control") {
|
|
span { "Debug mode" }
|
|
input(
|
|
type="checkbox",
|
|
bind:checked=debug_mode
|
|
)
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} |