Skip to content

Commit 7d2968c

Browse files
committed
プレビューで上半身のアニメが反映されるように
1 parent ad59378 commit 7d2968c

4 files changed

Lines changed: 269 additions & 39 deletions

File tree

backend/src/convert/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ use skeleton::{
3333
regenerate_inverse_bind_matrices, rename_bones, set_skin_skeleton_root,
3434
validate_bone_conversion_preconditions,
3535
};
36-
use skinning::{optimize_skinning_weights_and_joints, remap_unmapped_bone_weights};
36+
use skinning::{
37+
collapse_secondary_head_skins_to_primary, optimize_skinning_weights_and_joints,
38+
remap_unmapped_bone_weights,
39+
};
3740
use validation::{
3841
collect_mapped_bones, collect_missing_required_bones, collect_node_names,
3942
collect_parent_index_map, estimate_texture_fee, extract_author, extract_humanoid_bone_nodes,
@@ -359,6 +362,7 @@ fn transform_and_write_glb(
359362
// in the skin joints list after optimization.
360363
remap_unmapped_bone_weights(&mut json, &mut bin, humanoid_bone_nodes);
361364
optimize_skinning_weights_and_joints(&mut json, &mut bin)?;
365+
collapse_secondary_head_skins_to_primary(&mut json, &mut bin, humanoid_bone_nodes);
362366
// Clean up wrapper nodes above mPelvis. Keeps the topmost non-SL
363367
// ancestor as an identity-transform root so that skin.skeleton can
364368
// reference a node with no positional offset, preventing the SL viewer

backend/src/convert/skeleton.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -595,7 +595,11 @@ fn collect_primitives_for_skin_with_attributes(
595595
.cloned()
596596
.unwrap_or_default();
597597

598-
let mut seen = HashSet::<(usize, usize)>::new();
598+
// Deduplicate by full accessor tuple. Different primitives may reuse
599+
// JOINTS/WEIGHTS while pointing to different POSITION/NORMAL accessors;
600+
// collapsing only by (JOINTS, WEIGHTS) would skip geometry correction for
601+
// those primitives and can cause detached/flickering parts (e.g. face/eyes).
602+
let mut seen = HashSet::<(usize, usize, Option<usize>, Option<usize>)>::new();
599603
let mut bindings = Vec::new();
600604

601605
for node in nodes {
@@ -633,9 +637,6 @@ fn collect_primitives_for_skin_with_attributes(
633637
else {
634638
continue;
635639
};
636-
if !seen.insert((jnt_acc, wgt_acc)) {
637-
continue;
638-
}
639640
let pos_acc = attrs
640641
.get("POSITION")
641642
.and_then(Value::as_u64)
@@ -644,6 +645,9 @@ fn collect_primitives_for_skin_with_attributes(
644645
.get("NORMAL")
645646
.and_then(Value::as_u64)
646647
.map(|v| v as usize);
648+
if !seen.insert((jnt_acc, wgt_acc, pos_acc, norm_acc)) {
649+
continue;
650+
}
647651
bindings.push(PrimitiveBindingWithAttributes {
648652
joints_accessor: jnt_acc,
649653
weights_accessor: wgt_acc,
@@ -861,21 +865,18 @@ pub(super) fn promote_pelvis_to_scene_root(
861865
/// skeleton root." It does NOT require the skeleton root to be listed in the
862866
/// skin's `joints` array.
863867
///
864-
/// When an identity-root node exists above mPelvis, we point every skin's
865-
/// `skeleton` to that node. Because the identity root has a pure identity
866-
/// transform, the SL viewer (and other importers that multiply the skeleton
867-
/// root's transform into the skinning equation) will not inject any unwanted
868-
/// offset. All joints still resolve to their correct world positions through
869-
/// the normal parent-chain traversal.
868+
/// For Second Life compatibility, prefer `mPelvis` when available even if an
869+
/// identity wrapper root exists. Some importers/viewers treat non-bone wrapper
870+
/// roots differently and can introduce offsets/flicker around head/face skins.
870871
///
871-
/// When no identity root is available (i.e. `promote_pelvis_to_scene_root`
872-
/// found no wrapper ancestors) we fall back to mPelvis itself.
872+
/// The optional `identity_root` is kept only as a fallback for models where
873+
/// hips are unavailable for any reason.
873874
pub(super) fn set_skin_skeleton_root(
874875
json: &mut Value,
875876
humanoid_bone_nodes: &HashMap<String, usize>,
876877
identity_root: Option<usize>,
877878
) {
878-
let skeleton_index = identity_root.or_else(|| humanoid_bone_nodes.get("hips").copied());
879+
let skeleton_index = humanoid_bone_nodes.get("hips").copied().or(identity_root);
879880

880881
let skins = match json["skins"].as_array_mut() {
881882
Some(s) => s,

backend/src/convert/skinning.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,131 @@ pub(super) fn optimize_skinning_weights_and_joints(json: &mut Value, bin: &mut [
226226
Ok(())
227227
}
228228

229+
/// Stabilize tiny head/eye-only secondary skins by forcing a single `mHead`
230+
/// bind inside each skin.
231+
///
232+
/// This avoids importer instability with tiny facial skins that include eye
233+
/// joints while keeping skin-index topology unchanged.
234+
pub(super) fn collapse_secondary_head_skins_to_primary(
235+
json: &mut Value,
236+
bin: &mut [u8],
237+
humanoid_bone_nodes: &HashMap<String, usize>,
238+
) {
239+
let Some(skins) = json.get("skins").and_then(Value::as_array) else {
240+
return;
241+
};
242+
if skins.len() <= 1 {
243+
return;
244+
}
245+
246+
let primary_skin_index = skins
247+
.iter()
248+
.enumerate()
249+
.max_by_key(|(_, skin)| {
250+
skin.get("joints")
251+
.and_then(Value::as_array)
252+
.map(|j| j.len())
253+
.unwrap_or(0)
254+
})
255+
.map(|(i, _)| i);
256+
let Some(primary_skin_index) = primary_skin_index else {
257+
return;
258+
};
259+
260+
let Some(head_node_index) = humanoid_bone_nodes.get("head").copied() else {
261+
return;
262+
};
263+
264+
let mut used_skin_indices = HashSet::<usize>::new();
265+
if let Some(nodes) = json.get("nodes").and_then(Value::as_array) {
266+
for node in nodes {
267+
if let Some(si) = node.get("skin").and_then(Value::as_u64).map(|v| v as usize) {
268+
used_skin_indices.insert(si);
269+
}
270+
}
271+
}
272+
273+
for skin_index in 0..skins.len() {
274+
if skin_index == primary_skin_index || !used_skin_indices.contains(&skin_index) {
275+
continue;
276+
}
277+
278+
let joints: Vec<usize> = json["skins"][skin_index]["joints"]
279+
.as_array()
280+
.map(|arr| {
281+
arr.iter()
282+
.filter_map(|v| v.as_u64().map(|n| n as usize))
283+
.collect()
284+
})
285+
.unwrap_or_default();
286+
if joints.is_empty() {
287+
continue;
288+
}
289+
290+
// Limit to tiny head/eye-only skins so body limb skins are untouched.
291+
let is_tiny_head_skin = joints.len() <= 4
292+
&& joints.iter().all(|&node_idx| {
293+
let name = json["nodes"][node_idx]
294+
.get("name")
295+
.and_then(Value::as_str)
296+
.unwrap_or("");
297+
matches!(name, "mHead" | "mEyeLeft" | "mEyeRight")
298+
});
299+
if !is_tiny_head_skin {
300+
continue;
301+
}
302+
303+
let bindings = collect_skin_primitive_bindings(json, skin_index);
304+
for binding in bindings {
305+
let Some(joints_meta) = accessor_meta(json, binding.joints_accessor) else {
306+
continue;
307+
};
308+
let Some(weights_meta) = accessor_meta(json, binding.weights_accessor) else {
309+
continue;
310+
};
311+
if joints_meta.accessor_type != "VEC4" || weights_meta.accessor_type != "VEC4" {
312+
continue;
313+
}
314+
if !(joints_meta.component_type == 5121 || joints_meta.component_type == 5123) {
315+
continue;
316+
}
317+
if weights_meta.component_type != 5126 {
318+
continue;
319+
}
320+
321+
let count = joints_meta.count.min(weights_meta.count);
322+
for vertex_index in 0..count {
323+
let _ = write_joint_slot(bin, &joints_meta, vertex_index, 0, 0);
324+
let _ = write_joint_slot(bin, &joints_meta, vertex_index, 1, 0);
325+
let _ = write_joint_slot(bin, &joints_meta, vertex_index, 2, 0);
326+
let _ = write_joint_slot(bin, &joints_meta, vertex_index, 3, 0);
327+
328+
let _ = write_weight_f32(bin, &weights_meta, vertex_index, 0, 1.0);
329+
let _ = write_weight_f32(bin, &weights_meta, vertex_index, 1, 0.0);
330+
let _ = write_weight_f32(bin, &weights_meta, vertex_index, 2, 0.0);
331+
let _ = write_weight_f32(bin, &weights_meta, vertex_index, 3, 0.0);
332+
}
333+
}
334+
335+
// Rewrite this skin to a single-joint mHead bind.
336+
if let Some(skins_mut) = json.get_mut("skins").and_then(Value::as_array_mut)
337+
&& let Some(skin_mut) = skins_mut.get_mut(skin_index)
338+
{
339+
skin_mut["joints"] = Value::Array(vec![Value::from(head_node_index as u64)]);
340+
341+
if let Some(acc_idx) = skin_mut
342+
.get("inverseBindMatrices")
343+
.and_then(Value::as_u64)
344+
.map(|v| v as usize)
345+
&& let Some(accessors) = json.get_mut("accessors").and_then(Value::as_array_mut)
346+
&& let Some(accessor) = accessors.get_mut(acc_idx)
347+
{
348+
accessor["count"] = Value::from(1u64);
349+
}
350+
}
351+
}
352+
}
353+
229354
fn collect_skin_primitive_bindings(json: &Value, skin_index: usize) -> Vec<PrimitiveSkinBinding> {
230355
let nodes = json
231356
.get("nodes")

0 commit comments

Comments
 (0)