flutter3d
Showcase Changelog 38 packages API reference

Gaussian splats: the source

since 0.7.0 Scene and geometry

Regions the guide quotes: rows (line 36), decode (line 54), contributor (line 57), check (line 71), ply (line 85).

1/// A small Gaussian cloud, written to a binary PLY and read back the way a
2/// capture would arrive.
3library;
4
5import 'dart:convert';
6import 'dart:math' as math;
7import 'dart:typed_data';
8
9import 'package:flutter3d/flutter3d.dart';
10import 'package:flutter3d_showcase/src/demo/demo.dart';
11import 'package:vector_math/vector_math.dart';
13typedef _SplatRow = ({
14 Vector3 position,
15 Vector3 colour,
16 double opacity,
17 double scale,
18});
20final class GaussianSplatsDemo extends ShowcaseDemo {
21 late final SplatCloud _cloud;
22 late final SplatContributor _contributor;
24 static const int _count = 35;
26 @override
27 void configureView(DemoContext context) {
28 context.orbit
29 ..distance = 5.0
30 ..pitch = 0.18
31 ..yaw = 0.4;
32 }
34 @override
35 Scene build(DemoContext context) {
36 final List<_SplatRow> rows = <_SplatRow>[
37 for (var i = 0; i < _count; i++)
38 (
39 position: Vector3(
40 (i % 7 - 3) * 0.5,
41 0.4 * math.sin(i * 0.6),
42 (i ~/ 7 - 2) * 0.5,
43 ),
44 colour: Vector3(
45 0.3 + 0.5 * (i / _count),
46 0.4,
47 0.9 - 0.5 * (i / _count),
48 ),
49 opacity: 0.85,
50 scale: 0.16,
51 ),
52 ];
54 final Uint8List bytes = _splatPlyBytes(rows);
55 _cloud = parseSplatPly(bytes);
57 _contributor = context.renderer.addContributor(SplatContributor(_cloud));
59 final Scene scene = Scene()
60 ..ambientColor = Vector3(0.4, 0.46, 0.6)
61 ..ambientIntensity = 0.24
62 ..add(
63 LightNode(name: 'key', intensity: 1.2)
64 ..setLocalForward(Vector3(-0.4, -0.7, -0.5)),
65 );
66 return scene;
67 }
69 @override
70 void verify(Scene scene, FrameResult frame) {
71 final Int32List order = _cloud.sortedBackToFront(
72 Vector3(0.0, 0.0, -5.0),
73 Vector3(0.0, 0.0, 1.0),
74 );
75 final Set<int> seen = order.toSet();
76 if (_cloud.count != _count ||
77 seen.length != _count ||
78 !_contributor.isActive ||
79 frame.drawCalls < 1) {
80 throw StateError('the splat cloud was not decoded and drawn');
81 }
82 }
85/// A binary PLY holding [rows], in exactly the properties `parseSplatPly`
86/// requires: `x y z`, `f_dc_0..2`, `opacity`, `scale_0..2`, `rot_0..3`.
87Uint8List _splatPlyBytes(List<_SplatRow> rows) {
88 const List<String> properties = <String>[
89 'x',
90 'y',
91 'z',
92 'f_dc_0',
93 'f_dc_1',
94 'f_dc_2',
95 'opacity',
96 'scale_0',
97 'scale_1',
98 'scale_2',
99 'rot_0',
100 'rot_1',
101 'rot_2',
102 'rot_3',
103 ];
104 final StringBuffer header = StringBuffer()
105 ..writeln('ply')
106 ..writeln('format binary_little_endian 1.0')
107 ..writeln('element vertex ${rows.length}');
108 for (final String name in properties) {
109 header.writeln('property float $name');
110 }
111 header.writeln('end_header');
112 final List<int> headerBytes = ascii.encode(header.toString());
114 final ByteData body = ByteData(rows.length * properties.length * 4);
115 var offset = 0;
116 // The inverse of `splatChannel` and `splatOpacity`: a readable 0..1 colour
117 // and probability, converted back to the coefficient and the logit the
118 // file stores.
119 double coefficientFor(double channel01) => (channel01 - 0.5) / kSplatShC0;
120 double logitFor(double probability) =>
121 math.log(probability / (1.0 - probability));
122 void writeFloat(double value) {
123 body.setFloat32(offset, value, Endian.little);
124 offset += 4;
125 }
127 for (final _SplatRow row in rows) {
128 writeFloat(row.position.x);
129 writeFloat(row.position.y);
130 writeFloat(row.position.z);
131 writeFloat(coefficientFor(row.colour.x));
132 writeFloat(coefficientFor(row.colour.y));
133 writeFloat(coefficientFor(row.colour.z));
134 writeFloat(logitFor(row.opacity));
135 writeFloat(math.log(row.scale));
136 writeFloat(math.log(row.scale));
137 writeFloat(math.log(row.scale));
138 // rot_0..3 is w, x, y, z in the file's own order; the identity rotation
139 // leaves every ellipsoid axis-aligned.
140 writeFloat(1.0);
141 writeFloat(0.0);
142 writeFloat(0.0);
143 writeFloat(0.0);
144 }
146 final Uint8List result = Uint8List(headerBytes.length + body.lengthInBytes);
147 result.setRange(0, headerBytes.length, headerBytes);
148 result.setRange(headerBytes.length, result.length, body.buffer.asUint8List());
149 return result;