flutter3d
Showcase Changelog 38 packages API reference

Draco meshes

since 0.7.0 Formats and I/O

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