merge static method
Concatenates parts into one snapshot, rebasing indices.
Every part must share the primitive type, the presence of each optional attribute, and the same custom attribute names and widths. When some parts are indexed and others are not, sequential indices are synthesized for the unindexed ones.
Implementation
static MeshData merge(List<MeshData> parts) {
if (parts.isEmpty) {
throw ArgumentError('merge requires at least one MeshData');
}
if (parts.length == 1) return parts.first;
final first = parts.first;
for (final part in parts.skip(1)) {
if (part.primitiveType != first.primitiveType ||
(part.normals == null) != (first.normals == null) ||
(part.texCoords == null) != (first.texCoords == null) ||
(part.colors == null) != (first.colors == null)) {
throw ArgumentError(
'merge requires matching primitive types and attribute sets',
);
}
if (part.customAttributes.length != first.customAttributes.length ||
first.customAttributes.entries.any(
(e) =>
part.customAttributes[e.key]?.components != e.value.components,
)) {
throw ArgumentError(
'merge requires matching custom attribute names and widths',
);
}
}
final anyIndexed = parts.any((p) => p.indices != null);
var vertexCount = 0;
var indexCount = 0;
for (final part in parts) {
vertexCount += part.vertexCount;
if (anyIndexed) {
indexCount += part.indices?.length ?? part.vertexCount;
}
}
final positions = Float32List(vertexCount * 3);
final normals = first.normals == null ? null : Float32List(vertexCount * 3);
final texCoords = first.texCoords == null
? null
: Float32List(vertexCount * 2);
final colors = first.colors == null ? null : Float32List(vertexCount * 4);
final custom = <String, MeshAttributeData>{
for (final entry in first.customAttributes.entries)
entry.key: MeshAttributeData(
Float32List(vertexCount * entry.value.components),
components: entry.value.components,
),
};
final indices = anyIndexed ? List<int>.filled(indexCount, 0) : null;
var vertexBase = 0;
var indexBase = 0;
for (final part in parts) {
positions.setRange(
vertexBase * 3,
vertexBase * 3 + part.positions.length,
part.positions,
);
normals?.setRange(
vertexBase * 3,
vertexBase * 3 + part.normals!.length,
part.normals!,
);
texCoords?.setRange(
vertexBase * 2,
vertexBase * 2 + part.texCoords!.length,
part.texCoords!,
);
colors?.setRange(
vertexBase * 4,
vertexBase * 4 + part.colors!.length,
part.colors!,
);
for (final entry in part.customAttributes.entries) {
final components = entry.value.components;
custom[entry.key]!.data.setRange(
vertexBase * components,
vertexBase * components + entry.value.data.length,
entry.value.data,
);
}
if (indices != null) {
final partIndices = part.indices;
final partCount = partIndices?.length ?? part.vertexCount;
for (var i = 0; i < partCount; i++) {
indices[indexBase + i] = (partIndices?[i] ?? i) + vertexBase;
}
indexBase += partCount;
}
vertexBase += part.vertexCount;
}
return MeshData(
positions: positions,
vertexCount: vertexCount,
normals: normals,
texCoords: texCoords,
colors: colors,
indices: indices,
primitiveType: first.primitiveType,
customAttributes: custom,
);
}