writeFmat function

String writeFmat(
  1. MaterialDocument document
)

Writes document back out, so a tool that edits a material can save it.

Round-trips: what this writes, readFmat reads back to an equal document. That is the property a material editor stands on, and it is measured rather than asserted — see fmat_test.dart.

Implementation

String writeFmat(MaterialDocument document) {
  final surface = document.surface;

  String? pathOf(TextureBinding? slot) =>
      slot == null ||
          slot.imageIndex < 0 ||
          slot.imageIndex >= document.images.length
      ? null
      : document.images[slot.imageIndex];

  Object? slot(TextureBinding? binding) {
    final path = pathOf(binding);
    if (path == null) return null;
    final sampling = binding!.sampling;
    const plain = TextureSampling();
    if (sampling.magLinear == plain.magLinear &&
        sampling.minLinear == plain.minLinear &&
        sampling.useMipmaps == plain.useMipmaps &&
        sampling.mipLinear == plain.mipLinear &&
        sampling.wrapS == plain.wrapS &&
        sampling.wrapT == plain.wrapT) {
      // A slot that asks for nothing unusual is written as the path alone,
      // because that is what an artist writes by hand and what a diff should
      // show when only the path changed.
      return path;
    }
    return <String, Object?>{
      'path': path,
      if (!sampling.magLinear) 'magLinear': false,
      if (!sampling.minLinear) 'minLinear': false,
      if (!sampling.useMipmaps) 'mipmaps': false,
      if (!sampling.mipLinear) 'mipLinear': false,
      if (sampling.wrapS != TextureWrap.repeat) 'wrapS': sampling.wrapS.name,
      if (sampling.wrapT != TextureWrap.repeat) 'wrapT': sampling.wrapT.name,
    };
  }

  final textures = <String, Object?>{
    if (slot(surface.baseColorTexture) case final Object value) 'albedo': value,
    if (slot(surface.normalTexture) case final Object value) 'normal': value,
    if (slot(surface.metallicRoughnessTexture) case final Object value)
      'metallicRoughness': value,
    if (slot(surface.occlusionTexture) case final Object value)
      'occlusion': value,
    if (slot(surface.emissiveTexture) case final Object value)
      'emissive': value,
    for (final entry in document.extraTextures.entries)
      if (slot(entry.value) case final Object value) entry.key: value,
  };

  final lighting = document.lighting;
  final json = const JsonEncoder.withIndent('  ').convert(<String, Object?>{
    'fmat': kFmatVersion,
    if (lighting != null) 'lighting': _writeLighting(lighting),
    ...surfaceMaterialToJson(surface),
    if (textures.isNotEmpty) 'textures': textures,
    if (surface.extensions case final layers?)
      if (materialExtensionsToJson(layers, texture: slot) case final written
          when written.isNotEmpty)
        'extensions': written,
    if (document.parameterBlock != 'MaterialParams')
      'parameterBlock': document.parameterBlock,
    if (document.parameters.isNotEmpty)
      'parameters': <String, Object?>{
        for (final entry in document.parameters.entries)
          entry.key: <double>[for (final v in entry.value) _cleanFloat32(v)],
      },
    if (document.hints.isNotEmpty) 'hints': _writeHints(document.hints),
  });
  return '${_ensureFloatLiterals(json)}\n';
}