extractMeshData method
This subtree's geometry flattened into one snapshot, with every descendant transform baked into the vertices.
transform places the result. The default identity leaves the data in
this node's local space, which is the frame a collider attached to this
node expects; pass globalTransform for world space instead, which is
the frame for a collider on a node that has none of its own.
Baking matters because physics does not simulate scale, so a collider can never pick up a scale from the graph the way a mesh does. Pick the frame so that whatever the collider ends up on carries only a translation and a rotation. Runtime glTF import is the case to watch, since fromGlbAsset roots its model under the source handedness flip, which has to go into the vertices rather than onto the collider:
final model = await Node.fromGlbAsset('assets/ground.glb');
final ground = Node(name: 'ground')..add(model);
ground.addComponent(
Collider(
shape: model
.extractMeshData(transform: model.localTransform)
.toTriMeshShape(),
),
);
Scenes from loadScene carry that flip in their vertices already, so there the default frame is the one to use.
An attribute missing from any one primitive is dropped from the whole result, since the merged mesh carries a single attribute set. Skinned geometry contributes its bind pose.
Throws a StateError when the subtree holds no triangles, or holds
geometry this cannot read: caller-managed vertex buffers
(Geometry.isReadable is false), non-triangle primitives, or instanced
meshes, none of which can be flattened into a single mesh.
Implementation
MeshData extractMeshData({Matrix4? transform}) {
final parts = <MeshData>[];
_collectMeshData(transform ?? Matrix4.identity(), parts);
if (parts.isEmpty) {
throw StateError(
'Node "$name" has no triangle geometry in its subtree to extract',
);
}
return MeshData.merge(_reduceToSharedAttributes(parts));
}