merged static method
Bakes several placed geometries into one static geometry.
Each GeometryPlacement contributes its vertices transformed by its matrix (normals by the inverse-transpose, renormalized), its UVs unchanged and its indices offset. Use it for static scenery that shares one material — a run of wall blocks, a tiled floor, the grout lines of a plaza — so a retained renderer issues one draw call instead of one per block. Merged geometry is culled as a single bounding box, so keep batches to what is plausibly visible together (a room, not a city).
Throws ArgumentError when placements is empty or a matrix is not
invertible (a zero scale axis), since that would produce NaN normals.
Implementation
static Geometry merged(Iterable<GeometryPlacement> placements) {
final list = placements.toList(growable: false);
if (list.isEmpty) {
throw ArgumentError.value(placements, 'placements', 'must not be empty');
}
var vertexTotal = 0;
var indexTotal = 0;
for (final placement in list) {
vertexTotal += placement.geometry.vertexCount;
indexTotal += placement.geometry.indices.length;
}
final vertices = List<Vector3>.filled(vertexTotal, Vector3.zero());
final normals = List<Vector3>.filled(vertexTotal, Vector3.zero());
final uvs = List<Vector2>.filled(vertexTotal, Vector2.zero());
final indices = List<int>.filled(indexTotal, 0);
var vertexOffset = 0;
var indexOffset = 0;
final normalMatrix = Matrix3.zero();
for (final placement in list) {
final geometry = placement.geometry;
final transform = placement.transform;
final inverse = Matrix4.zero();
final determinant = inverse.copyInverse(transform);
if (determinant == 0.0 || !determinant.isFinite) {
throw ArgumentError(
'Geometry.merged: placement transform is not invertible.',
);
}
// Normal matrix = inverse-transpose of the upper 3×3.
inverse.copyRotation(normalMatrix);
normalMatrix.transpose();
final flipWinding = determinant < 0;
for (var i = 0; i < geometry.vertexCount; i++) {
vertices[vertexOffset + i] = transform.transformed3(
geometry.vertices[i],
);
final n = normalMatrix.transformed(geometry.normals[i]);
if (n.length2 > 0) n.normalize();
normals[vertexOffset + i] = n;
uvs[vertexOffset + i] = geometry.uvs[i].clone();
}
final source = geometry.indices;
for (var i = 0; i < source.length; i += 3) {
// A mirroring transform reverses the triangle winding; swap two
// indices so front faces stay front faces.
indices[indexOffset + i] = source[i] + vertexOffset;
indices[indexOffset + i + 1] =
source[flipWinding ? i + 2 : i + 1] + vertexOffset;
indices[indexOffset + i + 2] =
source[flipWinding ? i + 1 : i + 2] + vertexOffset;
}
vertexOffset += geometry.vertexCount;
indexOffset += source.length;
}
return Geometry(
vertices: vertices,
normals: normals,
uvs: uvs,
indices: indices,
);
}