flutter3d
Showcase Changelog 38 packages API reference

.fmat material files

since 0.3.0 Shading and materials

A .fmat is a material as a file of its own, separate from any model that wears it. That is what lets a studio's brushed steel be edited once and shared by every mesh that uses it, instead of being re-exported into each one of them by hand. readFmat and writeFmat are the two ends of it, and what one writes, the other reads back equal.

Step 1: A material #

Metallic, rough, and tinted a cool grey. Nothing exotic, which is the point: most of what a .fmat carries is exactly this handful of scalars.

final MaterialDocument document = MaterialDocument(
  surface: SurfaceMaterial(
    name: 'hull-plate',
    baseColor: Vector4(0.55, 0.6, 0.65, 1.0),
    metallic: 0.9,
    roughness: 0.28,
  ),
);

Step 2: Write it, and read it back #

writeFmat turns the document into the JSON text a person would edit by hand. readFmat turns that text back into a document, and the two are supposed to agree on every field.

_text = writeFmat(document);
_roundTripped = readFmat(
  Uint8List.fromList(utf8.encode(_text)),
  name: 'hull-plate.fmat',
);

Step 3: A file with a typo in it #

An unknown key does not fail the whole file. roughnesss, misspelled, is recorded as a warning and every field this reader does understand still loads with it.

// An unknown key does not fail the file: it is recorded as a warning and
// everything this reader does know still loads.
const String typo =
    '{"fmat": 1, "name": "typo", "roughnesss": 0.2, "baseColor": '
    '[1, 1, 1, 1]}';
_typoWarnings = readFmat(
  Uint8List.fromList(utf8.encode(typo)),
  name: 'typo.fmat',
).warnings;

Step 4: The report, beside the material it describes #

The page shows the written text and both warning lists directly: a material file is something to read first, not something to spin around first. But baseColor, metallic and roughness are values a reader has intuitions about, and the surest way to check those intuitions against what the file actually says is to look at the surface those numbers shade — so a sphere wearing the round-tripped material sits beside the report, and drags like this app's ordinary viewport does.

String _report() =>
    '$_text\n'
    'round trip warnings: ${_roundTripped.warnings.isEmpty ? "none" : _roundTripped.warnings}\n'
    'a file with an unknown key warns: $_typoWarnings';

Step 5: What this page checks #

The round trip has to leave metallic and roughness exactly where they started, a clean file has to produce no warnings, and the misspelled file has to produce at least one.

final SurfaceMaterial original = SurfaceMaterial(
  metallic: 0.9,
  roughness: 0.28,
);
final SurfaceMaterial read = _roundTripped.surface;
if (read.metallic != original.metallic ||
    read.roughness != original.roughness) {
  throw StateError('the round trip changed the material');
}
if (_roundTripped.warnings.isNotEmpty) {
  throw StateError(
    'a clean file should not warn: ${_roundTripped.warnings}',
  );
}
if (_typoWarnings.isEmpty) {
  throw StateError('an unknown key should have warned and did not');
}
if (frame.drawCalls < 1) {
  throw StateError('the plate was not drawn');
}