#![cfg(not(target_arch = "wasm32"))]
use scena::{
DiagnosticSeverity, InteractiveGltfViewer, OrbitControlAction, PlatformSurface, PointerButton,
PointerEvent, PointerEventKind, RenderMode, Renderer, Scene, SurfaceEvent,
interactive_gltf_viewer,
};
#[test]
fn interactive_gltf_viewer_builds_load_instantiate_frame_prepare_render_through_descriptor_surface()
{
let viewer: InteractiveGltfViewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(96, 64),
)
.with_default_light()
.with_render_mode(RenderMode::OnChange)
.build()
.expect("interactive viewer builds against a descriptor surface");
assert_eq!(
viewer.renderer().capabilities().backend,
scena::Backend::SurfaceDescriptor
);
assert_eq!(viewer.renderer().stats().target_width, 96);
assert_eq!(viewer.renderer().stats().target_height, 64);
let active_camera = viewer.camera();
let _ = active_camera;
let outcome = {
let mut viewer = viewer;
viewer
.render_next_frame()
.expect("interactive viewer renders one frame")
};
assert!(
outcome.draw_calls > 0 || outcome.primitives == 0,
"render_next_frame must report a coherent draw stat (got {outcome:?})",
);
}
#[test]
fn interactive_gltf_viewer_forwards_surface_resize_events_to_renderer() {
let mut viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(48, 32),
)
.build()
.expect("interactive viewer builds");
viewer
.handle_surface_event(SurfaceEvent::Resize {
width: 96,
height: 64,
})
.expect("handle_surface_event forwards resize");
assert_eq!(viewer.renderer().stats().target_width, 96);
assert_eq!(viewer.renderer().stats().target_height, 64);
}
#[test]
fn interactive_gltf_viewer_diagnostics_accessor_reports_renderer_diagnostics() {
let viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(64, 64),
)
.with_default_light()
.with_default_environment()
.build()
.expect("interactive viewer builds");
let diagnostics = viewer.diagnostics();
let _ = diagnostics
.iter()
.any(|diagnostic| diagnostic.severity() != DiagnosticSeverity::Info);
}
#[test]
fn interactive_gltf_viewer_with_orbit_controls_attaches_controller_seeded_from_framing() {
let viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(96, 64),
)
.with_orbit_controls()
.build()
.expect("interactive viewer builds with orbit controls");
let controls = viewer
.orbit_controls()
.expect("with_orbit_controls populates the controller field");
assert!(
controls.distance() > 0.0 && controls.distance().is_finite(),
"framed orbit controls must seed a positive finite distance, got {}",
controls.distance()
);
assert!(
controls.yaw_radians().abs() < f32::EPSILON,
"orbit controls start unrotated; yaw={}",
controls.yaw_radians()
);
}
#[test]
fn interactive_gltf_viewer_handle_pointer_event_orbits_and_applies_to_scene() {
let mut viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(96, 64),
)
.with_orbit_controls()
.build()
.expect("interactive viewer builds with orbit controls");
let camera_node = viewer
.scene()
.camera_node(viewer.camera())
.expect("active camera has a node");
let translation_before = viewer
.scene()
.world_transform(camera_node)
.expect("camera world transform")
.translation;
let press = viewer
.handle_pointer_event(PointerEvent {
kind: PointerEventKind::Pressed,
position: (32.0, 32.0),
button: Some(PointerButton::Primary),
delta: (0.0, 0.0),
scroll_delta: 0.0,
})
.expect("press event handled");
assert_eq!(press, OrbitControlAction::BeginOrbit);
let drag = viewer
.handle_pointer_event(PointerEvent {
kind: PointerEventKind::Moved,
position: (132.0, 32.0),
button: Some(PointerButton::Primary),
delta: (100.0, 0.0),
scroll_delta: 0.0,
})
.expect("drag event handled");
assert_eq!(drag, OrbitControlAction::Orbit);
let translation_after = viewer
.scene()
.world_transform(camera_node)
.expect("camera world transform")
.translation;
let dx = translation_after.x - translation_before.x;
let dz = translation_after.z - translation_before.z;
assert!(
dx * dx + dz * dz > 1e-6,
"100 px horizontal drag must rotate the camera around target; \
translation moved from {translation_before:?} to {translation_after:?}",
);
}
#[test]
fn interactive_gltf_viewer_handle_pointer_event_no_op_without_orbit_controls() {
let mut viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(64, 64),
)
.build()
.expect("interactive viewer builds without orbit controls");
assert!(viewer.orbit_controls().is_none());
let action = viewer
.handle_pointer_event(PointerEvent {
kind: PointerEventKind::Pressed,
position: (10.0, 10.0),
button: Some(PointerButton::Primary),
delta: (0.0, 0.0),
scroll_delta: 0.0,
})
.expect("event routes safely without orbit controls");
assert_eq!(action, OrbitControlAction::None);
}
#[test]
fn interactive_gltf_viewer_pick_at_forwards_to_scene_pick_with_assets() {
let viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(96, 64),
)
.build()
.expect("interactive viewer builds");
let viewport = scena::Viewport::new(96, 64, 1.0).expect("viewport validates");
let direct = viewer
.scene()
.pick_with_assets(
viewer.camera(),
scena::CursorPosition::physical(48.0, 32.0),
viewport,
viewer.assets(),
)
.expect("direct pick_with_assets runs");
let convenience = viewer.pick_at(48.0, 32.0).expect("viewer.pick_at runs");
assert_eq!(direct, convenience);
}
#[test]
fn interactive_gltf_viewer_pick_and_select_at_updates_interaction_state_on_hit() {
let mut viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(96, 64),
)
.build()
.expect("interactive viewer builds");
let result = viewer
.pick_and_select_at(48.0, 32.0)
.expect("pick_and_select_at runs");
if let Some(hit) = result {
assert_eq!(
viewer.scene().interaction().primary_selection(),
Some(hit.target()),
"viewer.pick_and_select_at must promote the hit to primary selection"
);
}
}
#[test]
fn interactive_gltf_viewer_pick_at_returns_none_for_off_canvas_ray() {
let viewer = interactive_gltf_viewer(
"tests/assets/gltf/khronos/UnlitTest/UnlitTest.gltf",
PlatformSurface::native_window(96, 64),
)
.build()
.expect("interactive viewer builds");
let hit = viewer.pick_at(10_000.0, 10_000.0);
let hit = hit.expect("off-canvas pick must still return Ok");
assert!(
hit.is_none(),
"off-canvas ray must report no hit; got {hit:?}",
);
}
#[test]
fn renderer_headless_default_yields_canonical_first_render_dimensions() {
let renderer = Renderer::headless_default().expect("headless default builds");
assert_eq!(renderer.stats().target_width, 800);
assert_eq!(renderer.stats().target_height, 600);
}
#[test]
fn scene_with_default_camera_returns_active_camera_in_one_call() {
let (scene, camera) = Scene::with_default_camera().expect("default scene + camera builds");
assert_eq!(
scene.active_camera(),
Some(camera),
"with_default_camera must register the camera as the active camera",
);
}