Skip to content

scenix v1.9.0 — XR, Audio, And Physics Bridges

Choose a tag to compare

@github-actions github-actions released this 21 Oct 04:11
· 2 commits to main since this release
8572cf4

Scenix v1.9.0 — XR, Audio, And Physics Bridges

Scenix 1.9.0 adds three optional, no_std-capable CPU crates that bring
simulation-adjacent systems onto the scenix vocabulary without an engine,
OpenXR runtime, or audio SDK — scenix-xr (session planning, controller
bays, hand frames, anchors, planes, hit testing, light estimation),
scenix-audio (spatial mixing, Doppler, analysis, streamed buffer health),
and scenix-physics (a deterministic penalty-based rigid-body solver with
sensors, queries, sleeping, and a jump-capable character controller) — plus
CPU-bridge scene nodes, camera stereo math, a WebXR fallback plan,
debug helpers, and facade integration. The renderer pipeline itself is
untouched: everything springs off the same deterministic CPU rules whether
the host is a game loop, a wasm demo, or an XR runtime host.

Highlights

  • scenix-xr is a standalone CPU crate (std/serde): XrSessionPlan
    validates mode/reference-space/feature combinations with
    XrSessionPlanError diagnostics and walks an explicit
    Idle→Requesting→Starting→Active→Paused/Ending→Ended lifecycle
    (XrSessionState::can_transition); XrControllerBay tracks both hands
    from pointer/grip poses with velocity-based kinematic prediction;
    XrHandFrame models the 26-slot hand profile (JOINT_WRIST,
    JOINT_THUMB_TIP, JOINT_INDEX_TIP) with tracked-joint accounting and
    thumb-index pinch distance; anchors (XrAnchorStore with age-based
    pruning), planes (XrPlaneStore), hit testing (XrHitTestProbe against
    floor/wall/window surfaces), and backward-compatible ambient-vs-light
    estimation plan the field systems; XrFramePlan carries per-frame stats,
    the swapchain XrViewport (tensor-scaled with the XR_MAX_VIEWPORT_SCALE
    clamp), and builds the stereo rig directly.
  • scenix-audio is a standalone CPU crate: AudioListener::axes builds the
    orthonormal listeners; compute_spatial_decision folds pan weights,
    four attenuation models (None/Linear/Inverse/InverseSquare with
    gain-clamped far curves), WebAudio-convention cones, and
    DopplerModel::shift (speed-of-sound ratios, factor 0..=1, clamped to
    0.25..=4) into one SpatialDecision; compute_spatial_gain finishes
    the stereo route. AudioStreamPlan tracks chunk buffering health
    (accept_chunk/promote_pending/consume_chunk + underrun counting) and
    AudioAnalyser meters mono/stereo input into RMS/peak plus three energy
    bands with smoothing. Constants like SPEED_OF_SOUND, STREAM_MAX_CHUNKS,
    and AUDIO_LISTENER_MAX_SOURCES bound the field sizes.
  • scenix-physics is a standalone CPU crate: PhysicsWorld::earth runs a
    penalty-based solver — O(n²) AABB broad phase with collider groups,
    analytic narrow phase (sphere/box/capsule/plane pairs), restitution plus
    Coulomb friction plus a Baumgarte positional push (CONTACT_RESOLVE_PERCENT
    0.2, CONTACT_SLOP 5 mm), real substeps (MAX_SUBSTEPS 4 at ≤
    MAX_SUBSTEP_DT 1/120 s), linear speed clamping, and frame-threshold
    sleeping with wake events — all deterministic against body ordering.
    Collider attaches with sensor/density controls for group-filtered
    triggers; cast_ray/overlap_sphere answer spatial questions;
    CharacterState::move_and_slide walks a kinematic capsule with slope
    limits, step offsets, jumps, and probe-based ground snapping;
    PhysicsTransformSync batches the per-frame write list for scene
    integration.
  • scenix-scene adds NodeKind::XrAnchor{anchor_id},
    NodeKind::AudioSource{source_id, listener_id}, and
    NodeKind::PhysicsBody{body_id, collider_id} riders on the scene schema;
    scenix-core adds the matching XrSessionId, XrAnchorId,
    AudioSourceId, AudioListenerId, PhysicsWorldId, PhysicsBodyId,
    and ColliderId newtypes.
  • scenix-camera gains the stereo surface: XrFov four-angle eye frusta,
    XrEyeProjection (symmetric helper plus the raw WebXR layout) producing
    WebGPU-convention projection matrices, and XrCameraRig coupling a head
    transform with eye separation into left/right view and view-projection
    matrices.
  • scenix-wasm gains an xr feature and the front-end fallback plan:
    webxr_fallback_plan(granted) splits granted runtimes from the desktop
    emulation, emulated_headset_rig builds the standing desk stereo rig, and
    emulated_controller_pose parks a fallback controller down-forward from
    the head — the demo never dead-ends when navigator.xr is missing.
  • scenix-helpers adds the debug pass surface: PoseAxesHelper draws an
    oriented triad (plus a -Z forward stick) for controllers and head rigs,
    HandSkeletonHelper wires the 26-slot hand profile through
    HAND_LINKS, and (behind the new physics feature)
    ColliderDebugHelper wireframes sensors apart from solid colliders via
    debug_wireframe/debug_wireframe_world.
  • The facade gains xr, audio, physics, and bridges features. The
    bridge re-exports are curated lists because their module namespaces
    (xr, audio, physics, world, sync) would otherwise shadow the
    scene/input glob re-exports; scenix-math gains floor/ceil/round
    shims so no_std substeps and viewport math round identically on every
    target.

