///|
/// Scene transform evaluation for offline tooling. glTF nodes can provide a
/// matrix in the full specification; the current data model stores TRS, so
/// this module evaluates the TRS form and makes inheritance explicit.
///|
pub(all) struct WorldTransform {
node : Int
parent : Int?
local_matrix : Mat4
world : Mat4
depth : Int
} derive(Debug, ToJson, Eq)
///|
pub(all) struct TransformReport {
transforms : Array[WorldTransform]
roots : Array[Int]
max_depth : Int
cycle_nodes : Array[Int]
} derive(Debug, ToJson, Eq)
///|
fn node_translation(node : GltfNode) -> Vec3 {
match node.translation {
Some(value) => value
None => { x: 0.0, y: 0.0, z: 0.0 }
}
}
///|
fn node_rotation(node : GltfNode) -> Quat {
match node.rotation {
Some(value) => value
None => { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }
}
}
///|
fn node_scale(node : GltfNode) -> Vec3 {
match node.scale {
Some(value) => value
None => { x: 1.0, y: 1.0, z: 1.0 }
}
}
///|
pub fn node_local_transform(node : GltfNode) -> Mat4 {
mat4_from_trs(node_translation(node), node_rotation(node), node_scale(node))
}
///|
pub fn node_has_nontrivial_transform(node : GltfNode) -> Bool {
let translation = node_translation(node)
let rotation = node_rotation(node)
let scale = node_scale(node)
translation.x != 0.0 ||
translation.y != 0.0 ||
translation.z != 0.0 ||
rotation.x != 0.0 ||
rotation.y != 0.0 ||
rotation.z != 0.0 ||
rotation.w != 1.0 ||
scale.x != 1.0 ||
scale.y != 1.0 ||
scale.z != 1.0
}
///|
pub fn count_transformed_nodes(doc : GltfDocument) -> Int {
doc.nodes.fold(init=0, (count, node) => {
if node_has_nontrivial_transform(node) {
count + 1
} else {
count
}
})
}
///|
fn append_transform(
report : Array[WorldTransform],
node : Int,
parent : Int?,
local_matrix : Mat4,
world : Mat4,
depth : Int,
) -> Unit {
report.push({ node, parent, local_matrix, world, depth })
}
///|
fn walk_transform(
doc : GltfDocument,
node : Int,
parent : Int?,
parent_world : Mat4,
depth : Int,
visiting : Map[Int, Bool],
result : Array[WorldTransform],
cycles : Array[Int],
max_depth : Ref[Int],
) -> Unit {
if node < 0 || node >= doc.nodes.length() {
return
}
if visiting.contains(node) {
if !cycles.contains(node) {
cycles.push(node)
}
return
}
visiting[node] = true
let current = doc.nodes[node]
let local_matrix = node_local_transform(current)
let world = mat4_mul(parent_world, local_matrix)
append_transform(result, node, parent, local_matrix, world, depth)
if depth > max_depth.val {
max_depth.val = depth
}
for child in current.children {
walk_transform(
doc,
child,
Some(node),
world,
depth + 1,
visiting,
result,
cycles,
max_depth,
)
}
visiting.remove(node)
}
///|
pub fn evaluate_scene_transforms(doc : GltfDocument) -> TransformReport {
let transforms : Array[WorldTransform] = []
let roots : Array[Int] = []
let cycles : Array[Int] = []
let visiting : Map[Int, Bool] = Map([])
let max_depth : Ref[Int] = Ref(0)
for scene in doc.scenes {
for root in scene.nodes {
if !roots.contains(root) {
roots.push(root)
}
}
}
for root in roots {
walk_transform(
doc,
root,
None,
identity_mat4(),
1,
visiting,
transforms,
cycles,
max_depth,
)
}
{ transforms, roots, max_depth: max_depth.val, cycle_nodes: cycles }
}
///|
pub fn world_transform_for(report : TransformReport, node : Int) -> Mat4? {
for item in report.transforms {
if item.node == node {
return Some(item.world)
}
}
None
}
///|
pub fn transformed_node_bounds(bounds : Bounds3, transform : Mat4) -> Bounds3 {
if !bounds.valid {
return bounds
}
let corners = [
{ x: bounds.min.x, y: bounds.min.y, z: bounds.min.z },
{ x: bounds.max.x, y: bounds.min.y, z: bounds.min.z },
{ x: bounds.min.x, y: bounds.max.y, z: bounds.min.z },
{ x: bounds.max.x, y: bounds.max.y, z: bounds.min.z },
{ x: bounds.min.x, y: bounds.min.y, z: bounds.max.z },
{ x: bounds.max.x, y: bounds.min.y, z: bounds.max.z },
{ x: bounds.min.x, y: bounds.max.y, z: bounds.max.z },
{ x: bounds.max.x, y: bounds.max.y, z: bounds.max.z },
]
let mut output = bounds_empty()
for corner in corners {
output = bounds_include(output, mat4_transform_point(transform, corner))
}
output
}
///|
pub fn scene_root_count(doc : GltfDocument) -> Int {
evaluate_scene_transforms(doc).roots.length()
}
///|
pub fn scene_cycle_count(doc : GltfDocument) -> Int {
evaluate_scene_transforms(doc).cycle_nodes.length()
}
///|
pub fn scene_transform_depth(doc : GltfDocument) -> Int {
evaluate_scene_transforms(doc).max_depth
}