use super::numeric::translation_composition_rounding_base;
use super::{
ScaleError, ScalePayloadShapeRow, ScaleProjectedRole, ScaleSourceNodeKind,
ScaleSourceTopologyRow,
};
use crate::model::{
BoneId, Document, MeshInstance, Skeleton, SourceInverseBindAccessorStatus, SourceNodeAsset,
SourceNodeLocalRest, SourceSkeletonCoverage, SourceSkinAsset, mat4_is_finite,
validate_document_shape as validate_model_document_shape, world_rest_matrices,
};
use glam::{DMat4, Mat4};
#[cfg(test)]
use std::cell::Cell;
use std::collections::{BTreeMap, BTreeSet};
pub(in crate::scale) fn validate_scale_input(document: &Document) -> Result<(), ScaleError> {
validate_model_document_shape(document)?;
for (mesh_index, mesh) in document.assets.meshes.iter().enumerate() {
for (primitive_index, primitive) in mesh.primitives.iter().enumerate() {
if primitive
.positions
.iter()
.any(|position| !position.is_finite())
{
return Err(ScaleError::InvalidMeshPrimitive {
mesh_index,
primitive_index,
reason: "non_finite_position",
});
}
for (vertex_index, weights) in primitive.weights.iter().enumerate() {
for (influence_index, &weight) in weights.iter().enumerate() {
if weight.is_finite() && weight < 0.0 {
return Err(ScaleError::NegativeSkinWeight {
mesh_index,
primitive_index,
vertex_index,
influence_index,
});
}
}
}
}
}
Ok(())
}
pub(in crate::scale) fn validate_candidate_structure(
source: &Document,
candidate: &Document,
) -> Result<(), ScaleError> {
if source.skeleton.bones.len() != candidate.skeleton.bones.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "bone_count_mismatch",
});
}
let complete_projection_differs = source.assets.source_skeleton.coverage
== SourceSkeletonCoverage::Complete
&& candidate.assets.source_skeleton.coverage == SourceSkeletonCoverage::Complete
&& source_node_projection(source) != source_node_projection(candidate);
if source
.skeleton
.bones
.iter()
.map(|bone| bone.parent)
.ne(candidate.skeleton.bones.iter().map(|bone| bone.parent))
|| source.assets.source_skeleton.coverage != candidate.assets.source_skeleton.coverage
|| complete_projection_differs
{
return Err(ScaleError::CandidateStructureMismatch {
reason: "skeleton_topology_mismatch",
});
}
if source_skin_payload_shapes(source) != source_skin_payload_shapes(candidate) {
return Err(ScaleError::CandidateStructureMismatch {
reason: "source_skin_payload_mismatch",
});
}
if source.clips.len() != candidate.clips.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "clip_count_mismatch",
});
}
for (source_clip, candidate_clip) in source.clips.iter().zip(candidate.clips.iter()) {
if source_clip.tracks.len() != candidate_clip.tracks.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "track_count_mismatch",
});
}
for (source_track, candidate_track) in
source_clip.tracks.iter().zip(candidate_clip.tracks.iter())
{
if source_track.bone != candidate_track.bone
|| source_track.property != candidate_track.property
|| source_track.interpolation != candidate_track.interpolation
|| source_track.times != candidate_track.times
|| source_track.values.len() != candidate_track.values.len()
{
return Err(ScaleError::CandidateStructureMismatch {
reason: "track_shape_mismatch",
});
}
}
}
if source.assets.instances.len() != candidate.assets.instances.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "instance_count_mismatch",
});
}
for (instance, candidate_instance) in source
.assets
.instances
.iter()
.zip(candidate.assets.instances.iter())
{
if instance.node != candidate_instance.node {
return Err(ScaleError::CandidateStructureMismatch {
reason: "instance_node_mismatch",
});
}
if instance.source_node_index != candidate_instance.source_node_index {
return Err(ScaleError::CandidateStructureMismatch {
reason: "instance_source_node_index_mismatch",
});
}
if instance.mesh != candidate_instance.mesh {
return Err(ScaleError::CandidateStructureMismatch {
reason: "instance_mesh_mismatch",
});
}
if instance.skin_joints != candidate_instance.skin_joints {
return Err(ScaleError::CandidateStructureMismatch {
reason: "instance_skin_joints_mismatch",
});
}
}
if source.assets.meshes.len() != candidate.assets.meshes.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "mesh_count_mismatch",
});
}
for (source_mesh, candidate_mesh) in source
.assets
.meshes
.iter()
.zip(candidate.assets.meshes.iter())
{
if source_mesh.source_mesh_index != candidate_mesh.source_mesh_index {
return Err(ScaleError::CandidateStructureMismatch {
reason: "mesh_source_identity_mismatch",
});
}
if source_mesh.primitives.len() != candidate_mesh.primitives.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "primitive_count_mismatch",
});
}
for (source_primitive, candidate_primitive) in source_mesh
.primitives
.iter()
.zip(candidate_mesh.primitives.iter())
{
if source_primitive.positions.len() != candidate_primitive.positions.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "primitive_vertex_count_mismatch",
});
}
if source_primitive.normals.len() != candidate_primitive.normals.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "primitive_normal_count_mismatch",
});
}
if source_primitive.joints.len() != candidate_primitive.joints.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "primitive_joint_count_mismatch",
});
}
if source_primitive.weights.len() != candidate_primitive.weights.len() {
return Err(ScaleError::CandidateStructureMismatch {
reason: "primitive_weight_count_mismatch",
});
}
}
}
Ok(())
}
fn source_node_projection(document: &Document) -> BTreeMap<usize, (Option<usize>, Option<BoneId>)> {
document
.assets
.source_skeleton
.nodes
.iter()
.map(|node| {
(
node.source_node_index,
(node.parent_source_node_index, node.bone),
)
})
.collect()
}
pub(in crate::scale) fn source_skin_payload_shapes(
document: &Document,
) -> Vec<ScalePayloadShapeRow> {
if document.assets.source_skeleton.coverage != SourceSkeletonCoverage::Complete {
return Vec::new();
}
let mut rows = Vec::new();
for skin in &document.assets.source_skeleton.skins {
rows.push(ScalePayloadShapeRow::SourceSkin {
source_skin_index: skin.source_skin_index,
skeleton_root_source_node_index: skin.skeleton_root_source_node_index,
joint_count: skin.joint_source_node_indices.len(),
attachment_count: skin.attachments.len(),
inverse_bind_status: skin.inverse_bind_accessor.status,
inverse_bind_declared_count: skin.inverse_bind_accessor.declared_count,
inverse_bind_matrix_count: skin.inverse_bind_accessor.matrices.len(),
});
rows.extend(skin.joint_source_node_indices.iter().enumerate().map(
|(slot, &source_node_index)| ScalePayloadShapeRow::SourceSkinJoint {
source_skin_index: skin.source_skin_index,
slot,
source_node_index,
},
));
rows.extend(
skin.attachments
.iter()
.enumerate()
.map(
|(attachment_index, attachment)| ScalePayloadShapeRow::SourceSkinAttachment {
source_skin_index: skin.source_skin_index,
attachment_index,
source_node_index: attachment.source_node_index,
source_mesh_index: attachment.source_mesh_index,
},
),
);
}
rows
}
pub(in crate::scale) fn whole_document_source_topology(
document: &Document,
) -> Vec<ScaleSourceTopologyRow> {
if document.assets.source_skeleton.coverage != SourceSkeletonCoverage::Complete {
return Vec::new();
}
let mut rows: Vec<_> = document
.assets
.source_skeleton
.nodes
.iter()
.map(|node| ScaleSourceTopologyRow {
source_node_index: node.source_node_index,
parent_source_node_index: node.parent_source_node_index,
kind: ScaleSourceNodeKind::OutsideDomain { bone: node.bone },
})
.collect();
rows.sort_unstable_by_key(|row| row.source_node_index);
rows
}
pub(in crate::scale) struct RestBindTopology {
pub(in crate::scale) source_rows: Vec<ScaleSourceTopologyRow>,
pub(in crate::scale) scaled_root_bone: BoneId,
#[cfg(test)]
pub(in crate::scale) ancestry_steps: usize,
}
impl RestBindTopology {
fn projected_rows(
&self,
) -> impl Iterator<
Item = (
&ScaleSourceTopologyRow,
BoneId,
ScaleProjectedRole,
Option<usize>,
),
> {
self.source_rows.iter().filter_map(|row| match row.kind {
ScaleSourceNodeKind::Projected {
bone,
role,
incoming_connector_tail,
..
} => Some((row, bone, role, incoming_connector_tail)),
ScaleSourceNodeKind::Connector | ScaleSourceNodeKind::OutsideDomain { .. } => None,
})
}
pub(in crate::scale) fn bone_of_source(&self) -> BTreeMap<usize, BoneId> {
self.projected_rows()
.map(|(row, bone, _, _)| (row.source_node_index, bone))
.collect()
}
pub(in crate::scale) fn affected_nodes(&self) -> Vec<BoneId> {
let mut nodes: Vec<_> = self.projected_rows().map(|(_, bone, _, _)| bone).collect();
nodes.sort_unstable();
nodes.dedup();
nodes
}
pub(in crate::scale) fn transform_only_attachments(&self) -> Vec<BoneId> {
let mut nodes: Vec<_> = self
.projected_rows()
.filter_map(|(_, bone, role, _)| {
(role == ScaleProjectedRole::TransformOnly).then_some(bone)
})
.collect();
nodes.sort_unstable();
nodes
}
pub(in crate::scale) fn connector_sources(&self) -> BTreeSet<usize> {
self.source_rows
.iter()
.filter_map(|row| {
matches!(row.kind, ScaleSourceNodeKind::Connector).then_some(row.source_node_index)
})
.collect()
}
}
struct RestBindSourceAncestry {
projected_parent_by_source: BTreeMap<usize, Option<usize>>,
connector_tail_by_source: BTreeMap<usize, usize>,
used_connectors: BTreeSet<usize>,
#[cfg(test)]
ancestry_steps: usize,
}
fn derive_rest_bind_source_ancestry(
by_source_index: &BTreeMap<usize, &SourceNodeAsset>,
source_nodes: &BTreeSet<usize>,
projected_sources: impl Iterator<Item = usize>,
source_root_node_index: usize,
) -> Result<RestBindSourceAncestry, ScaleError> {
let projected_sources: Vec<_> = projected_sources.collect();
let mut nearest_cache: BTreeMap<usize, Option<usize>> = BTreeMap::new();
let mut projected_parent_by_source = BTreeMap::new();
let mut connector_tail_by_source = BTreeMap::new();
#[cfg(test)]
let mut ancestry_steps = 0;
for source in projected_sources {
let asset = by_source_index
.get(&source)
.ok_or(ScaleError::IncompleteClosure {
reason: "dangling_source_parent_node_index",
})?;
if source != source_root_node_index
&& let Some(parent) = asset.parent_source_node_index.filter(|parent| {
source_nodes.contains(parent)
&& by_source_index
.get(parent)
.is_some_and(|parent| parent.bone.is_none())
})
{
connector_tail_by_source.insert(source, parent);
}
let mut cursor = asset.parent_source_node_index;
let mut path = Vec::new();
let mut visiting = BTreeSet::new();
let projected_parent = loop {
let Some(parent) = cursor else {
break None;
};
if let Some(&cached) = nearest_cache.get(&parent) {
break cached;
}
if !visiting.insert(parent) || visiting.len() > by_source_index.len() {
return Err(ScaleError::IncompleteClosure {
reason: "cyclic_or_unbounded_source_parent_chain",
});
}
let parent_asset =
by_source_index
.get(&parent)
.ok_or(ScaleError::IncompleteClosure {
reason: "dangling_source_parent_node_index",
})?;
#[cfg(test)]
{
ancestry_steps += 1;
}
if parent_asset.bone.is_some() {
break Some(parent);
}
path.push(parent);
cursor = parent_asset.parent_source_node_index;
};
for connector in path {
nearest_cache.insert(connector, projected_parent);
}
projected_parent_by_source.insert(source, projected_parent);
}
let mut used_connectors = BTreeSet::new();
let mut pending: Vec<usize> = connector_tail_by_source.values().copied().collect();
while let Some(source) = pending.pop() {
if !used_connectors.insert(source) {
continue;
}
let asset = by_source_index
.get(&source)
.ok_or(ScaleError::IncompleteClosure {
reason: "dangling_source_parent_node_index",
})?;
if let Some(parent) = asset.parent_source_node_index.filter(|parent| {
source_nodes.contains(parent)
&& by_source_index
.get(parent)
.is_some_and(|parent| parent.bone.is_none())
}) {
pending.push(parent);
}
}
Ok(RestBindSourceAncestry {
projected_parent_by_source,
connector_tail_by_source,
used_connectors,
#[cfg(test)]
ancestry_steps,
})
}
pub(in crate::scale) fn derive_rest_bind_plan_domain(
document: &Document,
source_skin_index: usize,
source_root_node_index: usize,
) -> Result<RestBindTopology, ScaleError> {
if document.assets.source_skeleton.coverage != SourceSkeletonCoverage::Complete {
return Err(ScaleError::IncompleteSourceSkeleton);
}
let skin = resolve_rest_bind_skin(document, source_skin_index)?;
let by_source_index = source_node_index_map(document);
if !by_source_index.contains_key(&source_root_node_index) {
return Err(ScaleError::InvalidRootSelector {
source_root_node_index,
});
}
let source_nodes =
rest_bind_affected_closure(document, &by_source_index, skin, source_root_node_index)?;
let scaled_root_bone = by_source_index[&source_root_node_index].bone.ok_or(
ScaleError::SourceNodeNotNormalized {
source_node_index: source_root_node_index,
},
)?;
for &joint in &skin.joint_source_node_indices {
if by_source_index[&joint].bone.is_none() {
return Err(ScaleError::SourceNodeNotNormalized {
source_node_index: joint,
});
}
}
let bone_of_source: BTreeMap<usize, BoneId> = source_nodes
.iter()
.filter_map(|source| by_source_index[source].bone.map(|bone| (*source, bone)))
.collect();
let ancestry = derive_rest_bind_source_ancestry(
&by_source_index,
&source_nodes,
bone_of_source.keys().copied(),
source_root_node_index,
)?;
if ancestry.used_connectors.iter().any(|source| {
let SourceNodeLocalRest::Matrix(matrix) = &by_source_index[source].local_rest else {
return false;
};
matrix.x_axis.w != 0.0
|| matrix.y_axis.w != 0.0
|| matrix.z_axis.w != 0.0
|| matrix.w_axis.w != 1.0
}) {
return Err(ScaleError::IncompleteClosure {
reason: "non_affine_connector_source_transform",
});
}
if let Some(&terminal) = source_nodes.iter().find(|source| {
by_source_index[source].bone.is_none() && !ancestry.used_connectors.contains(source)
}) {
return Err(ScaleError::SourceNodeNotNormalized {
source_node_index: terminal,
});
}
let joint_sources: BTreeSet<usize> = skin.joint_source_node_indices.iter().copied().collect();
let mut source_rows = Vec::with_capacity(document.assets.source_skeleton.nodes.len());
for asset in &document.assets.source_skeleton.nodes {
let source_node_index = asset.source_node_index;
let kind = if !source_nodes.contains(&source_node_index) {
ScaleSourceNodeKind::OutsideDomain { bone: asset.bone }
} else {
match asset.bone {
Some(bone) => {
let role = if source_node_index == source_root_node_index {
ScaleProjectedRole::Root
} else if joint_sources.contains(&source_node_index) {
ScaleProjectedRole::Joint
} else {
ScaleProjectedRole::TransformOnly
};
let projected_parent = *ancestry
.projected_parent_by_source
.get(&source_node_index)
.ok_or(ScaleError::IncompleteClosure {
reason: "projected_source_ancestry_missing",
})?;
ScaleSourceNodeKind::Projected {
bone,
role,
projected_parent,
incoming_connector_tail: ancestry
.connector_tail_by_source
.get(&source_node_index)
.copied(),
}
}
None => ScaleSourceNodeKind::Connector,
}
};
source_rows.push(ScaleSourceTopologyRow {
source_node_index,
parent_source_node_index: asset.parent_source_node_index,
kind,
});
}
source_rows.sort_unstable_by_key(|row| row.source_node_index);
Ok(RestBindTopology {
source_rows,
scaled_root_bone,
#[cfg(test)]
ancestry_steps: ancestry.ancestry_steps,
})
}
pub(in crate::scale) fn resolve_rest_bind_skin(
document: &Document,
source_skin_index: usize,
) -> Result<&SourceSkinAsset, ScaleError> {
let skin = document
.assets
.source_skeleton
.skins
.iter()
.find(|skin| skin.source_skin_index == source_skin_index)
.ok_or(ScaleError::InvalidSkinSelector { source_skin_index })?;
if skin.joint_source_node_indices.is_empty() {
return Err(ScaleError::InvalidSkinSelector { source_skin_index });
}
Ok(skin)
}
pub(in crate::scale) fn source_node_index_map(
document: &Document,
) -> BTreeMap<usize, &SourceNodeAsset> {
document
.assets
.source_skeleton
.nodes
.iter()
.map(|node| (node.source_node_index, node))
.collect()
}
pub(in crate::scale) fn rest_bind_affected_closure(
document: &Document,
by_source_index: &BTreeMap<usize, &SourceNodeAsset>,
skin: &SourceSkinAsset,
source_root_node_index: usize,
) -> Result<BTreeSet<usize>, ScaleError> {
let unskinned_attachment: BTreeSet<usize> = document
.assets
.instances
.iter()
.filter(|instance| instance.skin_joints.is_empty())
.map(|instance| instance.source_node_index)
.collect();
let reject_if_unskinned = |source_node_index: usize| -> Result<(), ScaleError> {
if !unskinned_attachment.contains(&source_node_index) {
return Ok(());
}
let bone = by_source_index
.get(&source_node_index)
.and_then(|asset| asset.bone)
.ok_or(ScaleError::SourceNodeNotNormalized { source_node_index })?;
Err(ScaleError::UnsupportedUnskinnedGeometry { node: bone })
};
let mut domain = BTreeSet::new();
let mut reaches_root = BTreeSet::from([source_root_node_index]);
reject_if_unskinned(source_root_node_index)?;
domain.insert(source_root_node_index);
for &joint in &skin.joint_source_node_indices {
if !by_source_index.contains_key(&joint) {
return Err(ScaleError::IncompleteClosure {
reason: "skin_joint_source_node_missing",
});
}
reject_if_unskinned(joint)?;
let mut cursor = joint;
let mut pending = Vec::new();
let mut visiting = BTreeSet::new();
loop {
if reaches_root.contains(&cursor) {
break;
}
if !visiting.insert(cursor) || visiting.len() > by_source_index.len() {
return Err(ScaleError::IncompleteClosure {
reason: "cyclic_or_unbounded_source_parent_chain",
});
}
let asset = *by_source_index
.get(&cursor)
.ok_or(ScaleError::IncompleteClosure {
reason: "dangling_source_parent_node_index",
})?;
pending.push(cursor);
match asset.parent_source_node_index {
Some(parent) => {
reject_if_unskinned(parent)?;
cursor = parent;
}
None => {
return Err(ScaleError::IncompleteClosure {
reason: "joint_not_descendant_of_scaled_root",
});
}
}
}
for source in pending {
domain.insert(source);
reaches_root.insert(source);
}
}
let mut children: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
for node in &document.assets.source_skeleton.nodes {
if let Some(parent) = node.parent_source_node_index {
children
.entry(parent)
.or_default()
.push(node.source_node_index);
}
}
let joint_owner: BTreeMap<usize, usize> = document
.assets
.source_skeleton
.skins
.iter()
.flat_map(|skin| {
skin.joint_source_node_indices
.iter()
.map(move |&joint| (joint, skin.source_skin_index))
})
.collect();
let mut queue: Vec<usize> = domain.iter().copied().collect();
while let Some(node) = queue.pop() {
for &child in children.get(&node).into_iter().flatten() {
if domain.contains(&child) {
continue;
}
if let Some(&owner) = joint_owner.get(&child)
&& owner != skin.source_skin_index
{
return Err(ScaleError::IncompleteClosure {
reason: "descendant_joint_of_another_skin",
});
}
reject_if_unskinned(child)?;
domain.insert(child);
queue.push(child);
}
}
Ok(domain)
}
#[derive(Clone, Copy)]
pub(in crate::scale) enum SourceWorldAccumulator {
Narrow(Mat4),
Widened(DMat4),
}
pub(in crate::scale) fn source_world_matrix(
start: usize,
by_source_index: &BTreeMap<usize, &SourceNodeAsset>,
connector_sources: &BTreeSet<usize>,
cache: &mut BTreeMap<usize, SourceWorldAccumulator>,
) -> Result<Mat4, ScaleError> {
if let Some(&world) = cache.get(&start) {
return narrow_source_world(start, world);
}
let mut chain = Vec::new();
let mut cursor = start;
let mut visited = BTreeSet::new();
let base_world = loop {
if let Some(&world) = cache.get(&cursor) {
break world;
}
if !visited.insert(cursor) {
return Err(ScaleError::IncompleteClosure {
reason: "cyclic_source_parent_chain",
});
}
let asset = *by_source_index
.get(&cursor)
.ok_or(ScaleError::IncompleteClosure {
reason: "missing_source_node",
})?;
chain.push((cursor, asset));
match asset.parent_source_node_index {
Some(parent) => cursor = parent,
None => break SourceWorldAccumulator::Narrow(Mat4::IDENTITY),
}
};
let mut world = base_world;
for (node, asset) in chain.into_iter().rev() {
let local = local_rest_matrix(&asset.local_rest);
if !mat4_is_finite(local) {
return Err(ScaleError::NonFiniteSourceTransform {
source_node_index: node,
});
}
world = if connector_sources.contains(&node) {
let widened = match world {
SourceWorldAccumulator::Narrow(world) => world.as_dmat4(),
SourceWorldAccumulator::Widened(world) => world,
} * local.as_dmat4();
if !widened.is_finite() {
return Err(ScaleError::NonFiniteSourceTransform {
source_node_index: node,
});
}
SourceWorldAccumulator::Widened(widened)
} else {
let narrowed = match world {
SourceWorldAccumulator::Narrow(world) => world * local,
SourceWorldAccumulator::Widened(world) => (world * local.as_dmat4()).as_mat4(),
};
if !mat4_is_finite(narrowed) {
return Err(ScaleError::NonFiniteSourceTransform {
source_node_index: node,
});
}
SourceWorldAccumulator::Narrow(narrowed)
};
cache.insert(node, world);
}
narrow_source_world(start, world)
}
fn narrow_source_world(
source_node_index: usize,
world: SourceWorldAccumulator,
) -> Result<Mat4, ScaleError> {
let world = match world {
SourceWorldAccumulator::Narrow(world) => world,
SourceWorldAccumulator::Widened(world) => world.as_mat4(),
};
if !mat4_is_finite(world) {
return Err(ScaleError::NonFiniteSourceTransform { source_node_index });
}
Ok(world)
}
pub(in crate::scale) fn local_rest_matrix(rest: &SourceNodeLocalRest) -> Mat4 {
match rest {
SourceNodeLocalRest::Trs {
translation,
rotation,
scale,
} => Mat4::from_scale_rotation_translation(*scale, *rotation, *translation),
SourceNodeLocalRest::Matrix(matrix) => *matrix,
}
}
pub(in crate::scale) fn world_rests(skeleton: &Skeleton) -> Result<Vec<Mat4>, ScaleError> {
world_rest_matrices(skeleton).map_err(|error| match error {
crate::model::WorldMatrixError::NonFiniteTransform { node } => {
ScaleError::NonFiniteTransform { node }
}
crate::model::WorldMatrixError::InvalidParent { node, parent } => {
ScaleError::InvalidParent { node, parent }
}
})
}
#[derive(Debug, Clone, Copy)]
pub(in crate::scale) struct WorldBonePose {
pub(in crate::scale) matrix: Mat4,
pub(in crate::scale) translation_rounding_magnitude: f64,
}
pub(in crate::scale) struct WorldPose {
pub(in crate::scale) bones: Vec<WorldBonePose>,
}
impl WorldPose {
pub(in crate::scale) fn bone(&self, node: BoneId) -> Result<WorldBonePose, ScaleError> {
self.bones
.get(node)
.copied()
.ok_or(ScaleError::BoneIndexOutOfRange { index: node })
}
}
pub(in crate::scale) fn child_translation_rounding_magnitude(
parent: WorldBonePose,
local: Mat4,
) -> f64 {
parent.translation_rounding_magnitude
+ translation_composition_rounding_base(parent.matrix, local)
}
pub(in crate::scale) fn rest_world_pose(skeleton: &Skeleton) -> Result<WorldPose, ScaleError> {
let matrices = world_rests(skeleton)?;
let mut bones: Vec<WorldBonePose> = Vec::with_capacity(matrices.len());
for (node, bone) in skeleton.bones.iter().enumerate() {
let matrix = matrices[node];
let translation_rounding_magnitude = match bone.parent {
Some(parent) if parent < node => {
child_translation_rounding_magnitude(bones[parent], bone.rest.to_mat4())
}
_ => 0.0,
};
bones.push(WorldBonePose {
matrix,
translation_rounding_magnitude,
});
}
Ok(WorldPose { bones })
}
#[cfg(test)]
std::thread_local! {
static AFFECTED_SKIN_CLASSIFICATION_STEPS: Cell<usize> = const { Cell::new(0) };
}
fn skin_palette_intersects_affected(instance: &MeshInstance, affected: &BTreeSet<BoneId>) -> bool {
instance.skin_joints.iter().any(|joint| {
#[cfg(test)]
AFFECTED_SKIN_CLASSIFICATION_STEPS
.set(AFFECTED_SKIN_CLASSIFICATION_STEPS.get().saturating_add(1));
affected.contains(joint)
})
}
pub(in crate::scale) fn affected_skin_instance_indices(
document: &Document,
affected: &BTreeSet<BoneId>,
) -> Vec<usize> {
document
.assets
.instances
.iter()
.enumerate()
.filter_map(|(instance_index, instance)| {
skin_palette_intersects_affected(instance, affected).then_some(instance_index)
})
.collect()
}
#[cfg(test)]
pub(in crate::scale) fn reset_affected_skin_classification_steps() {
AFFECTED_SKIN_CLASSIFICATION_STEPS.set(0);
}
#[cfg(test)]
pub(in crate::scale) fn affected_skin_classification_steps() -> usize {
AFFECTED_SKIN_CLASSIFICATION_STEPS.get()
}
pub(in crate::scale) fn stored_instance_bind(
document: &Document,
instance: &MeshInstance,
slot: usize,
joint: BoneId,
) -> Result<Option<Mat4>, ScaleError> {
if !instance.skin_ibms.is_empty() {
return instance
.skin_ibms
.get(slot)
.copied()
.map(Some)
.ok_or(ScaleError::BoneIndexOutOfRange { index: joint });
}
let bone = document
.skeleton
.bones
.get(joint)
.ok_or(ScaleError::BoneIndexOutOfRange { index: joint })?;
Ok(bone.inverse_bind)
}
pub(in crate::scale) fn instance_bind(
document: &Document,
instance: &MeshInstance,
slot: usize,
joint: BoneId,
) -> Result<Mat4, ScaleError> {
if let Some(stored) = stored_instance_bind(document, instance, slot, joint)? {
return Ok(stored);
}
if instance_source_skin(document, instance).is_some_and(|skin| {
skin.inverse_bind_accessor.status == SourceInverseBindAccessorStatus::Absent
}) {
return Ok(Mat4::IDENTITY);
}
Err(ScaleError::MissingInverseBind { node: joint })
}
fn instance_source_skin<'a>(
document: &'a Document,
instance: &MeshInstance,
) -> Option<&'a SourceSkinAsset> {
if document.assets.source_skeleton.coverage != SourceSkeletonCoverage::Complete {
return None;
}
document.assets.source_skeleton.skins.iter().find(|skin| {
skin.attachments
.iter()
.any(|attachment| attachment.source_node_index == instance.source_node_index)
})
}