.fmat material files: the source
Open the live demo · Read the guide · View on GitHub
Regions the guide quotes: document (line 33), roundtrip (line 42), typo (line 48), report (line 81), check (line 137).
1/// `.fmat`: a material as a file of its own, read and written by
2/// `readFmat`/`writeFmat`, round-tripping through `MaterialDocument`.
3///
4/// Quoted by `fmat_files.md` and shown whole in the Source tab.
5library;
7import 'dart:convert';
8import 'dart:typed_data';
10import 'package:flutter/gestures.dart';
11import 'package:flutter/widgets.dart';
12import 'package:flutter3d/flutter3d.dart' hide Material;
13import 'package:flutter3d/flutter3d.dart' as f3d show Material;
14import 'package:flutter3d_app/flutter3d_app.dart';
15import 'package:flutter3d_showcase/src/demo/demo.dart';
16import 'package:vector_math/vector_math.dart';
18final class FmatFilesDemo extends ShowcaseDemo {
19 late final String _text;
20 late final MaterialDocument _roundTripped;
21 late final List<String> _typoWarnings;
22 late final Scene _scene;
25 void configureView(DemoContext context) {
26 context.orbit
27 ..distance = 2.4
28 ..pitch = 0.2;
29 }
32 Scene build(DemoContext context) {
33 final MaterialDocument document = MaterialDocument(
34 surface: SurfaceMaterial(
35 name: 'hull-plate',
36 baseColor: Vector4(0.55, 0.6, 0.65, 1.0),
37 metallic: 0.9,
38 roughness: 0.28,
39 ),
40 );
42 _text = writeFmat(document);
43 _roundTripped = readFmat(
44 Uint8List.fromList(utf8.encode(_text)),
45 name: 'hull-plate.fmat',
46 );
48 // An unknown key does not fail the file: it is recorded as a warning and
49 // everything this reader does know still loads.
50 const String typo =
51 '{"fmat": 1, "name": "typo", "roughnesss": 0.2, "baseColor": '
52 '[1, 1, 1, 1]}';
53 _typoWarnings = readFmat(
54 Uint8List.fromList(utf8.encode(typo)),
55 name: 'typo.fmat',
56 ).warnings;
58 final SurfaceMaterial surface = _roundTripped.surface;
59 return _scene = Scene()
60 ..add(
61 MeshNode(
62 DeviceMesh.upload(
63 context.device,
64 SphereShape(segments: 40, rings: 20).build(),
65 ),
66 f3d.Material(
67 name: surface.name,
68 baseColor: surface.baseColor,
69 metallic: surface.metallic,
70 roughness: surface.roughness,
71 ),
72 name: 'plate',
73 ),
74 )
75 ..add(
76 LightNode(name: 'sun', intensity: 3.0)
77 ..setLocalForward(Vector3(-0.4, -0.7, -0.6)),
78 );
79 }
81 String _report() =>
82 '$_text\n'
83 'round trip warnings: ${_roundTripped.warnings.isEmpty ? "none" : _roundTripped.warnings}\n'
84 'a file with an unknown key warns: $_typoWarnings';
86 // A material file is text first — Step 4 of the guide says so, and the
87 // report stays the whole story on the left. But a reader who cannot see
88 // what "metallic: 0.9, roughness: 0.28" looks like is trusting the
89 // numbers on faith, so the sphere those numbers actually shade sits
90 // beside the report rather than instead of it, and drags like every
91 // other page's viewport does.
93 Widget? customBody(BuildContext buildContext, DemoContext context) => Row(
94 children: <Widget>[
95 Expanded(
96 child: Container(
97 color: const Color(0xFF14161A),
98 padding: const EdgeInsets.all(24),
99 alignment: Alignment.topLeft,
100 child: SingleChildScrollView(
101 child: DefaultTextStyle(
102 style: const TextStyle(
103 color: Color(0xFFE8E8EC),
104 fontSize: 14,
105 fontFamily: 'monospace',
106 ),
107 child: Text(_report()),
108 ),
109 ),
110 ),
111 ),
112 Expanded(
113 child: Listener(
114 onPointerMove: (PointerMoveEvent event) =>
115 context.orbit.rotate(event.delta.dx, event.delta.dy),
116 onPointerSignal: (PointerSignalEvent event) {
117 if (event is PointerScrollEvent) {
118 context.orbit.zoom(event.scrollDelta.dy > 0.0 ? 1.1 : 1.0 / 1.1);
119 }
120 },
121 child: SceneSurface(
122 renderer: context.renderer,
123 scene: _scene,
124 view: context.view,
125 settings: () => const RenderSettings(),
126 onBeforeFrame: () =>
127 context.orbit.syncProjectionDepth(context.camera),
128 presentFrame: presentFrame,
129 ),
130 ),
131 ),
132 ],
133 );
136 void verify(Scene scene, FrameResult frame) {
137 final SurfaceMaterial original = SurfaceMaterial(
138 metallic: 0.9,
139 roughness: 0.28,
140 );
141 final SurfaceMaterial read = _roundTripped.surface;
142 if (read.metallic != original.metallic ||
143 read.roughness != original.roughness) {
144 throw StateError('the round trip changed the material');
145 }
146 if (_roundTripped.warnings.isNotEmpty) {
147 throw StateError(
148 'a clean file should not warn: ${_roundTripped.warnings}',
149 );
150 }
151 if (_typoWarnings.isEmpty) {
152 throw StateError('an unknown key should have warned and did not');
153 }
154 if (frame.drawCalls < 1) {
155 throw StateError('the plate was not drawn');
156 }
157 }
158}