Compare commits
12 Commits
5e9c5db231
...
f148552964
Author | SHA1 | Date | |
---|---|---|---|
|
f148552964 | ||
|
6db9f5be6c | ||
|
3a721a4cc8 | ||
|
8fde202911 | ||
|
4afc82034b | ||
|
e80adf831d | ||
|
3d7ee98dd6 | ||
|
c04e29f586 | ||
|
01c2af6615 | ||
|
a40a110788 | ||
|
f62f44b5a7 | ||
|
ec48592ef1 |
@ -22,6 +22,7 @@ console_error_panic_hook = { version = "0.1.7", optional = true }
|
|||||||
version = "0.3.69"
|
version = "0.3.69"
|
||||||
features = [
|
features = [
|
||||||
'HtmlCanvasElement',
|
'HtmlCanvasElement',
|
||||||
|
'Performance',
|
||||||
'WebGl2RenderingContext',
|
'WebGl2RenderingContext',
|
||||||
'WebGlBuffer',
|
'WebGlBuffer',
|
||||||
'WebGlProgram',
|
'WebGlProgram',
|
||||||
|
@ -25,7 +25,7 @@ vecInv sphere(vec3 center, float radius) {
|
|||||||
|
|
||||||
// construction. the SPHERE_MAX array size seems to affect frame rate a lot,
|
// construction. the SPHERE_MAX array size seems to affect frame rate a lot,
|
||||||
// even though we should only be using the first few elements of each array
|
// even though we should only be using the first few elements of each array
|
||||||
const int SPHERE_MAX = 12;
|
const int SPHERE_MAX = 200;
|
||||||
uniform int sphere_cnt;
|
uniform int sphere_cnt;
|
||||||
uniform vecInv sphere_list[SPHERE_MAX];
|
uniform vecInv sphere_list[SPHERE_MAX];
|
||||||
uniform vec3 color_list[SPHERE_MAX];
|
uniform vec3 color_list[SPHERE_MAX];
|
||||||
@ -40,7 +40,7 @@ uniform vec2 radius;
|
|||||||
uniform float opacity;
|
uniform float opacity;
|
||||||
uniform float highlight;
|
uniform float highlight;
|
||||||
uniform int layer_threshold;
|
uniform int layer_threshold;
|
||||||
uniform bool test_mode;
|
uniform bool debug_mode;
|
||||||
|
|
||||||
// light and camera
|
// light and camera
|
||||||
const float focal_slope = 0.3;
|
const float focal_slope = 0.3;
|
||||||
@ -135,36 +135,63 @@ void main() {
|
|||||||
vec3 dir = vec3(focal_slope * scr, -1.);
|
vec3 dir = vec3(focal_slope * scr, -1.);
|
||||||
|
|
||||||
// cast rays through the spheres
|
// cast rays through the spheres
|
||||||
vec2 depth_pairs [SPHERE_MAX];
|
const int LAYER_MAX = 12;
|
||||||
taggedFrag frags [2*SPHERE_MAX];
|
taggedFrag frags [LAYER_MAX];
|
||||||
int frag_cnt = 0;
|
int layer_cnt = 0;
|
||||||
for (int i = 0; i < sphere_cnt; ++i) {
|
for (int id = 0; id < sphere_cnt; ++id) {
|
||||||
vec2 hit_depths = sphere_cast(sphere_list[i], dir);
|
// find out where the ray hits the sphere
|
||||||
if (!isnan(hit_depths[0])) {
|
vec2 hit_depths = sphere_cast(sphere_list[id], dir);
|
||||||
frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[0] * dir, color_list[i], i);
|
|
||||||
++frag_cnt;
|
// insertion-sort the fragments we hit into the fragment list
|
||||||
}
|
for (int side = 0; side < 2; ++side) {
|
||||||
if (!isnan(hit_depths[1])) {
|
float hit_z = -hit_depths[side];
|
||||||
frags[frag_cnt] = sphere_shading(sphere_list[i], hit_depths[1] * dir, color_list[i], i);
|
if (0. > hit_z) {
|
||||||
++frag_cnt;
|
for (int layer = layer_cnt; layer >= 0; --layer) {
|
||||||
}
|
if (layer < 1 || frags[layer-1].pt.z >= hit_z) {
|
||||||
}
|
// we're not as close to the screen as the fragment
|
||||||
|
// before the empty slot, so insert here
|
||||||
// sort the fragments by depth, using an insertion sort
|
if (layer < LAYER_MAX) {
|
||||||
for (int take = 1; take < frag_cnt; ++take) {
|
frags[layer] = sphere_shading(
|
||||||
taggedFrag pulled = frags[take];
|
sphere_list[id],
|
||||||
for (int put = take; put >= 0; --put) {
|
hit_depths[side] * dir,
|
||||||
if (put < 1 || frags[put-1].pt.z >= pulled.pt.z) {
|
color_list[id],
|
||||||
frags[put] = pulled;
|
id
|
||||||
break;
|
);
|
||||||
} else {
|
}
|
||||||
frags[put] = frags[put-1];
|
break;
|
||||||
|
} else {
|
||||||
|
// we're closer to the screen than the fragment before
|
||||||
|
// the empty slot, so move that fragment into the empty
|
||||||
|
// slot
|
||||||
|
frags[layer] = frags[layer-1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layer_cnt = min(layer_cnt + 1, LAYER_MAX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* DEBUG */
|
||||||
|
// in debug mode, show the layer count instead of the shaded image
|
||||||
|
if (debug_mode) {
|
||||||
|
// at the bottom of the screen, show the color scale instead of the
|
||||||
|
// layer count
|
||||||
|
if (gl_FragCoord.y < 10.) layer_cnt = 2 * int(8. * gl_FragCoord.x / resolution.x);
|
||||||
|
|
||||||
|
// convert number to color
|
||||||
|
vec3 color;
|
||||||
|
if (layer_cnt % 2 == 0) {
|
||||||
|
ivec3 bits = layer_cnt / ivec3(2, 4, 8);
|
||||||
|
color = mod(vec3(bits), 2.);
|
||||||
|
} else {
|
||||||
|
color = vec3(0.5);
|
||||||
|
}
|
||||||
|
outColor = vec4(color, 1.);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// highlight intersections and cusps
|
// highlight intersections and cusps
|
||||||
for (int i = frag_cnt-1; i >= 1; --i) {
|
for (int i = layer_cnt-1; i >= 1; --i) {
|
||||||
// intersections
|
// intersections
|
||||||
taggedFrag frag0 = frags[i];
|
taggedFrag frag0 = frags[i];
|
||||||
taggedFrag frag1 = frags[i-1];
|
taggedFrag frag1 = frags[i-1];
|
||||||
@ -187,7 +214,7 @@ void main() {
|
|||||||
|
|
||||||
// composite the sphere fragments
|
// composite the sphere fragments
|
||||||
vec3 color = vec3(0.);
|
vec3 color = vec3(0.);
|
||||||
for (int i = frag_cnt-1; i >= layer_threshold; --i) {
|
for (int i = layer_cnt-1; i >= layer_threshold; --i) {
|
||||||
if (frags[i].pt.z < 0.) {
|
if (frags[i].pt.z < 0.) {
|
||||||
vec4 frag_color = frags[i].color;
|
vec4 frag_color = frags[i].color;
|
||||||
color = mix(color, frag_color.rgb, frag_color.a);
|
color = mix(color, frag_color.rgb, frag_color.a);
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
|
|
||||||
extern crate js_sys;
|
extern crate js_sys;
|
||||||
use core::array;
|
use core::array;
|
||||||
use nalgebra::DVector;
|
use nalgebra::{DMatrix, DVector};
|
||||||
use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}};
|
use sycamore::{prelude::*, motion::create_raf, rt::{JsCast, JsValue}};
|
||||||
use web_sys::{console, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation};
|
use web_sys::{console, window, WebGl2RenderingContext, WebGlProgram, WebGlShader, WebGlUniformLocation};
|
||||||
|
|
||||||
mod engine;
|
mod engine;
|
||||||
|
|
||||||
@ -96,22 +96,58 @@ fn main() {
|
|||||||
let radius_y = create_signal(1.0);
|
let radius_y = create_signal(1.0);
|
||||||
let opacity = create_signal(0.5);
|
let opacity = create_signal(0.5);
|
||||||
let highlight = create_signal(0.2);
|
let highlight = create_signal(0.2);
|
||||||
let layer_threshold = create_signal(0.0);
|
let turntable = create_signal(false);
|
||||||
let debug_mode = 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
|
// display
|
||||||
let display = create_node_ref();
|
let display = create_node_ref();
|
||||||
|
|
||||||
|
// change listener
|
||||||
|
let scene_changed = create_signal(true);
|
||||||
|
create_effect(move || {
|
||||||
|
ctrl_x.track();
|
||||||
|
ctrl_y.track();
|
||||||
|
radius_x.track();
|
||||||
|
radius_y.track();
|
||||||
|
opacity.track();
|
||||||
|
highlight.track();
|
||||||
|
turntable.track();
|
||||||
|
layer_threshold.track();
|
||||||
|
debug_mode.track();
|
||||||
|
|
||||||
|
scene_changed.set(true);
|
||||||
|
});
|
||||||
|
|
||||||
on_mount(move || {
|
on_mount(move || {
|
||||||
// list construction elements
|
// list construction elements
|
||||||
const SPHERE_MAX: usize = 12;
|
const SPHERE_MAX: usize = 200;
|
||||||
let mut sphere_vec = Vec::<DVector<f64>>::new();
|
let mut sphere_vec = Vec::<DVector<f64>>::new();
|
||||||
let color_vec = vec![
|
let color_vec = vec![
|
||||||
[1.00_f32, 0.25_f32, 0.00_f32],
|
[1.00_f32, 0.25_f32, 0.00_f32],
|
||||||
[0.00_f32, 0.25_f32, 1.00_f32],
|
[0.00_f32, 0.25_f32, 1.00_f32],
|
||||||
[0.25_f32, 0.00_f32, 1.00_f32]
|
[0.25_f32, 0.00_f32, 1.00_f32],
|
||||||
|
[0.25_f32, 1.00_f32, 0.00_f32],
|
||||||
|
[0.75_f32, 0.75_f32, 0.00_f32],
|
||||||
|
[0.00_f32, 0.75_f32, 0.50_f32],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// timing
|
||||||
|
let mut last_time = 0.0;
|
||||||
|
|
||||||
|
// scene parameters
|
||||||
|
const TURNTABLE_SPEED: f64 = 0.5;
|
||||||
|
let mut turntable_angle = 0.0;
|
||||||
|
|
||||||
|
/* INSTRUMENTS */
|
||||||
|
let performance = window().unwrap().performance().unwrap();
|
||||||
|
|
||||||
// get the display canvas
|
// get the display canvas
|
||||||
let canvas = display
|
let canvas = display
|
||||||
.get::<DomNode>()
|
.get::<DomNode>()
|
||||||
@ -207,60 +243,110 @@ fn main() {
|
|||||||
bind_vertex_attrib(&ctx, position_index, 3, &positions);
|
bind_vertex_attrib(&ctx, position_index, 3, &positions);
|
||||||
|
|
||||||
// set up a repainting routine
|
// set up a repainting routine
|
||||||
let (_, start_updating_display, _) = create_raf(move || {
|
let (_, start_animation_loop, _) = create_raf(move || {
|
||||||
// update the construction
|
// get the time step
|
||||||
sphere_vec.clear();
|
let time = performance.now();
|
||||||
sphere_vec.push(engine::sphere(0.5, 0.5, -5.0 + ctrl_x.get(), radius_x.get()));
|
let time_step = 0.001*(time - last_time);
|
||||||
sphere_vec.push(engine::sphere(-0.5, -0.5, -5.0 + ctrl_y.get(), radius_y.get()));
|
last_time = time;
|
||||||
sphere_vec.push(engine::sphere(-0.5, 0.5, -5.0, 0.75));
|
|
||||||
|
|
||||||
// set the resolution
|
// move the turntable
|
||||||
let width = canvas.width() as f32;
|
let turntable_val = turntable.get();
|
||||||
let height = canvas.height() as f32;
|
if turntable_val {
|
||||||
ctx.uniform2f(resolution_loc.as_ref(), width, height);
|
turntable_angle += TURNTABLE_SPEED * time_step;
|
||||||
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
|
if scene_changed.get() {
|
||||||
ctx.uniform2f(ctrl_loc.as_ref(), ctrl_x.get() as f32, ctrl_y.get() as f32); /* DEBUG */
|
/* INSTRUMENTS */
|
||||||
ctx.uniform2f(radius_loc.as_ref(), radius_x.get() as f32, radius_y.get() as f32); /* DEBUG */
|
// measure mean frame interval
|
||||||
ctx.uniform1f(opacity_loc.as_ref(), opacity.get() as f32);
|
frames_since_last_sample += 1;
|
||||||
ctx.uniform1f(highlight_loc.as_ref(), highlight.get() as f32);
|
if frames_since_last_sample >= SAMPLE_PERIOD {
|
||||||
ctx.uniform1i(layer_threshold_loc.as_ref(), layer_threshold.get() as i32);
|
mean_frame_interval.set((time - last_sample_time) / (SAMPLE_PERIOD as f64));
|
||||||
ctx.uniform1i(debug_mode_loc.as_ref(), debug_mode.get() as i32);
|
last_sample_time = time;
|
||||||
|
frames_since_last_sample = 0;
|
||||||
// draw the scene
|
}
|
||||||
ctx.draw_arrays(WebGl2RenderingContext::TRIANGLES, 0, VERTEX_CNT as i32);
|
|
||||||
|
// set the orientation and translation
|
||||||
|
let orientation = {
|
||||||
|
let ang_cos = turntable_angle.cos();
|
||||||
|
let ang_sin = turntable_angle.sin();
|
||||||
|
DMatrix::from_column_slice(5, 5, &[
|
||||||
|
ang_cos, 0.0, ang_sin, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0, 0.0,
|
||||||
|
-ang_sin, 0.0, ang_cos, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 1.0
|
||||||
|
])
|
||||||
|
};
|
||||||
|
let translation = {
|
||||||
|
const LEN: f64 = -5.0;
|
||||||
|
const LEN_SQ: f64 = LEN*LEN;
|
||||||
|
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, LEN,
|
||||||
|
0.0, 0.0, 2.0*LEN, 1.0, LEN_SQ,
|
||||||
|
0.0, 0.0, 0.0, 0.0, 1.0
|
||||||
|
])
|
||||||
|
};
|
||||||
|
let construction_to_world = &translation * orientation;
|
||||||
|
|
||||||
|
// update the construction
|
||||||
|
sphere_vec.clear();
|
||||||
|
sphere_vec.push(&construction_to_world * engine::sphere(0.5, 0.5, ctrl_x.get(), radius_x.get()));
|
||||||
|
sphere_vec.push(&construction_to_world * engine::sphere(-0.5, -0.5, ctrl_y.get(), radius_y.get()));
|
||||||
|
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));
|
||||||
|
|
||||||
|
// 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.uniform2f(ctrl_loc.as_ref(), ctrl_x.get() as f32, ctrl_y.get() as f32); /* DEBUG */
|
||||||
|
ctx.uniform2f(radius_loc.as_ref(), radius_x.get() as f32, radius_y.get() as f32); /* DEBUG */
|
||||||
|
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(turntable_val);
|
||||||
|
} else {
|
||||||
|
frames_since_last_sample = 0;
|
||||||
|
mean_frame_interval.set(-1.0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
start_animation_loop();
|
||||||
/*
|
|
||||||
this wastes CPU time by running an animation loop, which updates the
|
|
||||||
display even when nothing has changed. there should be a way to make
|
|
||||||
Sycamore do single-frame updates in response to changes, but i
|
|
||||||
haven't found it yet
|
|
||||||
*/
|
|
||||||
start_updating_display();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
div(id="app") {
|
div(id="app") {
|
||||||
|
div { "Mean frame interval: " (mean_frame_interval.get()) " ms" }
|
||||||
canvas(ref=display, width="600", height="600")
|
canvas(ref=display, width="600", height="600")
|
||||||
div(class="control") {
|
div(class="control") {
|
||||||
label(for="ctrl-x") { "Sphere 0 depth" }
|
label(for="ctrl-x") { "Sphere 0 depth" }
|
||||||
@ -326,6 +412,14 @@ fn main() {
|
|||||||
bind:valueAsNumber=highlight
|
bind:valueAsNumber=highlight
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
div(class="control") {
|
||||||
|
label(for="turntable") { "Turntable" }
|
||||||
|
input(
|
||||||
|
type="checkbox",
|
||||||
|
id="turntable",
|
||||||
|
bind:checked=turntable
|
||||||
|
)
|
||||||
|
}
|
||||||
div(class="control") {
|
div(class="control") {
|
||||||
label(for="layer-threshold") { "Layer threshold" }
|
label(for="layer-threshold") { "Layer threshold" }
|
||||||
input(
|
input(
|
||||||
|
Loading…
Reference in New Issue
Block a user