import { star_kawaii_background } from "../assets/components/backgrounds/star_kawaii_background.mms"
import { voxel_terrain } from "../assets/components/floors/voxel_terrain.mms"
import { bisket_shirt_physics } from "../assets/components/secondary_motion/bisket-shirt-physics.mms"
import { bisket_colliders } from "../assets/components/colliders/bisket.mms"
// bisket-vr-only-example scene
//
// Shared VR room-scale demo using the canonical bisket avatar (bisket.glb).
// Mirrors examples/vr-input.{rs,mms} topology but with the head-driven
// AVC convention (head_bone="J_Bip_C_Head") and the bisket model.
//
// Drivers:
// - InputXR.on(): HMD pose → driven_t (translation + rotation)
// - XRHand(Left/Right, Grip): hand pose → splices into J_Bip_{L,R}_Hand
// (TwoBoneIK on upper/lower arm when chain resolves)
// - CXR (CameraXR): direct child of AVC, re-parented under J_Bip_C_Head.
// XR runtime overrides pose; no manual flip needed.
//
// Topology (after AvatarControlSystem init):
// ED
// └── InputXR
// └── T (driven_t)
// └── AVC
// ├── TransformForkTRS (body pipeline root)
// │ QuatYawFollow { threshold, rate, initial_yaw: π }
// │ T ← model_root (y auto-calibrated from J_Bip_C_Head)
// │ └── GLTF { EM } → ... → J_Bip_C_Head
// │ └── CXR ← re-parented here
// ├── head_mount → J_Bip_C_Head (fixed driver mount, yaw offset π)
// ├── XRHand(Left, Grip) ─→ TwoBoneIK on left arm chain
// └── XRHand(Right, Grip) ─→ TwoBoneIK on right arm chain
//
// To run:
// cargo run --release --example bisket-vr-only-example
// --- Renderer settings ---
RendererSettings {
window_size(640, 480)
}
BGC {
C.rgba(0.9, 0.5, 0.3, 1.0)
}
AL.rgb(0.10, 0.11, 0.16)
Clock.bpm(60) {}
RenderGraph {
EmissivePass {
BlurPass {
radius_ndc(0.06)
half_res(true)
}
}
Bloom {
intensity(0.90)
radius_ndc(0.06)
emissive_scale(1.2)
half_res(true)
}
}
// --- Directional light ---
T.position(0.15, -0.45, 1.0) {
DL {
intensity(1.1)
color(1.0, 0.98, 0.95)
}
}
// --- Terrain + back wall so the room has visual reference ---
// ED {
T.position(0.0, -6.0, 0.0) {
voxel_terrain({ length = 48 width = 48 })
}
// back wall
T.position(0.0, 2.15, -7.2).scale(8.8, 3.6, 0.24) {
R.cube() { C.rgba(0.95, 0.55, 0.22, 1.0) }
}
let repro_cube_a_transform = T.position(-0.9, -0.44, -1.0) {
name = "repro_cube_a_transform"
Transition {
duration_beats(1.0)
ease_in_out_sine()
replace_same_target()
}
T.scale(0.50, 0.50, 0.50) {
name = "repro_cube_a"
R.cube() {
C.rgba(1.0, 0.88, 0.15, 1.0)
EM.on()
Raycastable.enabled()
}
}
}
let repro_cube_b_transform = T.position(0.0, -0.44, -0.7) {
name = "repro_cube_b_transform"
Transition {
duration_beats(1.0)
ease_in_out_sine()
replace_same_target()
}
T.scale(0.50, 0.50, 0.50) {
name = "repro_cube_b"
R.cube() {
C.rgba(1.0, 0.35, 0.78, 1.0)
EM.on()
Raycastable.enabled()
}
}
}
let repro_cube_c_transform = T.position(0.9, -0.44, -1.1) {
name = "repro_cube_c_transform"
Transition {
duration_beats(1.0)
ease_in_out_sine()
replace_same_target()
}
T.scale(0.50, 0.50, 0.50) {
name = "repro_cube_c"
R.cube() {
C.rgba(0.10, 0.95, 1.0, 1.0)
EM.on()
Raycastable.enabled()
}
}
}
let repro_rotating_parent = T.position(2.0, 0.15, 1.0) {
name = "repro_rotating_parent"
Transition {
duration_beats(1.0)
ease_in_out_sine()
replace_same_target()
}
repro_cube_a_transform
repro_cube_b_transform
repro_cube_c_transform
}
repro_rotating_parent
Animation.looping().length(4.0) {
Keyframe.at(0.0) {
repro_rotating_parent.update_transform([2.0, 0.15, 1.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
repro_cube_a_transform.update_transform([-0.9, -0.44, -1.0], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
repro_cube_b_transform.update_transform([0.0, -0.44, -0.7], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
repro_cube_c_transform.update_transform([0.9, -0.44, -1.1], [0.0, 0.0, 0.0], [1.0, 1.0, 1.0])
}
Keyframe.at(1.0) {
repro_rotating_parent.update_transform([2.0, 0.15, 1.0], [0.0, 0.65, 0.0], [1.0, 1.0, 1.0])
repro_cube_a_transform.update_transform([-1.25, -0.10, -0.55], [0.0, 1.570795, 0.0], [1.0, 1.0, 1.0])
repro_cube_b_transform.update_transform([0.0, 0.45, -1.35], [1.570795, 0.0, 0.0], [1.0, 1.0, 1.0])
repro_cube_c_transform.update_transform([1.25, -0.20, -0.55], [0.0, 0.0, 1.570795], [1.0, 1.0, 1.0])
}
Keyframe.at(2.0) {
repro_rotating_parent.update_transform([2.0, 0.15, 1.0], [0.0, 1.35, 0.0], [1.0, 1.0, 1.0])
repro_cube_a_transform.update_transform([-0.35, 0.55, -1.45], [0.0, 3.14159, 0.0], [1.0, 1.0, 1.0])
repro_cube_b_transform.update_transform([0.0, -0.55, -0.25], [3.14159, 0.0, 0.0], [1.0, 1.0, 1.0])
repro_cube_c_transform.update_transform([0.35, 0.55, -1.45], [0.0, 0.0, 3.14159], [1.0, 1.0, 1.0])
}
Keyframe.at(3.0) {
repro_rotating_parent.update_transform([2.0, 0.15, 1.0], [0.0, 2.10, 0.0], [1.0, 1.0, 1.0])
repro_cube_a_transform.update_transform([-1.35, -0.25, -1.35], [0.0, 4.712385, 0.0], [1.0, 1.0, 1.0])
repro_cube_b_transform.update_transform([0.0, 0.30, 0.10], [4.712385, 0.0, 0.0], [1.0, 1.0, 1.0])
repro_cube_c_transform.update_transform([1.35, -0.25, -1.35], [0.0, 0.0, 4.712385], [1.0, 1.0, 1.0])
}
}
// }
// --- Background sun + stars ---
BG.occlusion_and_lighting() {
star_kawaii_background([1.0, 0.88, 0.42, 1.0])
T.position(2.0, 1.5, -8.0).scale(3.5, 3.5, 3.5) {
R.circle2d() {
C.rgba(1.0, 0.85, 0.15, 1.0)
EM.on()
}
T.position(-0.35, 0.35, -0.01).scale(0.45, 0.45, 0.45) {
R.circle2d() {
C.rgba(1.0, 1.0, 1.0, 1.0)
EM.on()
}
}
}
}
// --- bisket avatar — VR single-input topology ---
//
// initial_yaw(π): body starts facing -Z to match OpenXR HMD rest-forward.
// camera_bone == head_bone: head bone is the eye anchor (CXR re-parented here;
// model_root.y auto-calibrated so head bone sits at HMD height).
ED {
T {
InputXR.on() {
InputXRGamepad {
locomotion()
speed(1.5)
}
T {
AVC {
head_bone("J_Bip_C_Head")
camera_bone("J_Bip_C_Head")
left_hand_bone("J_Bip_L_Hand")
right_hand_bone("J_Bip_R_Hand")
initial_yaw(3.14159)
// Body-local pole hints. The solver rotates them by the
// current model_root world rotation each tick, so author them
// as anatomical body-space directions. Bias slightly downward
// as well as outward so elbows drop when the hands pull back
// toward the chest instead of lifting upward.
left_arm_pole_direction([ 1, -0.35, 1])
right_arm_pole_direction([-1, -0.35, 1])
hand_rotation_smoothing(220.0)
// Trial: yaw inward 90 degrees, then apply the opposite
// mirrored pitch branch in the post-yaw local frame.
// Keep the near-correct left/right branches, then add a shared
// post-correction twist back toward the thumb by ~40 degrees.
hand_grip_rotation_left([-0.6408564, 0.29883623, 0.29883623, 0.6408564])
hand_grip_rotation_right([-0.6408564, -0.29883623, -0.29883623, 0.6408564])
T {
GLTF.new("assets/models/bisket.glb") {
EM.on()
PoseCapture { label("Bisket") asset_name("bisket") }
bisket_colliders()
bisket_shirt_physics(false)
}
}
// Camera wrapped in T(eye_offset). The T's translation is the
// eye position relative to the head bone pivot (head-local
// frame; +Y up, +Z forward). AVC discovers it during init and
// reparents the T under J_Bip_C_Head.
// This authored offset is used to place the head pivot relative
// to the fixed HMD pose AND to offset the whole avatar baseline,
// so changing it moves body/neck together instead of crushing the
// upper torso with a head-only correction.
T.position(0.0, 0.08, 0.12) {
name = "xr_camera_wrapper"
CXR { Pointer {} }
}
// Tracked Grip controllers — re-parented to lower-arm bones
// by AVC, drive J_Bip_{L,R}_Hand via TwoBoneIK.
XRHand.new(true, Left, Grip)
.laser_from_avatar_finger("[name='J_Bip_L_Middle1']", "[name='J_Bip_L_Middle2']", "[name='J_Bip_L_Middle3']") {
T { Pointer {} }
}
XRHand.new(true, Right, Grip)
.laser_from_avatar_finger("[name='J_Bip_R_Middle1']", "[name='J_Bip_R_Middle2']", "[name='J_Bip_R_Middle3']") {
T { Pointer {} }
}
}
// debug camera marker
OV {
T.scale(0.06, 0.06, 0.12) {
R.cube() {
C.rgba(0.00, 1.0, 1.0, 0.5)
EM.on()
}
}
}
}
}
}
}
// // --- Controller debug cubes (Aim pose, rotation-smoothed) ---
// //
// // Sit alongside the avatar — useful to see raw controller tracking before
// // any IK/splice transforms touch them.
// InputXR.on() {
// T {
// // T.position(0.0, 1.85, 0.6) {
// // RendererStats {
// // camera_target(Xr)
// // }
// // }
// XRHand.new(true, Left, Aim) {
// T.scale(0.06, 0.06, 0.12) {
// TransformForkTRS {
// TransformMapTranslation {}
// TransformMapRotation {
// QuatTemporalFilter.smoothing_factor(220.0)
// }
// TransformMapScale {}
// T {
// R.cube() { C.rgba(0.10, 0.90, 1.00, 1.0) }
// }
// }
// }
// }
// XRHand.new(true, Right, Aim) {
// T.scale(0.06, 0.06, 0.12) {
// TransformForkTRS {
// TransformMapTranslation {}
// TransformMapRotation {
// QuatTemporalFilter.smoothing_factor(220.0)
// }
// TransformMapScale {}
// T {
// R.cube() { C.rgba(1.00, 0.35, 0.35, 1.0) }
// }
// }
// }
// }
// // Grip pose markers (yellow = left, green = right) — compare with Aim above.
// XRHand.new(true, Left, Grip) {
// T.scale(0.05, 0.05, 0.10) {
// T {
// R.cube() { C.rgba(1.0, 1.0, 0.0, 1.0) EM.on() }
// }
// }
// }
// XRHand.new(true, Right, Grip) {
// T.scale(0.05, 0.05, 0.10) {
// T {
// R.cube() { C.rgba(0.2, 1.0, 0.2, 1.0) EM.on() }
// }
// }
// }
// }
// }
// --- OpenXR runtime ---
XR.on()
// pink yellow and orange lighting
T.position(0, 2, 0) {
DL {
intensity(0.8)
color(1.0, 0.45, 0.85)
}
}
T.position(-1, -1, 0) {
DL {
intensity(0.8)
color(1.0, 0.9, 1.0)
}
}
T.position(1, -1, 0) {
DL {
intensity(0.8)
color(1.0, 0.6, 0.15)
}
}