flutter3d
Showcase Changelog 38 packages API reference

Meshopt compression: the source

since 0.7.0 Formats and I/O

Regions the guide quotes: write (line 33), used (line 41), decode (line 47), check (line 76).

1/// `EXT_meshopt_compression`, written by `GltfWriter(compressGeometry:
2/// true)` and decoded again by `GltfLoader`.
3///
4/// Quoted by `meshopt.md` and shown whole in the Source tab.
5library;
6
7import 'dart:convert';
8
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_showcase/src/demo/demo.dart';
11import 'package:vector_math/vector_math.dart';
13final class MeshoptDemo extends ShowcaseDemo {
14 late final bool _usedMeshopt;
15 late final List<DocumentDifference> _differences;
16 late final int _plainBytes;
17 late final int _compressedBytes;
18 late final ModelDocument _readBack;
20 final _document = PlainModelDocument(
21 surfaces: <ModelSurface>[
22 ModelSurface(mesh: SphereShape(segments: 32, rings: 16).build()),
23 ],
24 // `GltfWriter` reads placement from the node graph alone, so a document
25 // headed for glTF needs a node naming its surface.
26 nodes: <ModelNode>[
27 ModelNode(surfaces: <int>[0]),
28 ],
29 );
31 @override
32 Future<void> prepare(DemoContext context) async {
33 // The vertex and index bitstreams themselves live in
34 // `meshopt_vertex_codec.dart` and `meshopt_index_codec.dart`; this is
35 // the writer that reaches for them.
36 final plain = GltfWriter(_document).writeGlb();
37 final compressed = GltfWriter(_document, compressGeometry: true).writeGlb();
38 _plainBytes = plain.length;
39 _compressedBytes = compressed.length;
41 // A GLB's JSON chunk is plain UTF-8 text at the front of the file, so
42 // whether the extension was actually reached for is a byte search away.
43 _usedMeshopt = latin1
44 .decode(compressed, allowInvalid: true)
45 .contains('EXT_meshopt_compression');
47 // Reordering moves which byte offset a vertex or an index lands at
48 // without changing what the mesh draws, so the comparison asks whether
49 // the same triangles came back, not whether they are in the same
50 // order.
51 _readBack = await GltfLoader().load(compressed);
52 _differences = compareModelDocuments(
53 _document,
54 _readBack,
55 tolerance: 1e-2,
56 allowVertexReorder: true,
57 );
58 }
60 @override
61 Scene build(DemoContext context) => Scene()
62 ..add(
63 MeshNode(
64 DeviceMesh.upload(context.device, _readBack.surfaces.single.mesh),
65 Material(baseColor: Vector4(0.6, 0.7, 0.85, 1.0), roughness: 0.5),
66 name: 'ball',
67 ),
68 )
69 ..add(
70 LightNode(name: 'sun', intensity: 3.0)
71 ..setLocalForward(Vector3(-0.4, -1.0, -0.3)),
72 );
74 @override
75 void verify(Scene scene, FrameResult frame) {
76 if (!_usedMeshopt) {
77 throw StateError('compressGeometry did not reach for meshopt at all');
78 }
79 if (_differences.isNotEmpty) {
80 throw StateError('the compressed mesh drew different triangles back');
81 }
82 if (_compressedBytes >= _plainBytes) {
83 throw StateError('the compressed file was not smaller');
84 }
85 if (frame.drawCalls < 1) {
86 throw StateError('the ball was not drawn');
87 }
88 }