A glue scene seats its body and pet on the bone's animated matrix (#2984)
Fixes #2980 A glue scene's seat read only a rotation and scale parked on global-sequence keys, once, when the scene was built. That lost a seat keyed in the scene's sequence: a custom `UI_CharacterSelect` turns its seat bone 180° and scales it 0.673 there, so the body stood backwards at about 1.5×. No stock scene keys its seat that way. The reference seats a child at the bone's whole animated matrix times the attachment position, every frame (`0x7140d8`-`0x714149`), after it animates the scene (`0x714000`). The body and the pet now take that matrix from the scene rig's pose each frame. This runs in `PostUpdate` after the rig's model pass, now the named set `RigModelPass`, and before propagation, so the seat and the scene share a frame. Test: `a_seat_takes_the_bones_whole_matrix_at_the_attachment`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Sam <samuv.eth@gmail.com>
This commit is contained in:
@@ -307,18 +307,12 @@ pub(crate) struct CreateScene {
|
|||||||
/// The character's stage spot, scene attachment 0 in Bevy model space (`ZERO` with no scene);
|
/// The character's stage spot, scene attachment 0 in Bevy model space (`ZERO` with no scene);
|
||||||
/// the body seats on attachment 0 at select too (`0x473039`).
|
/// the body seats on attachment 0 at select too (`0x473039`).
|
||||||
char_spot: Vec3,
|
char_spot: Vec3,
|
||||||
/// The stage's authored frame, attachment 0's bone rotation and uniform scale
|
|
||||||
/// ([`stage_frame`]); identity and 1.0 with no scene.
|
|
||||||
char_facing: Quat,
|
|
||||||
char_scale: f32,
|
|
||||||
/// The pet's spot, scene attachment 1 (`0x47306b` seats the secondary model there). Every
|
/// The pet's spot, scene attachment 1 (`0x47306b` seats the secondary model there). Every
|
||||||
/// `UI_*` scene authors exactly attachments 0 and 1, both on parentless root bones.
|
/// `UI_*` scene authors exactly attachments 0 and 1, both on parentless root bones.
|
||||||
pet_root: Entity,
|
pet_root: Entity,
|
||||||
pet_spot: Vec3,
|
pet_spot: Vec3,
|
||||||
/// Attachment 1's bone frame, read like [`Self::char_facing`]. UI_Human's holds the same
|
/// The family ramp scale of the pet standing at the seat.
|
||||||
/// −16.5° yaw and 0.97 scale as attachment 0; UI_Tauren's holds 0.7324 on the pet's bone alone.
|
pet_scale: f32,
|
||||||
pet_facing: Quat,
|
|
||||||
pet_stage_scale: f32,
|
|
||||||
/// The revision of the pet bake standing at [`Self::pet_spot`], so a re-bake happens once.
|
/// The revision of the pet bake standing at [`Self::pet_spot`], so a re-bake happens once.
|
||||||
pet_baked: Option<u64>,
|
pet_baked: Option<u64>,
|
||||||
/// The fog the spawn wrote; the scene rebuilds when it changes.
|
/// The fog the spawn wrote; the scene rebuilds when it changes.
|
||||||
@@ -364,11 +358,7 @@ struct ParkedScene {
|
|||||||
file: GlueSceneFile,
|
file: GlueSceneFile,
|
||||||
handle: Handle<M2Model>,
|
handle: Handle<M2Model>,
|
||||||
char_spot: Vec3,
|
char_spot: Vec3,
|
||||||
char_facing: Quat,
|
|
||||||
char_scale: f32,
|
|
||||||
pet_spot: Vec3,
|
pet_spot: Vec3,
|
||||||
pet_facing: Quat,
|
|
||||||
pet_stage_scale: f32,
|
|
||||||
stage: u64,
|
stage: u64,
|
||||||
clock_ms: u64,
|
clock_ms: u64,
|
||||||
}
|
}
|
||||||
@@ -382,12 +372,9 @@ impl CreateScene {
|
|||||||
handle: None,
|
handle: None,
|
||||||
spawned: false,
|
spawned: false,
|
||||||
char_spot: Vec3::ZERO,
|
char_spot: Vec3::ZERO,
|
||||||
char_facing: Quat::IDENTITY,
|
|
||||||
char_scale: 1.0,
|
|
||||||
pet_root,
|
pet_root,
|
||||||
pet_spot: Vec3::ZERO,
|
pet_spot: Vec3::ZERO,
|
||||||
pet_facing: Quat::IDENTITY,
|
pet_scale: 1.0,
|
||||||
pet_stage_scale: 1.0,
|
|
||||||
pet_baked: None,
|
pet_baked: None,
|
||||||
fog: None,
|
fog: None,
|
||||||
stage: 0,
|
stage: 0,
|
||||||
@@ -431,11 +418,7 @@ impl CreateScene {
|
|||||||
file,
|
file,
|
||||||
handle,
|
handle,
|
||||||
char_spot: self.char_spot,
|
char_spot: self.char_spot,
|
||||||
char_facing: self.char_facing,
|
|
||||||
char_scale: self.char_scale,
|
|
||||||
pet_spot: self.pet_spot,
|
pet_spot: self.pet_spot,
|
||||||
pet_facing: self.pet_facing,
|
|
||||||
pet_stage_scale: self.pet_stage_scale,
|
|
||||||
stage: self.stage,
|
stage: self.stage,
|
||||||
clock_ms: self.clock_ms,
|
clock_ms: self.clock_ms,
|
||||||
});
|
});
|
||||||
@@ -465,11 +448,7 @@ impl CreateScene {
|
|||||||
self.file = Some(p.file);
|
self.file = Some(p.file);
|
||||||
self.handle = Some(p.handle);
|
self.handle = Some(p.handle);
|
||||||
self.char_spot = p.char_spot;
|
self.char_spot = p.char_spot;
|
||||||
self.char_facing = p.char_facing;
|
|
||||||
self.char_scale = p.char_scale;
|
|
||||||
self.pet_spot = p.pet_spot;
|
self.pet_spot = p.pet_spot;
|
||||||
self.pet_facing = p.pet_facing;
|
|
||||||
self.pet_stage_scale = p.pet_stage_scale;
|
|
||||||
self.stage = p.stage;
|
self.stage = p.stage;
|
||||||
self.clock_ms = p.clock_ms;
|
self.clock_ms = p.clock_ms;
|
||||||
self.spawned = true;
|
self.spawned = true;
|
||||||
@@ -493,8 +472,6 @@ impl CreateScene {
|
|||||||
self.handle = None;
|
self.handle = None;
|
||||||
self.spawned = false;
|
self.spawned = false;
|
||||||
self.char_spot = Vec3::ZERO;
|
self.char_spot = Vec3::ZERO;
|
||||||
self.char_facing = Quat::IDENTITY;
|
|
||||||
self.char_scale = 1.0;
|
|
||||||
clear_pet(commands, self);
|
clear_pet(commands, self);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@@ -1072,14 +1049,7 @@ pub(super) fn sync_glue_scene(
|
|||||||
"glue scene: {name} resumed at {} ms on its own clock",
|
"glue scene: {name} resumed at {} ms on its own clock",
|
||||||
scene.clock_ms
|
scene.clock_ms
|
||||||
);
|
);
|
||||||
apply_yaw(
|
apply_yaw(&mut commands, booth.root, scene.char_spot, preview.yaw);
|
||||||
&mut commands,
|
|
||||||
booth.root,
|
|
||||||
scene.char_spot,
|
|
||||||
scene.char_facing,
|
|
||||||
scene.char_scale,
|
|
||||||
preview.yaw,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if !scene.spawned {
|
if !scene.spawned {
|
||||||
let Some(model) = scene.handle.as_ref().and_then(|h| m2s.get(h)) else {
|
let Some(model) = scene.handle.as_ref().and_then(|h| m2s.get(h)) else {
|
||||||
@@ -1090,12 +1060,6 @@ pub(super) fn sync_glue_scene(
|
|||||||
}
|
}
|
||||||
// The scene's light, in its own buffer so the portraits' studio light is untouched.
|
// The scene's light, in its own buffer so the portraits' studio light is untouched.
|
||||||
let stage = attachment_point(&model.skeleton, &model.attachments, 0).unwrap_or(Vec3::ZERO);
|
let stage = attachment_point(&model.skeleton, &model.attachments, 0).unwrap_or(Vec3::ZERO);
|
||||||
// The stage's frame, not just its point ([`stage_frame`]).
|
|
||||||
let (stage_facing, stage_scale) = model
|
|
||||||
.attachments
|
|
||||||
.iter()
|
|
||||||
.find(|a| a.id == 0)
|
|
||||||
.map_or((Quat::IDENTITY, 1.0), |a| stage_frame(model, a.bone));
|
|
||||||
let rig = if ghost {
|
let rig = if ghost {
|
||||||
ghost_rig()
|
ghost_rig()
|
||||||
} else {
|
} else {
|
||||||
@@ -1239,39 +1203,20 @@ pub(super) fn sync_glue_scene(
|
|||||||
scene_rig.finish(&mut commands);
|
scene_rig.finish(&mut commands);
|
||||||
scene.spawned = true;
|
scene.spawned = true;
|
||||||
scene.char_spot = stage;
|
scene.char_spot = stage;
|
||||||
scene.char_facing = stage_facing;
|
// The pet's seat: attachment 1 ([`seat_on_stage_bones`] follows its bone from here).
|
||||||
scene.char_scale = stage_scale;
|
|
||||||
// The pet's seat: attachment 1, frame and all.
|
|
||||||
scene.pet_spot =
|
scene.pet_spot =
|
||||||
attachment_point(&model.skeleton, &model.attachments, 1).unwrap_or(Vec3::ZERO);
|
attachment_point(&model.skeleton, &model.attachments, 1).unwrap_or(Vec3::ZERO);
|
||||||
let (pet_facing, pet_stage_scale) = model
|
|
||||||
.attachments
|
|
||||||
.iter()
|
|
||||||
.find(|a| a.id == 1)
|
|
||||||
.map_or((Quat::IDENTITY, 1.0), |a| stage_frame(model, a.bone));
|
|
||||||
scene.pet_facing = pet_facing;
|
|
||||||
scene.pet_stage_scale = pet_stage_scale;
|
|
||||||
// A rebuilt scene moved the seat, so the standing pet re-seats.
|
// A rebuilt scene moved the seat, so the standing pet re-seats.
|
||||||
scene.pet_baked = None;
|
scene.pet_baked = None;
|
||||||
scene.rev += 1; // the character re-lights onto the scene rig (sync_glue_booth's key)
|
scene.rev += 1; // the character re-lights onto the scene rig (sync_glue_booth's key)
|
||||||
info!(
|
info!(
|
||||||
"create scene: {name} up — {} submeshes, {} camera(s), stage {:?} facing {:.2}° \
|
"create scene: {name} up — {} submeshes, {} camera(s), stage {:?}",
|
||||||
scale {:.3}",
|
|
||||||
model.submeshes.len(),
|
model.submeshes.len(),
|
||||||
model.cameras.len(),
|
model.cameras.len(),
|
||||||
scene.char_spot,
|
scene.char_spot,
|
||||||
scene.char_facing.to_euler(EulerRot::YXZ).0.to_degrees(),
|
|
||||||
scene.char_scale,
|
|
||||||
);
|
);
|
||||||
// Seat the character at the current facing (the yaw path below runs only on a change).
|
// Seat the character at the current facing (the yaw path below runs only on a change).
|
||||||
apply_yaw(
|
apply_yaw(&mut commands, booth.root, scene.char_spot, preview.yaw);
|
||||||
&mut commands,
|
|
||||||
booth.root,
|
|
||||||
scene.char_spot,
|
|
||||||
scene.char_facing,
|
|
||||||
scene.char_scale,
|
|
||||||
preview.yaw,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// The installed camera owns the booth camera while the scene shows, every frame: the pane's
|
// The installed camera owns the booth camera while the scene shows, every frame: the pane's
|
||||||
// camera of the file's table, or with none the orthographic leg (`0x76ceeb` installs no
|
// camera of the file's table, or with none the orthographic leg (`0x76ceeb` installs no
|
||||||
@@ -1595,16 +1540,9 @@ pub(super) fn sync_glue_booth(
|
|||||||
let Some(booth) = booths.0.get_mut(GLUE_SLOT) else {
|
let Some(booth) = booths.0.get_mut(GLUE_SLOT) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let (spot, stage, stage_scale, scene_rev) =
|
let (spot, scene_rev) = scene.as_deref().map_or((Vec3::ZERO, 0), |s| {
|
||||||
scene
|
(if s.spawned { s.char_spot } else { Vec3::ZERO }, s.rev)
|
||||||
.as_deref()
|
});
|
||||||
.map_or((Vec3::ZERO, Quat::IDENTITY, 1.0, 0), |s| {
|
|
||||||
if s.spawned {
|
|
||||||
(s.char_spot, s.char_facing, s.char_scale, s.rev)
|
|
||||||
} else {
|
|
||||||
(Vec3::ZERO, Quat::IDENTITY, 1.0, s.rev)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let (last_rev, last_yaw, last_scene) = state.last.unwrap_or((u64::MAX, f32::NAN, u64::MAX));
|
let (last_rev, last_yaw, last_scene) = state.last.unwrap_or((u64::MAX, f32::NAN, u64::MAX));
|
||||||
// `sync_glue_scene` runs first and commits a stage only beside its character, so a standing
|
// `sync_glue_scene` runs first and commits a stage only beside its character, so a standing
|
||||||
// stage matching the request means this bake may show ([`PendingSwap`]).
|
// stage matching the request means this bake may show ([`PendingSwap`]).
|
||||||
@@ -1627,14 +1565,7 @@ pub(super) fn sync_glue_booth(
|
|||||||
booth.parked = false;
|
booth.parked = false;
|
||||||
state.last = Some((bake.revision, preview.yaw, scene_rev));
|
state.last = Some((bake.revision, preview.yaw, scene_rev));
|
||||||
state.body = None;
|
state.body = None;
|
||||||
apply_yaw(
|
apply_yaw(&mut commands, booth.root, spot, preview.yaw);
|
||||||
&mut commands,
|
|
||||||
booth.root,
|
|
||||||
spot,
|
|
||||||
stage,
|
|
||||||
stage_scale,
|
|
||||||
preview.yaw,
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// The rig comes from the display cache the builder gated on; if not ready, retry.
|
// The rig comes from the display cache the builder gated on; if not ready, retry.
|
||||||
@@ -1962,14 +1893,7 @@ pub(super) fn sync_glue_booth(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
apply_yaw(
|
apply_yaw(&mut commands, booth.root, spot, preview.yaw);
|
||||||
&mut commands,
|
|
||||||
booth.root,
|
|
||||||
spot,
|
|
||||||
stage,
|
|
||||||
stage_scale,
|
|
||||||
preview.yaw,
|
|
||||||
);
|
|
||||||
// A yaw-only pass must not record revisions it did not commit, or a held pair never retries.
|
// A yaw-only pass must not record revisions it did not commit, or a held pair never retries.
|
||||||
state.last = Some((
|
state.last = Some((
|
||||||
if rebake { bake.revision } else { last_rev },
|
if rebake { bake.revision } else { last_rev },
|
||||||
@@ -2204,11 +2128,11 @@ pub(super) fn sync_glue_pet(
|
|||||||
);
|
);
|
||||||
// `anchor` writes the pose buffer, `finish` commits it.
|
// `anchor` writes the pose buffer, `finish` commits it.
|
||||||
pet_rig.finish(&mut commands);
|
pet_rig.finish(&mut commands);
|
||||||
// The seat: attachment 1's whole frame, times the family ramp scale.
|
// The seat at bind pose, times the family ramp scale; the bone's frame comes next
|
||||||
|
// ([`seat_on_stage_bones`]).
|
||||||
|
scene.pet_scale = pet.scale.max(0.01);
|
||||||
commands.entity(scene.pet_root).insert(
|
commands.entity(scene.pet_root).insert(
|
||||||
Transform::from_translation(scene.pet_spot)
|
Transform::from_translation(scene.pet_spot).with_scale(Vec3::splat(scene.pet_scale)),
|
||||||
.with_rotation(scene.pet_facing)
|
|
||||||
.with_scale(Vec3::splat(pet.scale.max(0.01) * scene.pet_stage_scale)),
|
|
||||||
);
|
);
|
||||||
scene.pet_baked = Some(pet.revision);
|
scene.pet_baked = Some(pet.revision);
|
||||||
info!(
|
info!(
|
||||||
@@ -2234,55 +2158,61 @@ fn clear_pet(commands: &mut Commands, scene: &mut CreateScene) {
|
|||||||
scene.pet_baked = None;
|
scene.pet_baked = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seat the model root on the stage `spot` in the stage's frame, facing `yaw` (the reference's
|
/// Seat the model root on the stage `spot` at bind pose, facing `yaw` (the reference's
|
||||||
/// `Model:SetRotation`, about the root's own origin).
|
/// `Model:SetRotation`, about the root's own origin); [`seat_on_stage_bones`] then puts it in the
|
||||||
fn apply_yaw(
|
/// bone's frame.
|
||||||
commands: &mut Commands,
|
fn apply_yaw(commands: &mut Commands, root: Entity, spot: Vec3, yaw: f32) {
|
||||||
root: Entity,
|
commands
|
||||||
spot: Vec3,
|
.entity(root)
|
||||||
stage: Quat,
|
.insert(Transform::from_translation(spot).with_rotation(Quat::from_rotation_y(yaw)));
|
||||||
stage_scale: f32,
|
|
||||||
yaw: f32,
|
|
||||||
) {
|
|
||||||
commands.entity(root).insert(
|
|
||||||
Transform::from_translation(spot)
|
|
||||||
.with_rotation(stage * Quat::from_rotation_y(yaw))
|
|
||||||
.with_scale(Vec3::splat(stage_scale)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The stage's authored frame: the constant rotation and uniform scale parked on attachment
|
/// A child's seat under a scene bone: the bone's whole animated matrix, moved to the attachment's
|
||||||
/// `bone`'s chain. The reference composes an attachment's whole frame, not only its point
|
/// position, the composed result of `0x7140d8`-`0x714149`. The child's own turn and scale go in
|
||||||
/// (`0x71439b` in `0x714260`). `UI_Human` keys its stage bones with a constant −16.5° yaw to face
|
/// its frame, under the bone's.
|
||||||
/// its off-axis camera; the other race scenes leave them unrotated. The scale is uniform on every
|
fn stage_seat(bone: bevy::math::Affine3A, offset: Vec3, own: Transform) -> Transform {
|
||||||
/// shipped scene, so `x` stands for it.
|
let mut seat = bone;
|
||||||
fn stage_frame(model: &M2Model, bone: u16) -> (Quat, f32) {
|
seat.translation = bone.transform_point3(offset).into();
|
||||||
let Some(anims) = model.animations.as_ref() else {
|
Transform::from_matrix(Mat4::from(seat) * own.to_matrix())
|
||||||
return (Quat::IDENTITY, 1.0);
|
}
|
||||||
|
|
||||||
|
/// Seats the body and the pet on attachments 0 and 1 of the standing scene every frame, from the
|
||||||
|
/// scene rig's pose: a seat's keys, a parent's and keys that move all reach them. A scene with no
|
||||||
|
/// posed rig keeps the bind-pose seats its build wrote.
|
||||||
|
pub(super) fn seat_on_stage_bones(
|
||||||
|
mut seats: Query<&mut Transform>,
|
||||||
|
scene: Option<Res<CreateScene>>,
|
||||||
|
booths: Res<Booths>,
|
||||||
|
preview: Res<GluePreview>,
|
||||||
|
m2s: Res<Assets<M2Model>>,
|
||||||
|
rigs: Query<&benilla_world::rig_anim::RigPose>,
|
||||||
|
) {
|
||||||
|
let (Some(scene), Some(booth)) = (scene, booths.0.get(GLUE_SLOT)) else {
|
||||||
|
return;
|
||||||
};
|
};
|
||||||
let mut rot = Quat::IDENTITY;
|
if !scene.spawned {
|
||||||
let mut scale = 1.0;
|
return;
|
||||||
let mut idx = bone;
|
}
|
||||||
// Cycle-guarded by the joint count, as the pivot walk is.
|
let Some(model) = scene.handle.as_ref().and_then(|h| m2s.get(h)) else {
|
||||||
for _ in 0..=model.skeleton.joints.len() {
|
return;
|
||||||
if let Some(g) = anims.global_bones.iter().find(|g| g.bone == idx) {
|
};
|
||||||
// `t = 0`: these are parked keys, and no stage bone animates.
|
let Ok(pose) = rigs.get(scene.root) else {
|
||||||
if let Some(c) = g.rotation.as_ref() {
|
return;
|
||||||
rot = c.sample(0.0) * rot;
|
};
|
||||||
}
|
let body = Transform::from_rotation(Quat::from_rotation_y(preview.yaw));
|
||||||
if let Some(c) = g.scale.as_ref() {
|
let pet = Transform::from_scale(Vec3::splat(scene.pet_scale));
|
||||||
scale *= c.sample(0.0).x;
|
for (id, root, own) in [(0, booth.root, body), (1, scene.pet_root, pet)] {
|
||||||
}
|
let Some(a) = model.attachments.iter().find(|a| a.id == id) else {
|
||||||
}
|
continue;
|
||||||
let Some(joint) = model.skeleton.joints.get(usize::from(idx)) else {
|
|
||||||
break;
|
|
||||||
};
|
};
|
||||||
match u16::try_from(joint.parent) {
|
let Some(bone) = pose.model.get(usize::from(a.bone)) else {
|
||||||
Ok(parent) => idx = parent,
|
continue;
|
||||||
Err(_) => break, // -1 = root reached
|
};
|
||||||
|
if let Ok(mut t) = seats.get_mut(root) {
|
||||||
|
// A still seat leaves the booth's transform unchanged.
|
||||||
|
t.set_if_neq(stage_seat(*bone, a.offset, own));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(rot, scale)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The create preview instrument:
|
/// The create preview instrument:
|
||||||
@@ -2976,6 +2906,22 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_seat_takes_the_bones_whole_matrix_at_the_attachment() {
|
||||||
|
// UI_CharacterSelect's seat: a half turn and 0.673 keyed on the bone, which also moves.
|
||||||
|
let half_turn = Quat::from_rotation_y(std::f32::consts::PI);
|
||||||
|
let bone = bevy::math::Affine3A::from_scale_rotation_translation(
|
||||||
|
Vec3::splat(0.5),
|
||||||
|
half_turn,
|
||||||
|
Vec3::new(1.0, 2.0, 3.0),
|
||||||
|
);
|
||||||
|
let quarter = Quat::from_rotation_y(std::f32::consts::FRAC_PI_2);
|
||||||
|
let seat = stage_seat(bone, Vec3::X, Transform::from_rotation(quarter));
|
||||||
|
assert!(seat.translation.abs_diff_eq(Vec3::new(0.5, 2.0, 3.0), 1e-5));
|
||||||
|
assert!(seat.scale.abs_diff_eq(Vec3::splat(0.5), 1e-5));
|
||||||
|
assert!(seat.rotation.dot(half_turn * quarter).abs() > 0.9999);
|
||||||
|
}
|
||||||
|
|
||||||
fn create(sex: u8, class: u8, skin: u8, hair_style: u8) -> GlueLook {
|
fn create(sex: u8, class: u8, skin: u8, hair_style: u8) -> GlueLook {
|
||||||
GlueLook::Create(CreateLook {
|
GlueLook::Create(CreateLook {
|
||||||
race: 1,
|
race: 1,
|
||||||
@@ -3715,11 +3661,7 @@ mod tests {
|
|||||||
file: GlueSceneFile::new("UI_MainMenu.mdx"),
|
file: GlueSceneFile::new("UI_MainMenu.mdx"),
|
||||||
handle: Handle::default(),
|
handle: Handle::default(),
|
||||||
char_spot: Vec3::ZERO,
|
char_spot: Vec3::ZERO,
|
||||||
char_facing: Quat::IDENTITY,
|
|
||||||
char_scale: 1.0,
|
|
||||||
pet_spot: Vec3::ZERO,
|
pet_spot: Vec3::ZERO,
|
||||||
pet_facing: Quat::IDENTITY,
|
|
||||||
pet_stage_scale: 1.0,
|
|
||||||
stage: 1,
|
stage: 1,
|
||||||
clock_ms: 0,
|
clock_ms: 0,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -828,6 +828,14 @@ impl Plugin for PortraitPlugin {
|
|||||||
// the booths' transforms and cameras are none a call reads.
|
// the booths' transforms and cameras are none a call reads.
|
||||||
.after(crate::vm_pass::VmPass::End),
|
.after(crate::vm_pass::VmPass::End),
|
||||||
)
|
)
|
||||||
|
// The seats ride this frame's scene pose, as the reference animates the scene before
|
||||||
|
// the children on it (`0x714000`); the seats' builds above wrote the bind pose.
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
glue_booth::seat_on_stage_bones
|
||||||
|
.after(benilla_world::rig_anim::RigModelPass)
|
||||||
|
.before(bevy::transform::TransformSystems::Propagate),
|
||||||
|
)
|
||||||
// The character panes: the VM's paint list, after its paint pass armed the turns and
|
// The character panes: the VM's paint list, after its paint pass armed the turns and
|
||||||
// the extract published the panes' geometry; before the camera gate reads the marks.
|
// the extract published the panes' geometry; before the camera gate reads the marks.
|
||||||
.add_systems(
|
.add_systems(
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ const PUBLISHED: &[(&str, &str)] = &[
|
|||||||
("rig_anim::PosePost", "wall"),
|
("rig_anim::PosePost", "wall"),
|
||||||
("rig_anim::RigAnchor", "wall"),
|
("rig_anim::RigAnchor", "wall"),
|
||||||
("rig_anim::RigFrame", "wall"),
|
("rig_anim::RigFrame", "wall"),
|
||||||
|
("rig_anim::RigModelPass", "wall"),
|
||||||
("rig_anim::RigPose", "wall"),
|
("rig_anim::RigPose", "wall"),
|
||||||
("rig_anim::SceneClock", "record"),
|
("rig_anim::SceneClock", "record"),
|
||||||
("rig_palette::RigPalettes", "record"),
|
("rig_palette::RigPalettes", "record"),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ mod global_seq;
|
|||||||
mod pose;
|
mod pose;
|
||||||
|
|
||||||
pub(crate) use compose::seed_rig_rows;
|
pub(crate) use compose::seed_rig_rows;
|
||||||
pub use compose::{finalize_rig_worlds, PosePost};
|
pub use compose::{finalize_rig_worlds, PosePost, RigModelPass};
|
||||||
pub use global_seq::GlobalSeqDrive;
|
pub use global_seq::GlobalSeqDrive;
|
||||||
pub use pose::{RigAnchor, RigFrame, RigPose};
|
pub use pose::{RigAnchor, RigFrame, RigPose};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ use super::{AnimParked, RigFrame, RigPose};
|
|||||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct PosePost;
|
pub struct PosePost;
|
||||||
|
|
||||||
|
/// The model pass ([`compose_rig_models`]): a reader ordered after it and before propagation sees
|
||||||
|
/// this frame's model-space bones.
|
||||||
|
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct RigModelPass;
|
||||||
|
|
||||||
/// Fold each pose-dirty rig into model space and re-seat its anchors, leaving `pose_dirty` for
|
/// Fold each pose-dirty rig into model space and re-seat its anchors, leaving `pose_dirty` for
|
||||||
/// the world pass. A parked rig is skipped; its wake unparks it before this runs.
|
/// the world pass. A parked rig is skipped; its wake unparks it before this runs.
|
||||||
fn compose_rig_models(
|
fn compose_rig_models(
|
||||||
@@ -270,12 +275,13 @@ pub fn plugin(app: &mut App) {
|
|||||||
.after(bevy::app::AnimationSystems)
|
.after(bevy::app::AnimationSystems)
|
||||||
.before(bevy::transform::TransformSystems::Propagate),
|
.before(bevy::transform::TransformSystems::Propagate),
|
||||||
)
|
)
|
||||||
.add_systems(
|
.configure_sets(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
compose_rig_models
|
RigModelPass
|
||||||
.after(PosePost)
|
.after(PosePost)
|
||||||
.before(bevy::transform::TransformSystems::Propagate),
|
.before(bevy::transform::TransformSystems::Propagate),
|
||||||
);
|
)
|
||||||
|
.add_systems(PostUpdate, compose_rig_models.in_set(RigModelPass));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user