Draco meshes
Open the live demo · Read the source · View on GitHub
Draco is a compressed mesh format most export pipelines can write into a
glTF file, behind KHR_draco_mesh_compression. This engine has no Draco
encoder, only a decoder, so this page decodes a real Draco bitstream
rather than building a fake one from scratch.
Step 1: A real bitstream #
This is the exact bytes of a small edgebreaker-encoded mesh, the same fixture the engine's own decoder tests check against triangles computed independently. There is no pure-Dart Draco encoder to build this file with here, only the decoder that reads it.
// A small edgebreaker-encoded mesh, the same fixture
// `flutter3d_core`'s own decoder test checks against an independently
// computed set of triangles. Edgebreaker connectivity carries no index
// buffer and no vertex order of its own: both come out however the
// encoder's traversal happened to walk the mesh.
static const String _fixtureBase64 =
'RFJBQ08CAgEBAAAAQDwBPAEBHx8BF2/btm3bfdu2bdv2ruu6ruu6ruu6rusC/wEz/wI1RAL/'
'AAAAAAABAAkDAAACAQEJAwABAwEBAQAPIwEHAQgBEAEBAQkBFxTABEaQRtBEhyBH5YsP2Cnb'
'LPkrs79pACDdXi8AUMmBEQBA+gYCgBrctAHA6m9DAYAfsbEGAFY/rh8AqMHMbwDgo5GqAQAA'
'wPwGAEAarh8AsMEaawDA9WtDAQAfsWkDANd/AwGADW4nBIDtV60BAMzAvQEADuOWBAB3O94D'
'AMLH6BYAyDZfeQAgfLS1AYC7jckFALodqhUAAACYXAAA3LG1AQC77ZUHAMjH6BYA0DbjPQCA'
'fNySAMBu/zMAEQKoACR0HgLAYSQDgAgnDQAJjQFAhEMFABpbAugdMwB4+DeAl5UAgFkjBKAM'
'CQFA0hECYAYaAUDU3gBgehgAMHsJwLdZAYCJMgCQUNIAiJAZAEhIVACcESYA4DADwId9A3CJ'
'CQBUW0IAyIgQACYtIQBjnBEAQwUOoCUAAAAA/z8AAAAgrr4AUBi/ACCuvgAwcz8OAAMBAQP/'
'B4E2/5+AAQGrAQP//////58BAf//////o4EBqwEBAQH/m4ATIJkSAFI2CrI77Ywk2kkIW/ru'
'gP8DAAD/AQAACg==';
Step 2: Decode it #
decodeDraco reads both connectivity methods Draco supports, sequential
and edgebreaker, and every prediction scheme a current encoder writes.
This file uses edgebreaker, which carries no index buffer of its own:
both the indices and the vertex order come out however the encoder's
traversal happened to walk the mesh.
final bytes = base64Decode(_fixtureBase64.replaceAll('\n', ''));
_mesh = decodeDraco(bytes);
Step 3: Turn it into something this engine can draw #
A DracoMesh is not yet a MeshData. Its attributes are keyed by
Draco's own attribute id rather than by name, so a caller picks out the
ones it wants; this page takes positions and recomputes a flat normal per
triangle.
// A decoded `DracoMesh` is not yet a `MeshData`: its attributes are
// keyed by Draco's own attribute id, not by name, so a caller picks
// out the ones it wants. This page draws positions only, with a flat
// normal recomputed per triangle.
final position = _mesh.attributes.values.firstWhere(
(attribute) => attribute.type == DracoAttributeType.position,
);
final builder = MeshBuilder(
VertexLayout.standard,
reserveVertices: _mesh.indices.length,
reserveIndices: _mesh.indices.length,
);
Vector3 pointAt(int index) => Vector3(
position.values[index * 3],
position.values[index * 3 + 1],
position.values[index * 3 + 2],
);
for (var t = 0; t + 2 < _mesh.indices.length; t += 3) {
final a = pointAt(_mesh.indices[t]);
final b = pointAt(_mesh.indices[t + 1]);
final c = pointAt(_mesh.indices[t + 2]);
final cross = (b - a).cross(c - a);
final normal = cross.length2 > 0.0
? cross.normalized()
: Vector3(0, 0, 1);
final texcoord = Vector2.zero();
final i0 = builder.addVertex(
position: a,
normal: normal,
texcoord: texcoord,
);
final i1 = builder.addVertex(
position: b,
normal: normal,
texcoord: texcoord,
);
final i2 = builder.addVertex(
position: c,
normal: normal,
texcoord: texcoord,
);
builder.addTriangle(i0, i1, i2);
}
_upload = builder.build();
Step 4: Check the claim #
This particular fixture always decodes to sixty triangles. A decoder that drifted from that, or that produced no points at all, would be a different mesh entirely.
if (_mesh.indices.length != 180) {
throw StateError(
'this fixture always decodes to 60 triangles (180 corners); got '
'${_mesh.indices.length}',
);
}
if (_mesh.pointCount == 0) {
throw StateError('the decoded mesh has no points');
}
if (frame.drawCalls < 1) {
throw StateError('the decoded mesh was not drawn');
}