MeshGeometry.fromArrays constructor

MeshGeometry.fromArrays({
  1. required Float32List positions,
  2. Float32List? normals,
  3. Float32List? texCoords,
  4. Float32List? colors,
  5. List<int>? indices,
  6. PrimitiveType primitiveType = gpu.PrimitiveType.triangle,
  7. Aabb3? bounds,
  8. GeometryStorage storage = GeometryStorage.fixed,
})

Builds a mesh from structure-of-arrays vertex attributes.

positions is required and holds three floats per vertex. The optional attributes hold, per vertex, three floats for normals, two for texCoords, and four for colors; each must match the vertex count implied by positions when supplied. Absent attributes fall back to defaults: texture coordinate (0, 0) and color opaque white.

When normals is omitted and primitiveType is a triangle list, area-weighted vertex normals are generated from the faces; for line and point primitives, absent normals keep their default. indices, when supplied, is an index list; when omitted a triangle mesh must have a vertex count that is a multiple of three.

primitiveType selects how the vertex/index data is assembled into primitives when drawn, and defaults to a triangle list.

storage selects whether the mesh can later be updated in place. An updatable mesh fixes its indexed-or-not state at construction: supplying indices makes rebuild require indices thereafter, and omitting them makes it reject them. To start empty, pass a zero-length positions array with GeometryStorage.updatable.

bounds, when supplied, becomes the local-space culling AABB and skips the construction-time position scan; a caller that assembled the vertices off the UI thread (a worker isolate) can compute it there. It must cover every position or the mesh over-culls.

Implementation

MeshGeometry.fromArrays({
  required Float32List positions,
  Float32List? normals,
  Float32List? texCoords,
  Float32List? colors,
  List<int>? indices,
  gpu.PrimitiveType primitiveType = gpu.PrimitiveType.triangle,
  Aabb3? bounds,
  this.storage = GeometryStorage.fixed,
}) {
  if (positions.length % 3 != 0) {
    throw ArgumentError(
      'positions has ${positions.length} floats; expected a multiple of '
      'three (one vec3 per vertex)',
    );
  }
  final vertexCount = positions.length ~/ 3;
  this.primitiveType = primitiveType;

  if (bounds != null) {
    final center = (bounds.min + bounds.max) * 0.5;
    setLocalBounds(
      bounds,
      Sphere.centerRadius(center, (bounds.max - center).length),
    );
  }

  // Normals are generated from triangle faces; line and point
  // geometry has none, so absent normals are left at their default.
  final resolvedNormals =
      normals ??
      (vertexCount > 0 && primitiveType == gpu.PrimitiveType.triangle
          ? InterleavedLayoutAdapter.generateNormals(
              positions: positions,
              vertexCount: vertexCount,
              indices: indices,
            )
          : null);

  if (storage == GeometryStorage.fixed) {
    _uploadFixed(
      positions,
      vertexCount,
      resolvedNormals,
      texCoords,
      colors,
      indices,
    );
  } else {
    _indexed = indices != null;
    _setCpuStreams(
      positions,
      vertexCount,
      resolvedNormals,
      texCoords,
      colors,
    );
    _positionRing = _RingBufferStream(
      InterleavedLayoutAdapter.positionStreamBytes,
    );
    _normalRing = _RingBufferStream(
      InterleavedLayoutAdapter.normalStreamBytes,
    );
    _texCoordRing = _RingBufferStream(
      InterleavedLayoutAdapter.texCoordStreamBytes,
    );
    _colorRing = _RingBufferStream(InterleavedLayoutAdapter.colorStreamBytes);
    _liveVertexCount = vertexCount;
    // Indices first, so the attribute upload can retain them for raycasting.
    if (_indexed) _uploadIndices(indices!);
    _uploadAllStreams();
    _recomputeBounds();
  }
}