Small Example

use scenix::{
    AttenuationModel, AudioListener, AudioStreamPlan, AudioSource, CharacterInput,
    CharacterState, ColliderShape, DopplerModel, PhysicsBody, PhysicsWorld, PlaybackState, Quat,
    RigidBodyKind, StreamFormat, Transform, Vec3, XrControllerBay, XrFeatures, XrHand, XrPose,
    XrReferenceSpace, XrSessionPlan, XrSessionState, XrTrackingStatus,
};

// 1. XR: validate and present a room-scale VR plan.
let mut xr = XrSessionPlan::immersive_vr()
    .with_reference_space(XrReferenceSpace::Stage)
    .with_features(XrFeatures::room_scale_vr());
xr.validate()?;
for state in [
    XrSessionState::Requesting,
    XrSessionState::Starting,
    XrSessionState::Active,
] {
    xr.advance(state)?;
}

// Controller bays track pointer/grip poses; prediction extrapolates them.
let mut bay = XrControllerBay::new();
bay.update(
    XrHand::Right,
    XrPose::from_position(Vec3::new(0.25, 1.0, -0.6)),
    XrTrackingStatus::Tracked,
);

// 2. Audio: face a listener down-range and let the spatial mix decide.
let listener = AudioListener::axes(Vec3::new(0.0, 1.5, 2.0), Vec3::NEG_Z, Vec3::Y);
let mut source = AudioSource::new(1, PlaybackState::Playing);
source.position = Vec3::new(-1.0, 1.0, -1.0);
let decision = scenix::compute_spatial_decision(
    &listener,
    &source,
    AttenuationModel::Inverse,
    1.0,
    &DopplerModel::default(),
);
assert!(decision.audible);

// 3. Physics: walk a capsule across a floor.
let mut world = PhysicsWorld::earth();
world.add_solid(
    PhysicsBody::new(Transform::IDENTITY).with_kind(RigidBodyKind::Static),
    ColliderShape::boxy(20.0, 0.5, 20.0),
);
let player = world.add_body(
    PhysicsBody::new(Transform::new(Vec3::new(0.0, 1.4, 0.0), Quat::IDENTITY, Vec3::ONE))
        .with_kind(RigidBodyKind::KinematicPosition),
);
world.attach_collider(player, ColliderShape::capsule(0.35, 0.55));
let mut controller = CharacterState::new(player, 0.35, 0.55);
controller.move_and_slide(&mut world, &CharacterInput::move_towards(Vec3::X, 3.0, 1.0 / 60.0));

// 4. Streamed audio health stays deterministic for music-reactive scenes.
let mut plan = AudioStreamPlan::new(4, StreamFormat::PcmF32, 48_000, 2);
plan.accept_chunk();
assert!(plan.promote_pending());

Feature gates: xr, audio and physics are independent facade features;
bridges enables all three at once; scenix-wasm's xr feature carries
the browser fallback; scenix-helpers' physics feature carries the
collider wireframe helper. The renderer WGSL is untouched in this release.