merged static method

Geometry merged(
  1. Iterable<GeometryPlacement> placements
)

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,
  );
}