use super::*;
pub(crate) fn use_raytrace_state() -> UseRayTrace {
UseRayTrace {
fps: App::use_signal(|| 0.0),
running: App::use_signal(|| true),
loop_started: App::use_signal(|| false),
auto_rotate: App::use_signal(|| true),
render_scale: App::use_signal(|| 1.0),
loaded: App::use_signal(|| false),
active: App::use_signal(|| false),
error_detail: App::use_signal(String::new),
init_error_code: App::use_signal(|| ""),
}
}
pub(crate) fn use_raytrace_webgl_state() -> UseRayTraceWebGl {
UseRayTraceWebGl {
fps: App::use_signal(|| 0.0),
running: App::use_signal(|| true),
auto_rotate: App::use_signal(|| true),
loaded: App::use_signal(|| false),
active: App::use_signal(|| false),
loop_started: App::use_signal(|| false),
init_error_code: App::use_signal(|| ""),
error_detail: App::use_signal(String::new),
}
}
pub(crate) fn use_raytrace_webgpu_state() -> UseRayTraceWebGpu {
UseRayTraceWebGpu {
fps: App::use_signal(|| 0.0),
running: App::use_signal(|| true),
auto_rotate: App::use_signal(|| true),
loaded: App::use_signal(|| false),
active: App::use_signal(|| false),
loop_started: App::use_signal(|| false),
init_error_code: App::use_signal(|| ""),
error_detail: App::use_signal(String::new),
}
}
pub(crate) fn use_raytrace_fullscreen_state() -> UseRayTraceFullscreen {
UseRayTraceFullscreen {
canvas_2d: App::use_signal(|| false),
web_gl: App::use_signal(|| false),
web_gpu: App::use_signal(|| false),
}
}
fn raytrace_canvas_detached(canvas_selector: &str) -> bool {
window()
.and_then(|window_value: Window| window_value.document())
.and_then(|document: Document| document.query_selector(canvas_selector).ok().flatten())
.is_none()
}
fn read_raytrace_canvas_size(canvas_selector: &str) -> Option<(f64, f64)> {
let window_value: Window = window()?;
let document_value: Document = window_value.document()?;
let element: Element = document_value
.query_selector(canvas_selector)
.ok()
.flatten()?;
let canvas: HtmlCanvasElement = element.unchecked_into();
let rect: DomRect = canvas.get_bounding_client_rect();
Some((rect.width(), rect.height()))
}
fn query_raytrace_canvas(canvas_selector: &str) -> Option<HtmlCanvasElement> {
let window_value: Window = window()?;
let document_value: Document = window_value.document()?;
let element: Element = document_value
.query_selector(canvas_selector)
.ok()
.flatten()?;
Some(element.unchecked_into())
}
pub(crate) fn build_raytrace_scene() -> RayTraceScene {
let ground_material: Material = Material::lambert(Vector3D::new(0.62, 0.63, 0.66));
let ground: Occluder = Occluder::aabb(
Vector3D::new(-6.0, -0.62, -6.0),
Vector3D::new(6.0, -0.5, 6.0),
ground_material,
);
let mirror_material: Material = Material::phong(Vector3D::new(0.86, 0.87, 0.9), 1.0, 96.0);
let mirror: Occluder = Occluder::sphere(Vector3D::new(-0.9, 0.35, 0.2), 0.85, mirror_material);
let emissive_material: Material = Material::emissive(Vector3D::new(1.0, 0.42, 0.12));
let emissive: Occluder =
Occluder::sphere(Vector3D::new(1.75, 0.55, -1.35), 0.42, emissive_material);
let panel_material: Material = Material::phong(Vector3D::new(0.24, 0.46, 0.62), 0.9, 48.0);
let panel: Occluder = Occluder::triangle(
Vector3D::new(-2.7, -0.5, -2.3),
Vector3D::new(0.4, 2.55, -3.6),
Vector3D::new(2.3, -0.5, -1.15),
panel_material,
);
let occluders: Vec<Occluder> = vec![ground, mirror, emissive, panel];
RayTraceScene::new(occluders)
}
pub(crate) fn build_raytrace_lighting() -> LightingUniforms {
let eye: Vector3D = Vector3D::new(
RAYTRACE_SHADING_EYE_X,
RAYTRACE_SHADING_EYE_Y,
RAYTRACE_SHADING_EYE_Z,
);
let sun: Light = Light::new_directional(
Vector3D::new(0.42, -0.86, 0.3).normalized(),
Vector3D::new(1.0, 0.95, 0.84),
);
let fill: Light = Light::new_point(
Vector3D::new(-2.6, 2.9, 2.4),
Vector3D::new(0.42, 0.52, 0.78),
2.6,
);
let rim: Light = Light::new_spot(
Vector3D::new(3.1, 2.35, 1.75),
(Vector3D::new(1.75, 0.55, -1.35) - Vector3D::new(3.1, 2.35, 1.75)).normalized(),
Vector3D::new(0.78, 0.86, 1.0),
3.2,
0.62,
);
let mut lights: LightingUniforms = LightingUniforms::with_eye(eye);
lights.set_ambient(Vector3D::new(0.09, 0.1, 0.14));
lights.add_light(sun);
lights.add_light(fill);
lights.add_light(rim);
lights
}
pub(crate) fn build_raytrace_settings() -> RayTraceSettings {
RayTraceSettings::with_quality(RAYTRACE_SAMPLES_PER_AXIS, RAYTRACE_MAX_BOUNCES)
}
pub(crate) fn compute_eye_position(yaw: f64, pitch: f64) -> Vector3D {
let cos_pitch: f64 = pitch.cos();
let distance: f64 = RAYTRACE_CAMERA_DISTANCE;
Vector3D::new(
RAYTRACE_CAMERA_LOOK_AT_X + distance * yaw.sin() * cos_pitch,
RAYTRACE_CAMERA_LOOK_AT_Y + distance * pitch.sin(),
RAYTRACE_CAMERA_LOOK_AT_Z + distance * yaw.cos() * cos_pitch,
)
}
pub(crate) fn build_raytrace_camera(yaw: f64, pitch: f64, width: u32, height: u32) -> Camera3D {
Camera3D::create(
compute_eye_position(yaw, pitch),
Vector3D::new(
RAYTRACE_CAMERA_LOOK_AT_X,
RAYTRACE_CAMERA_LOOK_AT_Y,
RAYTRACE_CAMERA_LOOK_AT_Z,
),
f64::from(width),
f64::from(height),
)
}
pub(crate) fn raytrace_canvas_backing_width(canvas_selector: &str) -> u32 {
query_raytrace_canvas(canvas_selector)
.map(|canvas: HtmlCanvasElement| canvas.width())
.unwrap_or(1)
}
pub(crate) fn raytrace_canvas_backing_height(canvas_selector: &str) -> u32 {
query_raytrace_canvas(canvas_selector)
.map(|canvas: HtmlCanvasElement| canvas.height())
.unwrap_or(1)
}
pub(crate) fn raytrace_scaled_dimensions(scale: f64) -> (u32, u32) {
let width: u32 = (RAYTRACE_WIDTH * scale).round().max(1.0) as u32;
let height: u32 = (RAYTRACE_HEIGHT * scale).round().max(1.0) as u32;
(width, height)
}
pub(crate) fn raytrace_error_detail(error: &JsValue) -> String {
error
.as_string()
.unwrap_or_else(|| RAYTRACE_ERROR_DETAIL_UNKNOWN.to_string())
}
pub(crate) fn raytrace_record_error(
active: Signal<bool>,
error_detail: Signal<String>,
init_error_code: Signal<&'static str>,
code: &'static str,
call: &str,
error: &JsValue,
) {
let detail: String = raytrace_error_detail(error);
Console::error(format!(
"{RAYTRACE_LOG_PREFIX} {call} failed [{code}]: {detail}"
));
error_detail.set(detail);
init_error_code.set(code);
active.set(false);
}
pub(crate) fn raytrace_clear_error(
active: Signal<bool>,
error_detail: Signal<String>,
init_error_code: Signal<&'static str>,
) {
error_detail.set(String::new());
init_error_code.set("");
active.set(true);
}
fn raytrace_schedule_one_shot<F>(pending_timers: RayTracePendingTimers, millis: i32, body: F)
where
F: FnMut() + 'static,
{
let timer_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(body));
let Some(window_value): Option<Window> = window() else {
return;
};
let timer_callback: Function = timer_closure.as_ref().unchecked_ref::<Function>().clone();
match window_value
.set_timeout_with_callback_and_timeout_and_arguments_0(&timer_callback, millis)
{
Ok(timer_id) => {
let mut pending_guard: RayTracePendingTimerGuard = match pending_timers.try_borrow_mut()
{
Ok(guard) => guard,
Err(_contended) => return,
};
pending_guard.push((timer_id, timer_closure));
}
Err(_reason) => {}
}
}
fn raytrace_release_pending_timers(pending_timers: RayTracePendingTimers) {
let timer_ids: Vec<i32> = pending_timers
.borrow()
.iter()
.map(|entry: &(i32, Closure<dyn FnMut()>)| entry.0)
.collect();
for timer_id in timer_ids {
let Some(window_value): Option<Window> = window() else {
return;
};
window_value.clear_timeout_with_handle(timer_id);
}
let mut pending_guard: RayTracePendingTimerGuard = match pending_timers.try_borrow_mut() {
Ok(guard) => guard,
Err(_contended) => return,
};
pending_guard.clear();
}
fn raytrace_set_loaded_delayed(
pending_timers: RayTracePendingTimers,
loaded: Signal<bool>,
millis: i32,
) {
raytrace_schedule_one_shot(pending_timers, millis, move || {
loaded.set(true);
});
}
fn raytrace_register_resize_debounce(
resize_dirty: Rc<Cell<bool>>,
resize_timer: Rc<Cell<Option<i32>>>,
cancelled: Rc<Cell<bool>>,
) -> RayTraceDebounceOwner {
let debounce_owner: RayTraceDebounceOwner = Rc::new(RefCell::new(None));
let resize_dirty_for_event: Rc<Cell<bool>> = resize_dirty;
let resize_timer_for_event: Rc<Cell<Option<i32>>> = resize_timer;
let cancelled_for_event: Rc<Cell<bool>> = cancelled;
let debounce_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
resize_dirty_for_event.set(true);
}));
let debounce_callback: Function = debounce_closure
.as_ref()
.unchecked_ref::<Function>()
.clone();
let Some(resize_window): Option<Window> = window() else {
*debounce_owner.borrow_mut() = Some(debounce_closure);
return debounce_owner;
};
App::use_window_event(RAYTRACE_EVENT_RESIZE, move || {
if cancelled_for_event.get() {
return;
}
let old_timer: Option<i32> = resize_timer_for_event.get();
if let Some(timer_id) = old_timer {
let Some(clear_window): Option<Window> = window() else {
return;
};
clear_window.clear_timeout_with_handle(timer_id);
}
let new_timer: i32 = resize_window
.set_timeout_with_callback_and_timeout_and_arguments_0(
&debounce_callback,
RAYTRACE_RESIZE_DEBOUNCE_MILLIS,
)
.unwrap_or_default();
resize_timer_for_event.set(Some(new_timer));
});
*debounce_owner.borrow_mut() = Some(debounce_closure);
debounce_owner
}
pub(crate) fn read_raytrace_dpr() -> f64 {
let Some(window_value): Option<Window> = window() else {
return 1.0;
};
Reflect::get(
window_value.as_ref(),
&JsValue::from_str(RAYTRACE_EVENT_PROPERTY_DEVICE_PIXEL_RATIO),
)
.ok()
.and_then(|value: JsValue| value.as_f64())
.filter(|value: &f64| value.is_finite() && *value >= 1.0)
.unwrap_or(1.0)
}
pub(crate) fn raytrace_resize_backend_to_canvas(
renderer: &mut RayTraceRenderer,
canvas_selector: &str,
active: Signal<bool>,
error_detail: Signal<String>,
init_error_code: Signal<&'static str>,
) -> bool {
let Some(canvas): Option<HtmlCanvasElement> = query_raytrace_canvas(canvas_selector) else {
return false;
};
let backing_width: u32 = canvas.width();
let backing_height: u32 = canvas.height();
let dpr: f64 = read_raytrace_dpr();
let (css_width, css_height): (f64, f64) =
read_raytrace_canvas_size(canvas_selector).unwrap_or((0.0, 0.0));
if css_width <= 0.0 || css_height <= 0.0 {
return backing_width > 0 && backing_height > 0;
}
let target_width: u32 = (css_width * dpr).round() as u32;
let target_height: u32 = (css_height * dpr).round() as u32;
if target_width == 0 || target_height == 0 {
return backing_width > 0 && backing_height > 0;
}
if backing_width == target_width && backing_height == target_height {
return true;
}
match renderer.resize(target_width, target_height) {
Ok(()) => {
raytrace_clear_error(active, error_detail, init_error_code);
true
}
Err(error) => {
raytrace_record_error(
active,
error_detail,
init_error_code,
RAYTRACE_ERROR_CODE_RESIZE,
"resize",
&error,
);
false
}
}
}
fn raytrace_release_loop(
cancelled: Rc<Cell<bool>>,
raf_id: Rc<Cell<Option<i32>>>,
closure_cell: RafClosureCell,
pending_timers: RayTracePendingTimers,
renderer_rc: Rc<RefCell<Option<RayTraceRenderer>>>,
) {
cancelled.set(true);
if let Some(cancel_id) = raf_id.get()
&& let Some(window_value) = window()
{
let _: Result<(), JsValue> = window_value.cancel_animation_frame(cancel_id);
}
raf_id.set(None);
raytrace_release_pending_timers(pending_timers);
let _: Option<Closure<dyn FnMut()>> = closure_cell.try_take();
if let Some(mut renderer) = renderer_rc
.try_borrow_mut()
.ok()
.and_then(|mut slot: RefMut<'_, Option<RayTraceRenderer>>| slot.take())
{
renderer.dispose();
}
}
pub(crate) fn start_raytrace_loop(state: UseRayTrace, angles: RayTraceCameraAngles) {
let init_state: UseRayTrace = state;
let loop_state: UseRayTrace = state;
let renderer_rc: Rc<RefCell<Option<RayTraceRenderer>>> = Rc::new(RefCell::new(None));
let cancelled: Rc<Cell<bool>> = Rc::new(Cell::new(false));
let raf_id: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));
let closure_cell: RafClosureCell = Rc::new(MaybeEngineCell::new());
let pending_timers: RayTracePendingTimers = Rc::new(RefCell::new(Vec::new()));
let renderer_for_cleanup: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let raf_for_cleanup: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_for_cleanup: RafClosureCell = closure_cell.clone();
let cancelled_for_cleanup: Rc<Cell<bool>> = cancelled.clone();
let timers_for_cleanup: RayTracePendingTimers = pending_timers.clone();
App::use_cleanup(move || {
raytrace_release_loop(
cancelled_for_cleanup,
raf_for_cleanup,
cell_for_cleanup,
timers_for_cleanup,
renderer_for_cleanup,
);
});
let cancelled_for_init: Rc<Cell<bool>> = cancelled.clone();
let pending_timers_for_init: RayTracePendingTimers = pending_timers.clone();
raytrace_schedule_one_shot(pending_timers_for_init.clone(), 0, || {
draw_game_3d_loading(RAYTRACE_LOADING_CANVAS_SELECTOR, RAYTRACE_CANVAS_SELECTOR);
});
spawn_local(async move {
let Some(canvas): Option<HtmlCanvasElement> =
query_raytrace_canvas(RAYTRACE_CANVAS_SELECTOR)
else {
init_state.get_active().set(false);
init_state
.get_init_error_code()
.set(RAYTRACE_ERROR_CODE_INIT);
init_state.get_error_detail().set(String::new());
init_state.get_loaded().set(true);
return;
};
let scene: RayTraceScene = build_raytrace_scene();
let renderer: RayTraceRenderer =
match RayTraceRenderer::init(canvas, RayTraceBackend::Canvas2D, &scene).await {
Ok(value) => value,
Err(error) => {
raytrace_record_error(
init_state.get_active(),
init_state.get_error_detail(),
init_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_INIT,
"canvas2d init",
&error,
);
init_state.get_loaded().set(true);
return;
}
};
if cancelled_for_init.get() {
let mut stale: RayTraceRenderer = renderer;
stale.dispose();
return;
}
Console::log(format!(
"{RAYTRACE_LOG_PREFIX} {RAYTRACE_LOG_BACKEND_READY} (2d)"
));
*renderer_rc.borrow_mut() = Some(renderer);
raytrace_set_loaded_delayed(
pending_timers_for_init,
init_state.get_loaded(),
RAYTRACE_LOADING_MIN_MILLIS,
);
let lights: LightingUniforms = build_raytrace_lighting();
let settings: RayTraceSettings = build_raytrace_settings();
let scale_index: Rc<Cell<usize>> = Rc::new(Cell::new(RAYTRACE_RENDER_SCALE_START_INDEX));
let ema_millis: Rc<Cell<f64>> = Rc::new(Cell::new(0.0));
let slow_frames: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let fast_frames: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let very_fast_frames: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let last_time: Rc<Cell<f64>> = Rc::new(Cell::new(-1.0));
let frame_count: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let fps_timer: Rc<Cell<f64>> = Rc::new(Cell::new(0.0));
let renderer_for_loop: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let raf_clone: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_clone: RafClosureCell = closure_cell.clone();
let last_clone: Rc<Cell<f64>> = last_time.clone();
let frame_clone: Rc<Cell<u32>> = frame_count.clone();
let fps_clone: Rc<Cell<f64>> = fps_timer.clone();
let scale_clone: Rc<Cell<usize>> = scale_index.clone();
let ema_clone: Rc<Cell<f64>> = ema_millis.clone();
let slow_clone: Rc<Cell<u32>> = slow_frames.clone();
let fast_clone: Rc<Cell<u32>> = fast_frames.clone();
let very_fast_clone: Rc<Cell<u32>> = very_fast_frames.clone();
let yaw_clone: Rc<Cell<f64>> = angles.yaw.clone();
let pitch_clone: Rc<Cell<f64>> = angles.pitch.clone();
let cancelled_for_loop: Rc<Cell<bool>> = cancelled.clone();
let raf_for_detach: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_for_detach: RafClosureCell = closure_cell.clone();
let timers_for_detach: RayTracePendingTimers = pending_timers.clone();
let renderer_for_detach: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let timers_for_loop: RayTracePendingTimers = pending_timers.clone();
let raf_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
if cancelled_for_loop.get() {
return;
}
if raytrace_canvas_detached(RAYTRACE_CANVAS_SELECTOR) {
raytrace_release_loop(
cancelled_for_loop.clone(),
raf_for_detach.clone(),
cell_for_detach.clone(),
timers_for_detach.clone(),
renderer_for_detach.clone(),
);
return;
}
let Some(window_value): Option<Window> = window() else {
return;
};
let Some(performance): Option<Performance> = window_value.performance() else {
return;
};
let current_time: f64 = performance.now() / 1000.0;
let prev: f64 = last_clone.get();
let frame_time: f64 = if prev < 0.0 {
1.0 / 60.0
} else {
current_time - prev
};
let anim_time: f64 = frame_time.min(0.25);
last_clone.set(current_time);
if loop_state.get_auto_rotate().get() {
yaw_clone.set(yaw_clone.get() + RAYTRACE_AUTO_YAW_SPEED * anim_time);
}
if loop_state.get_running().get() {
let scale: f64 = RAYTRACE_RENDER_SCALES[scale_clone.get()];
let (frame_width, frame_height): (u32, u32) = raytrace_scaled_dimensions(scale);
let camera: Camera3D = build_raytrace_camera(
yaw_clone.get(),
pitch_clone.get(),
frame_width,
frame_height,
);
let render_start: f64 = performance.now();
let render_result: Result<(), JsValue> =
match renderer_for_loop.borrow_mut().as_mut() {
Some(renderer) => renderer.render(&camera, &lights, &settings),
None => Err(JsValue::NULL),
};
let render_millis: f64 = performance.now() - render_start;
match render_result {
Ok(()) => {
raytrace_clear_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
);
if !loop_state.get_loaded().get() {
raytrace_set_loaded_delayed(
timers_for_loop.clone(),
loop_state.get_loaded(),
RAYTRACE_LOADING_MIN_MILLIS,
);
}
}
Err(error) => {
raytrace_record_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_RENDER,
"canvas2d render",
&error,
);
}
}
let ema_prev: f64 = ema_clone.get();
let ema: f64 = if ema_prev <= 0.0 {
render_millis
} else {
ema_prev * (1.0 - RAYTRACE_ADAPT_EMA_ALPHA)
+ render_millis * RAYTRACE_ADAPT_EMA_ALPHA
};
ema_clone.set(ema);
if ema > RAYTRACE_ADAPT_SLOW_FRAME_MILLIS {
slow_clone.set(slow_clone.get() + 1);
fast_clone.set(0);
very_fast_clone.set(0);
} else if ema < RAYTRACE_ADAPT_VERY_FAST_FRAME_MILLIS {
fast_clone.set(fast_clone.get() + 1);
very_fast_clone.set(very_fast_clone.get() + 1);
slow_clone.set(0);
} else if ema < RAYTRACE_ADAPT_FAST_FRAME_MILLIS {
fast_clone.set(fast_clone.get() + 1);
slow_clone.set(0);
very_fast_clone.set(0);
} else {
slow_clone.set(0);
fast_clone.set(0);
very_fast_clone.set(0);
}
let index: usize = scale_clone.get();
let mut next: usize = index;
if slow_clone.get() >= RAYTRACE_ADAPT_SLOW_FRAMES
&& index + 1 < RAYTRACE_RENDER_SCALES.len()
{
next = index + 1;
} else if fast_clone.get() >= RAYTRACE_ADAPT_FAST_FRAMES && index > 0 {
next = if very_fast_clone.get() >= RAYTRACE_ADAPT_FAST_FRAMES {
index.saturating_sub(2)
} else {
index - 1
};
}
if next != index {
let (next_width, next_height): (u32, u32) =
raytrace_scaled_dimensions(RAYTRACE_RENDER_SCALES[next]);
let resized: bool = match renderer_for_loop.try_borrow_mut().ok() {
Some(mut renderer_slot) => match renderer_slot.as_mut() {
Some(renderer) => renderer.resize(next_width, next_height).is_ok(),
None => false,
},
None => false,
};
if resized {
scale_clone.set(next);
slow_clone.set(0);
fast_clone.set(0);
very_fast_clone.set(0);
loop_state
.get_render_scale()
.set(RAYTRACE_RENDER_SCALES[next]);
} else {
raytrace_record_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_RESIZE,
"render scale",
&JsValue::NULL,
);
slow_clone.set(0);
fast_clone.set(0);
very_fast_clone.set(0);
}
}
}
if loop_state.get_running().get() {
frame_clone.set(frame_clone.get() + 1);
fps_clone.set(fps_clone.get() + frame_time);
if fps_clone.get() >= 1.0 {
let fps: f64 = f64::from(frame_clone.get()) / fps_clone.get();
loop_state.get_fps().set(fps);
frame_clone.set(0);
fps_clone.set(0.0);
}
}
let Some(raf_closure_ref): Option<&'static Closure<dyn FnMut()>> = cell_clone.try_get()
else {
return;
};
let next_id: i32 = window_value
.request_animation_frame(raf_closure_ref.as_ref().unchecked_ref())
.unwrap_or_default();
raf_clone.set(Some(next_id));
}));
let _: Result<(), _> = closure_cell.try_set(raf_closure);
let raf_for_start: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_for_start: RafClosureCell = closure_cell.clone();
raytrace_schedule_one_shot(
pending_timers.clone(),
RAYTRACE_LOOP_START_DELAY_MILLIS,
move || {
let Some(start_window): Option<Window> = window() else {
return;
};
let Some(start_raf_ref): Option<&'static Closure<dyn FnMut()>> =
cell_for_start.try_get()
else {
return;
};
let start_id: i32 = start_window
.request_animation_frame(start_raf_ref.as_ref().unchecked_ref())
.unwrap_or_default();
raf_for_start.set(Some(start_id));
},
);
});
}
pub(crate) fn start_raytrace_webgl_loop(state: UseRayTraceWebGl, angles: RayTraceCameraAngles) {
let init_state: UseRayTraceWebGl = state;
let loop_state: UseRayTraceWebGl = state;
let renderer_rc: Rc<RefCell<Option<RayTraceRenderer>>> = Rc::new(RefCell::new(None));
let cancelled: Rc<Cell<bool>> = Rc::new(Cell::new(false));
let raf_id: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));
let closure_cell: RafClosureCell = Rc::new(MaybeEngineCell::new());
let resize_dirty: Rc<Cell<bool>> = Rc::new(Cell::new(false));
let resize_timer: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));
let observer_cell: Rc<RefCell<Option<ResizeObserver>>> = Rc::new(RefCell::new(None));
let observer_closure_cell: RayTraceObserverClosureOwner = Rc::new(RefCell::new(None));
let pending_timers: RayTracePendingTimers = Rc::new(RefCell::new(Vec::new()));
let debounce_owner: RayTraceDebounceOwner = raytrace_register_resize_debounce(
resize_dirty.clone(),
resize_timer.clone(),
cancelled.clone(),
);
let renderer_for_cleanup: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let raf_for_cleanup: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_for_cleanup: RafClosureCell = closure_cell.clone();
let cancelled_for_cleanup: Rc<Cell<bool>> = cancelled.clone();
let resize_timer_for_cleanup: Rc<Cell<Option<i32>>> = resize_timer.clone();
let observer_for_cleanup: Rc<RefCell<Option<ResizeObserver>>> = observer_cell.clone();
let observer_closure_for_cleanup: RayTraceObserverClosureOwner = observer_closure_cell.clone();
let timers_for_cleanup: RayTracePendingTimers = pending_timers.clone();
let debounce_for_cleanup: RayTraceDebounceOwner = debounce_owner;
App::use_cleanup(move || {
raytrace_release_loop(
cancelled_for_cleanup,
raf_for_cleanup,
cell_for_cleanup,
timers_for_cleanup,
renderer_for_cleanup,
);
if let Some(timer_id) = resize_timer_for_cleanup.get()
&& let Some(window_value) = window()
{
window_value.clear_timeout_with_handle(timer_id);
}
if let Some(observer) = observer_for_cleanup
.try_borrow_mut()
.ok()
.and_then(|mut observer_slot: RefMut<'_, Option<ResizeObserver>>| observer_slot.take())
{
observer.disconnect();
}
let _: Option<Closure<dyn FnMut(js_sys::Array, ResizeObserver)>> =
observer_closure_for_cleanup
.try_borrow_mut()
.ok()
.and_then(|mut closure_slot: RayTraceObserverCallbackSlot<'_>| closure_slot.take());
let _: Option<Closure<dyn FnMut()>> = debounce_for_cleanup
.try_borrow_mut()
.ok()
.and_then(|mut debounce_slot: RayTraceDebounceCallbackSlot<'_>| debounce_slot.take());
});
let cancelled_for_init: Rc<Cell<bool>> = cancelled.clone();
let pending_timers_for_init: RayTracePendingTimers = pending_timers.clone();
let observer_closure_cell_for_init: RayTraceObserverClosureOwner =
observer_closure_cell.clone();
raytrace_schedule_one_shot(pending_timers_for_init.clone(), 0, || {
draw_game_3d_loading(
RAYTRACE_WEBGL_LOADING_CANVAS_SELECTOR,
RAYTRACE_WEBGL_CANVAS_SELECTOR,
);
});
spawn_local(async move {
let Some(canvas): Option<HtmlCanvasElement> =
query_raytrace_canvas(RAYTRACE_WEBGL_CANVAS_SELECTOR)
else {
init_state.get_active().set(false);
init_state
.get_init_error_code()
.set(RAYTRACE_ERROR_CODE_INIT);
init_state.get_error_detail().set(String::new());
init_state.get_loaded().set(true);
return;
};
let scene: RayTraceScene = build_raytrace_scene();
let renderer: RayTraceRenderer =
match RayTraceRenderer::init(canvas, RayTraceBackend::WebGl2, &scene).await {
Ok(value) => value,
Err(error) => {
raytrace_record_error(
init_state.get_active(),
init_state.get_error_detail(),
init_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_INIT,
"webgl2 init",
&error,
);
init_state.get_loaded().set(true);
return;
}
};
if cancelled_for_init.get() {
let mut stale: RayTraceRenderer = renderer;
stale.dispose();
return;
}
Console::log(format!(
"{RAYTRACE_LOG_PREFIX} {RAYTRACE_LOG_BACKEND_READY} (gl)"
));
*renderer_rc.borrow_mut() = Some(renderer);
raytrace_set_loaded_delayed(
pending_timers_for_init,
init_state.get_loaded(),
RAYTRACE_LOADING_MIN_MILLIS,
);
let lights: LightingUniforms = build_raytrace_lighting();
let settings: RayTraceSettings = build_raytrace_settings();
let renderer_for_observer: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let observer_closure: Closure<dyn FnMut(js_sys::Array, ResizeObserver)> = Closure::wrap(
Box::new(move |_entries: js_sys::Array, _obs: ResizeObserver| {
let renderer_for_frame: Rc<RefCell<Option<RayTraceRenderer>>> =
renderer_for_observer.clone();
let resize_accepted: bool = match renderer_for_frame.try_borrow_mut() {
Ok(mut renderer_slot) => match renderer_slot.as_mut() {
Some(renderer) => raytrace_resize_backend_to_canvas(
renderer,
RAYTRACE_WEBGL_CANVAS_SELECTOR,
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
),
None => true,
},
Err(_contended) => false,
};
if !resize_accepted {
Console::error(format!(
"{RAYTRACE_LOG_PREFIX} resize observer refused the new CSS box; \
the backend keeps its previous size"
));
}
}),
);
let observer_callback: Function = observer_closure
.as_ref()
.unchecked_ref::<Function>()
.clone();
if let Ok(mut closure_slot) = observer_closure_cell_for_init.try_borrow_mut() {
*closure_slot = Some(observer_closure);
}
if let Ok(resize_observer) = ResizeObserver::new(&observer_callback)
&& let Some(window_value) = window()
&& let Some(document_value) = window_value.document()
&& let Some(element) = document_value
.query_selector(RAYTRACE_WEBGL_CANVAS_SELECTOR)
.ok()
.flatten()
{
resize_observer.observe(&element);
if let Ok(mut observer_slot) = observer_cell.try_borrow_mut() {
*observer_slot = Some(resize_observer);
}
}
let last_time: Rc<Cell<f64>> = Rc::new(Cell::new(-1.0));
let frame_count: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let fps_timer: Rc<Cell<f64>> = Rc::new(Cell::new(0.0));
let renderer_for_loop: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let raf_clone: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_clone: RafClosureCell = closure_cell.clone();
let last_clone: Rc<Cell<f64>> = last_time.clone();
let frame_clone: Rc<Cell<u32>> = frame_count.clone();
let fps_clone: Rc<Cell<f64>> = fps_timer.clone();
let yaw_clone: Rc<Cell<f64>> = angles.yaw.clone();
let pitch_clone: Rc<Cell<f64>> = angles.pitch.clone();
let resize_dirty_for_loop: Rc<Cell<bool>> = resize_dirty.clone();
let cancelled_for_loop: Rc<Cell<bool>> = cancelled.clone();
let raf_for_detach: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_for_detach: RafClosureCell = closure_cell.clone();
let timers_for_detach: RayTracePendingTimers = pending_timers.clone();
let renderer_for_detach: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let timers_for_loop: RayTracePendingTimers = pending_timers.clone();
let raf_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
if cancelled_for_loop.get() {
return;
}
if raytrace_canvas_detached(RAYTRACE_WEBGL_CANVAS_SELECTOR) {
raytrace_release_loop(
cancelled_for_loop.clone(),
raf_for_detach.clone(),
cell_for_detach.clone(),
timers_for_detach.clone(),
renderer_for_detach.clone(),
);
return;
}
let Some(window_value): Option<Window> = window() else {
return;
};
let Some(performance): Option<Performance> = window_value.performance() else {
return;
};
let current_time: f64 = performance.now() / 1000.0;
let prev: f64 = last_clone.get();
let frame_time: f64 = if prev < 0.0 {
1.0 / 60.0
} else {
current_time - prev
};
let anim_time: f64 = frame_time.min(0.25);
last_clone.set(current_time);
if loop_state.get_auto_rotate().get() {
yaw_clone.set(yaw_clone.get() + RAYTRACE_AUTO_YAW_SPEED * anim_time);
}
if resize_dirty_for_loop.get() {
resize_dirty_for_loop.set(false);
}
let running: bool = loop_state.get_running().get();
let render_result: Option<Result<(), JsValue>> = if running {
renderer_for_loop.try_borrow_mut().ok().and_then(
|mut renderer_slot: RefMut<'_, Option<RayTraceRenderer>>| {
let renderer: &mut RayTraceRenderer = renderer_slot.as_mut()?;
if !raytrace_resize_backend_to_canvas(
renderer,
RAYTRACE_WEBGL_CANVAS_SELECTOR,
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
) {
return None;
}
let width: u32 =
raytrace_canvas_backing_width(RAYTRACE_WEBGL_CANVAS_SELECTOR);
let height: u32 =
raytrace_canvas_backing_height(RAYTRACE_WEBGL_CANVAS_SELECTOR);
let camera: Camera3D = build_raytrace_camera(
yaw_clone.get(),
pitch_clone.get(),
width,
height,
);
Some(renderer.render(&camera, &lights, &settings))
},
)
} else {
None
};
if running {
match render_result {
Some(Ok(())) => {
raytrace_clear_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
);
if !loop_state.get_loaded().get() {
raytrace_set_loaded_delayed(
timers_for_loop.clone(),
loop_state.get_loaded(),
RAYTRACE_LOADING_MIN_MILLIS,
);
}
}
Some(Err(error)) => {
raytrace_record_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_RENDER,
"webgl2 render",
&error,
);
}
None => {
if loop_state.get_error_detail().get().is_empty() {
raytrace_record_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_RENDER,
"webgl2 render",
&JsValue::NULL,
);
}
}
}
}
if running {
frame_clone.set(frame_clone.get() + 1);
fps_clone.set(fps_clone.get() + frame_time);
if fps_clone.get() >= 1.0 {
let fps: f64 = f64::from(frame_clone.get()) / fps_clone.get();
loop_state.get_fps().set(fps);
frame_clone.set(0);
fps_clone.set(0.0);
}
}
let Some(raf_closure_ref): Option<&'static Closure<dyn FnMut()>> = cell_clone.try_get()
else {
return;
};
let next_id: i32 = window_value
.request_animation_frame(raf_closure_ref.as_ref().unchecked_ref())
.unwrap_or_default();
if cancelled_for_loop.get() {
raf_clone.set(None);
} else {
raf_clone.set(Some(next_id));
}
}));
let _: Result<(), _> = closure_cell.try_set(raf_closure);
let Some(start_window): Option<Window> = window() else {
return;
};
let Some(start_raf_ref): Option<&'static Closure<dyn FnMut()>> = closure_cell.try_get()
else {
return;
};
let start_id: i32 = start_window
.request_animation_frame(start_raf_ref.as_ref().unchecked_ref())
.unwrap_or_default();
raf_id.set(Some(start_id));
});
}
pub(crate) fn start_raytrace_webgpu_loop(state: UseRayTraceWebGpu, angles: RayTraceCameraAngles) {
let init_state: UseRayTraceWebGpu = state;
let loop_state: UseRayTraceWebGpu = state;
let renderer_rc: Rc<RefCell<Option<RayTraceRenderer>>> = Rc::new(RefCell::new(None));
let cancelled: Rc<Cell<bool>> = Rc::new(Cell::new(false));
let raf_id: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));
let closure_cell: RafClosureCell = Rc::new(MaybeEngineCell::new());
let resize_dirty: Rc<Cell<bool>> = Rc::new(Cell::new(false));
let resize_timer: Rc<Cell<Option<i32>>> = Rc::new(Cell::new(None));
let observer_cell: Rc<RefCell<Option<ResizeObserver>>> = Rc::new(RefCell::new(None));
let observer_closure_cell: RayTraceObserverClosureOwner = Rc::new(RefCell::new(None));
let pending_timers: RayTracePendingTimers = Rc::new(RefCell::new(Vec::new()));
let debounce_owner: RayTraceDebounceOwner = raytrace_register_resize_debounce(
resize_dirty.clone(),
resize_timer.clone(),
cancelled.clone(),
);
let renderer_for_cleanup: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let raf_for_cleanup: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_for_cleanup: RafClosureCell = closure_cell.clone();
let cancelled_for_cleanup: Rc<Cell<bool>> = cancelled.clone();
let resize_timer_for_cleanup: Rc<Cell<Option<i32>>> = resize_timer.clone();
let observer_for_cleanup: Rc<RefCell<Option<ResizeObserver>>> = observer_cell.clone();
let observer_closure_for_cleanup: RayTraceObserverClosureOwner = observer_closure_cell.clone();
let timers_for_cleanup: RayTracePendingTimers = pending_timers.clone();
let debounce_for_cleanup: RayTraceDebounceOwner = debounce_owner;
App::use_cleanup(move || {
raytrace_release_loop(
cancelled_for_cleanup,
raf_for_cleanup,
cell_for_cleanup,
timers_for_cleanup,
renderer_for_cleanup,
);
if let Some(timer_id) = resize_timer_for_cleanup.get()
&& let Some(window_value) = window()
{
window_value.clear_timeout_with_handle(timer_id);
}
if let Some(observer) = observer_for_cleanup
.try_borrow_mut()
.ok()
.and_then(|mut observer_slot: RefMut<'_, Option<ResizeObserver>>| observer_slot.take())
{
observer.disconnect();
}
let _: Option<Closure<dyn FnMut(js_sys::Array, ResizeObserver)>> =
observer_closure_for_cleanup
.try_borrow_mut()
.ok()
.and_then(|mut closure_slot: RayTraceObserverCallbackSlot<'_>| closure_slot.take());
let _: Option<Closure<dyn FnMut()>> = debounce_for_cleanup
.try_borrow_mut()
.ok()
.and_then(|mut debounce_slot: RayTraceDebounceCallbackSlot<'_>| debounce_slot.take());
});
let cancelled_for_init: Rc<Cell<bool>> = cancelled.clone();
let pending_timers_for_init: RayTracePendingTimers = pending_timers.clone();
let observer_closure_cell_for_init: RayTraceObserverClosureOwner =
observer_closure_cell.clone();
raytrace_schedule_one_shot(pending_timers_for_init.clone(), 0, || {
draw_game_3d_loading(
RAYTRACE_WEBGPU_LOADING_CANVAS_SELECTOR,
RAYTRACE_WEBGPU_CANVAS_SELECTOR,
);
});
spawn_local(async move {
let Some(canvas): Option<HtmlCanvasElement> =
query_raytrace_canvas(RAYTRACE_WEBGPU_CANVAS_SELECTOR)
else {
init_state.get_active().set(false);
init_state
.get_init_error_code()
.set(RAYTRACE_ERROR_CODE_INIT);
init_state.get_error_detail().set(String::new());
init_state.get_loaded().set(true);
return;
};
let scene: RayTraceScene = build_raytrace_scene();
let renderer: RayTraceRenderer =
match RayTraceRenderer::init(canvas, RayTraceBackend::WebGpu, &scene).await {
Ok(value) => value,
Err(error) => {
raytrace_record_error(
init_state.get_active(),
init_state.get_error_detail(),
init_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_INIT,
"webgpu init",
&error,
);
init_state.get_loaded().set(true);
return;
}
};
if cancelled_for_init.get() {
let mut stale: RayTraceRenderer = renderer;
stale.dispose();
return;
}
Console::log(format!(
"{RAYTRACE_LOG_PREFIX} {RAYTRACE_LOG_BACKEND_READY} (gpu)"
));
*renderer_rc.borrow_mut() = Some(renderer);
raytrace_set_loaded_delayed(
pending_timers_for_init,
init_state.get_loaded(),
RAYTRACE_LOADING_MIN_MILLIS,
);
let lights: LightingUniforms = build_raytrace_lighting();
let settings: RayTraceSettings = build_raytrace_settings();
let renderer_for_observer: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let observer_closure: Closure<dyn FnMut(js_sys::Array, ResizeObserver)> = Closure::wrap(
Box::new(move |_entries: js_sys::Array, _obs: ResizeObserver| {
let renderer_for_frame: Rc<RefCell<Option<RayTraceRenderer>>> =
renderer_for_observer.clone();
let resize_accepted: bool = match renderer_for_frame.try_borrow_mut() {
Ok(mut renderer_slot) => match renderer_slot.as_mut() {
Some(renderer) => raytrace_resize_backend_to_canvas(
renderer,
RAYTRACE_WEBGPU_CANVAS_SELECTOR,
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
),
None => true,
},
Err(_contended) => false,
};
if !resize_accepted {
Console::error(format!(
"{RAYTRACE_LOG_PREFIX} resize observer refused the new CSS box; \
the backend keeps its previous size"
));
}
}),
);
let observer_callback: Function = observer_closure
.as_ref()
.unchecked_ref::<Function>()
.clone();
if let Ok(mut closure_slot) = observer_closure_cell_for_init.try_borrow_mut() {
*closure_slot = Some(observer_closure);
}
if let Ok(resize_observer) = ResizeObserver::new(&observer_callback)
&& let Some(window_value) = window()
&& let Some(document_value) = window_value.document()
&& let Some(element) = document_value
.query_selector(RAYTRACE_WEBGPU_CANVAS_SELECTOR)
.ok()
.flatten()
{
resize_observer.observe(&element);
if let Ok(mut observer_slot) = observer_cell.try_borrow_mut() {
*observer_slot = Some(resize_observer);
}
}
let last_time: Rc<Cell<f64>> = Rc::new(Cell::new(-1.0));
let frame_count: Rc<Cell<u32>> = Rc::new(Cell::new(0));
let fps_timer: Rc<Cell<f64>> = Rc::new(Cell::new(0.0));
let renderer_for_loop: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let raf_clone: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_clone: RafClosureCell = closure_cell.clone();
let last_clone: Rc<Cell<f64>> = last_time.clone();
let frame_clone: Rc<Cell<u32>> = frame_count.clone();
let fps_clone: Rc<Cell<f64>> = fps_timer.clone();
let yaw_clone: Rc<Cell<f64>> = angles.yaw.clone();
let pitch_clone: Rc<Cell<f64>> = angles.pitch.clone();
let resize_dirty_for_loop: Rc<Cell<bool>> = resize_dirty.clone();
let cancelled_for_loop: Rc<Cell<bool>> = cancelled.clone();
let raf_for_detach: Rc<Cell<Option<i32>>> = raf_id.clone();
let cell_for_detach: RafClosureCell = closure_cell.clone();
let timers_for_detach: RayTracePendingTimers = pending_timers.clone();
let renderer_for_detach: Rc<RefCell<Option<RayTraceRenderer>>> = renderer_rc.clone();
let timers_for_loop: RayTracePendingTimers = pending_timers.clone();
let raf_closure: Closure<dyn FnMut()> = Closure::wrap(Box::new(move || {
if cancelled_for_loop.get() {
return;
}
if raytrace_canvas_detached(RAYTRACE_WEBGPU_CANVAS_SELECTOR) {
raytrace_release_loop(
cancelled_for_loop.clone(),
raf_for_detach.clone(),
cell_for_detach.clone(),
timers_for_detach.clone(),
renderer_for_detach.clone(),
);
return;
}
let Some(window_value): Option<Window> = window() else {
return;
};
let Some(performance): Option<Performance> = window_value.performance() else {
return;
};
let current_time: f64 = performance.now() / 1000.0;
let prev: f64 = last_clone.get();
let frame_time: f64 = if prev < 0.0 {
1.0 / 60.0
} else {
current_time - prev
};
let anim_time: f64 = frame_time.min(0.25);
last_clone.set(current_time);
if loop_state.get_auto_rotate().get() {
yaw_clone.set(yaw_clone.get() + RAYTRACE_AUTO_YAW_SPEED * anim_time);
}
if resize_dirty_for_loop.get() {
resize_dirty_for_loop.set(false);
}
let running: bool = loop_state.get_running().get();
let render_result: Option<Result<(), JsValue>> = if running {
renderer_for_loop.try_borrow_mut().ok().and_then(
|mut renderer_slot: RefMut<'_, Option<RayTraceRenderer>>| {
let renderer: &mut RayTraceRenderer = renderer_slot.as_mut()?;
if !raytrace_resize_backend_to_canvas(
renderer,
RAYTRACE_WEBGPU_CANVAS_SELECTOR,
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
) {
return None;
}
let width: u32 =
raytrace_canvas_backing_width(RAYTRACE_WEBGPU_CANVAS_SELECTOR);
let height: u32 =
raytrace_canvas_backing_height(RAYTRACE_WEBGPU_CANVAS_SELECTOR);
let camera: Camera3D = build_raytrace_camera(
yaw_clone.get(),
pitch_clone.get(),
width,
height,
);
Some(renderer.render(&camera, &lights, &settings))
},
)
} else {
None
};
if running {
match render_result {
Some(Ok(())) => {
raytrace_clear_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
);
if !loop_state.get_loaded().get() {
raytrace_set_loaded_delayed(
timers_for_loop.clone(),
loop_state.get_loaded(),
RAYTRACE_LOADING_MIN_MILLIS,
);
}
}
Some(Err(error)) => {
raytrace_record_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_RENDER,
"webgpu render",
&error,
);
}
None => {
if loop_state.get_error_detail().get().is_empty() {
raytrace_record_error(
loop_state.get_active(),
loop_state.get_error_detail(),
loop_state.get_init_error_code(),
RAYTRACE_ERROR_CODE_RENDER,
"webgpu render",
&JsValue::NULL,
);
}
}
}
}
if running {
frame_clone.set(frame_clone.get() + 1);
fps_clone.set(fps_clone.get() + frame_time);
if fps_clone.get() >= 1.0 {
let fps: f64 = f64::from(frame_clone.get()) / fps_clone.get();
loop_state.get_fps().set(fps);
frame_clone.set(0);
fps_clone.set(0.0);
}
}
let Some(raf_closure_ref): Option<&'static Closure<dyn FnMut()>> = cell_clone.try_get()
else {
return;
};
let next_id: i32 = window_value
.request_animation_frame(raf_closure_ref.as_ref().unchecked_ref())
.unwrap_or_default();
if cancelled_for_loop.get() {
raf_clone.set(None);
} else {
raf_clone.set(Some(next_id));
}
}));
let _: Result<(), _> = closure_cell.try_set(raf_closure);
let Some(start_window): Option<Window> = window() else {
return;
};
let Some(start_raf_ref): Option<&'static Closure<dyn FnMut()>> = closure_cell.try_get()
else {
return;
};
let start_id: i32 = start_window
.request_animation_frame(start_raf_ref.as_ref().unchecked_ref())
.unwrap_or_default();
raf_id.set(Some(start_id));
});
}
pub(crate) fn raytrace_on_toggle_pause(running: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
let current: bool = running.get();
running.set(!current);
}))
}
pub(crate) fn raytrace_on_toggle_auto_rotate(
auto_rotate: Signal<bool>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
let current: bool = auto_rotate.get();
auto_rotate.set(!current);
}))
}
pub(crate) fn raytrace_on_reset_camera(angles: RayTraceCameraAngles) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
angles.yaw.set(0.6);
angles.pitch.set(0.25);
}))
}
fn client_x_from_event(event: &Event) -> f64 {
Reflect::get(
event.as_ref(),
&JsValue::from_str(RAYTRACE_EVENT_PROPERTY_CLIENT_X),
)
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or_default()
}
fn client_y_from_event(event: &Event) -> f64 {
Reflect::get(
event.as_ref(),
&JsValue::from_str(RAYTRACE_EVENT_PROPERTY_CLIENT_Y),
)
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or_default()
}
fn raytrace_apply_drag(dx: f64, dy: f64, angles: RayTraceCameraAngles, auto_rotate: Signal<bool>) {
let yaw: f64 = angles.yaw.get() - dx * RAYTRACE_DRAG_SENSITIVITY;
let pitch: f64 = (angles.pitch.get() + dy * RAYTRACE_DRAG_SENSITIVITY).clamp(
-FRAC_PI_2 + RAYTRACE_PITCH_CLAMP,
FRAC_PI_2 - RAYTRACE_PITCH_CLAMP,
);
angles.yaw.set(yaw);
angles.pitch.set(pitch);
auto_rotate.set(false);
}
pub(crate) fn raytrace_on_pointer_move(
angles: RayTraceCameraAngles,
auto_rotate: Signal<bool>,
last_pointer: Rc<Cell<Option<(f64, f64)>>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |event: Event| {
let last: Option<(f64, f64)> = last_pointer.get();
let Some((last_x, last_y)) = last else {
return;
};
let client_x: f64 = client_x_from_event(&event);
let client_y: f64 = client_y_from_event(&event);
last_pointer.set(Some((client_x, client_y)));
raytrace_apply_drag(
client_x - last_x,
client_y - last_y,
angles.clone(),
auto_rotate,
);
}))
}
pub(crate) fn raytrace_on_pointer_down(
last_pointer: Rc<Cell<Option<(f64, f64)>>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |event: Event| {
let client_x: f64 = client_x_from_event(&event);
let client_y: f64 = client_y_from_event(&event);
last_pointer.set(Some((client_x, client_y)));
}))
}
pub(crate) fn raytrace_on_pointer_up(
last_pointer: Rc<Cell<Option<(f64, f64)>>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
last_pointer.set(None);
}))
}
fn first_touch_client(event: &Event) -> (f64, f64) {
let touches_value: JsValue = Reflect::get(
event.as_ref(),
&JsValue::from_str(RAYTRACE_EVENT_PROPERTY_TOUCHES),
)
.ok()
.unwrap_or(JsValue::NULL);
let touches: Array = touches_value.unchecked_into();
if touches.length() == 0 {
return (0.0, 0.0);
}
let touch: JsValue = touches.get(0);
let client_x: f64 = Reflect::get(&touch, &JsValue::from_str(RAYTRACE_EVENT_PROPERTY_CLIENT_X))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or_default();
let client_y: f64 = Reflect::get(&touch, &JsValue::from_str(RAYTRACE_EVENT_PROPERTY_CLIENT_Y))
.ok()
.and_then(|value: JsValue| value.as_f64())
.unwrap_or_default();
(client_x, client_y)
}
pub(crate) fn raytrace_on_touch_start(
last_pointer: Rc<Cell<Option<(f64, f64)>>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |event: Event| {
if event.cancelable() {
event.prevent_default();
}
let (client_x, client_y): (f64, f64) = first_touch_client(&event);
last_pointer.set(Some((client_x, client_y)));
}))
}
pub(crate) fn raytrace_on_touch_move(
angles: RayTraceCameraAngles,
auto_rotate: Signal<bool>,
last_pointer: Rc<Cell<Option<(f64, f64)>>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |event: Event| {
if event.cancelable() {
event.prevent_default();
}
let last: Option<(f64, f64)> = last_pointer.get();
let Some((last_x, last_y)) = last else {
return;
};
let (client_x, client_y): (f64, f64) = first_touch_client(&event);
last_pointer.set(Some((client_x, client_y)));
raytrace_apply_drag(
client_x - last_x,
client_y - last_y,
angles.clone(),
auto_rotate,
);
}))
}
pub(crate) fn raytrace_on_touch_end(
last_pointer: Rc<Cell<Option<(f64, f64)>>>,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |event: Event| {
if event.cancelable() {
event.prevent_default();
}
last_pointer.set(None);
}))
}
pub(crate) fn raytrace_on_tab_select(
tab: Signal<RayTraceTab>,
value: RayTraceTab,
fullscreen: UseRayTraceFullscreen,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
fullscreen.get_canvas_2d().set(false);
fullscreen.get_web_gl().set(false);
fullscreen.get_web_gpu().set(false);
tab.set(value);
}))
}
pub(crate) fn enter_raytrace_fullscreen(tab: Signal<bool>) {
tab.set(true);
Router::overlay_push_state();
UseEuvLayout::apply_cached_insets();
let Some(window_value): Option<Window> = window() else {
return;
};
let event: Result<Event, JsValue> = Event::new(RAYTRACE_EVENT_RESIZE);
if let Ok(event) = event {
let _: Result<bool, JsValue> = window_value.dispatch_event(&event);
}
}
pub(crate) fn exit_raytrace_fullscreen(tab: Signal<bool>) {
tab.set(false);
UseEuvLayout::apply_cached_insets();
let Some(window_value): Option<Window> = window() else {
return;
};
let event: Result<Event, JsValue> = Event::new(RAYTRACE_EVENT_RESIZE);
if let Ok(event) = event {
let _: Result<bool, JsValue> = window_value.dispatch_event(&event);
}
}
pub(crate) fn exit_raytrace_fullscreen_from_popstate(tab: Signal<bool>) {
tab.set(false);
UseEuvLayout::apply_cached_insets();
let Some(window_value): Option<Window> = window() else {
return;
};
let event: Result<Event, JsValue> = Event::new(RAYTRACE_EVENT_RESIZE);
if let Ok(event) = event {
let _: Result<bool, JsValue> = window_value.dispatch_event(&event);
}
}
pub(crate) fn use_raytrace_fullscreen_popstate(state: UseRayTraceFullscreen) -> usize {
Router::register_popstate_guard(Rc::new(move || {
if state.get_canvas_2d().get() {
exit_raytrace_fullscreen_from_popstate(state.get_canvas_2d());
true
} else if state.get_web_gl().get() {
exit_raytrace_fullscreen_from_popstate(state.get_web_gl());
true
} else if state.get_web_gpu().get() {
exit_raytrace_fullscreen_from_popstate(state.get_web_gpu());
true
} else {
false
}
}))
}
pub(crate) fn raytrace_on_enter_fullscreen(tab: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
enter_raytrace_fullscreen(tab);
}))
}
pub(crate) fn raytrace_on_exit_fullscreen(tab: Signal<bool>) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
exit_raytrace_fullscreen(tab);
Router::overlay_back(None);
}))
}