write static method

Uint8List write(
  1. ModelData data, {
  2. String? stamp,
})

data as a cooked file, carrying stamp (what it was cooked from; see stampOf). Images with ImageData.levels are stored as those levels; any other keeps its encoded bytes.

Implementation

static Uint8List write(ModelData data, {String? stamp}) {
  final blobs = <Uint8List>[];
  int blob(TypedData d) {
    blobs.add(d.buffer.asUint8List(d.offsetInBytes, d.lengthInBytes));
    return blobs.length - 1;
  }

  List<double> box(Aabb3 b) => b.isEmpty
      ? const []
      : [b.min.x, b.min.y, b.min.z, b.max.x, b.max.y, b.max.z];
  Map<String, dynamic>? ref(TextureRef? r) => r == null
      ? null
      : {
          't': r.texture,
          if (r.texCoord != 0) 'uv': r.texCoord,
          if (r.hasTransform)
            'tx': [
              r.offset.$1,
              r.offset.$2,
              r.scale.$1,
              r.scale.$2,
              r.rotation,
            ],
        };

  final header = <String, dynamic>{
    'name': data.name,
    'stamp': ?stamp,
    'bounds': box(data.bounds),
    'roots': data.roots,
    'warnings': data.warnings,
    'meshes': [
      for (final mesh in data.meshes)
        {
          'name': mesh.name,
          'primitives': [
            for (final p in mesh.primitives)
              {
                'v': blob(p.vertices),
                'i': blob(p.indices),
                if (p.skin != null) 's': blob(p.skin!),
                'm': p.material,
                'b': box(p.bounds),
                'tan': p.hasTangents,
              },
          ],
        },
    ],
    'materials': [
      for (final m in data.materials)
        {
          'name': m.name,
          'bc': [
            m.baseColor.$1,
            m.baseColor.$2,
            m.baseColor.$3,
            m.baseColor.$4,
          ],
          'bct': ref(m.baseColorTexture),
          'mt': m.metallic,
          'rg': m.roughness,
          'mrt': ref(m.metallicRoughnessTexture),
          'nt': ref(m.normalTexture),
          'ns': m.normalScale,
          'ot': ref(m.occlusionTexture),
          'os': m.occlusionStrength,
          'em': [m.emissive.$1, m.emissive.$2, m.emissive.$3],
          'et': ref(m.emissiveTexture),
          'es': m.emissiveStrength,
          'am': m.alphaMode.name,
          'ac': m.alphaCutoff,
          'ds': m.doubleSided,
          'ul': m.unlit,
        },
    ],
    'textures': [
      for (final t in data.textures)
        {
          'image': t.image,
          'sampler': [
            t.sampler.magFilter,
            t.sampler.minFilter,
            t.sampler.wrapS,
            t.sampler.wrapT,
          ],
        },
    ],
    'images': [
      for (final image in data.images)
        {
          'name': image.name,
          'mime': image.mimeType,
          if (image.levels case final levels?) ...{
            'levels': [
              for (final l in levels) [l.width, l.height, blob(l.pixels)],
            ],
            'srgb': image.levelsSrgb,
          } else
            'bytes': blob(image.bytes),
        },
    ],
    'nodes': [
      for (final n in data.nodes)
        {
          'name': n.name,
          'local': n.local.storage.toList(),
          'children': n.children,
          'mesh': n.mesh,
          'skin': n.skin,
          'light': n.light,
          if (n.translation case final t?) 't': [t.x, t.y, t.z],
          if (n.rotation case final r?) 'r': [r.x, r.y, r.z, r.w],
          if (n.scale case final s?) 's': [s.x, s.y, s.z],
        },
    ],
    'lights': [
      for (final l in data.lights)
        {
          'type': l.type.name,
          'name': l.name,
          'color': [l.color.$1, l.color.$2, l.color.$3],
          'intensity': l.intensity,
          'range': l.range,
          'inner': l.innerConeAngle,
          'outer': l.outerConeAngle,
        },
    ],
    'skins': [
      for (final s in data.skins)
        {
          'name': s.name,
          'joints': s.joints,
          'ibm': blob(s.inverseBindMatrices),
          'skeleton': s.skeleton,
        },
    ],
    'animations': [
      for (final a in data.animations)
        {
          'name': a.name,
          'duration': a.duration,
          'channels': [
            for (final c in a.channels) [c.sampler, c.node, c.path.name],
          ],
          'samplers': [
            for (final s in a.samplers)
              {
                'in': blob(s.input),
                'out': blob(s.output),
                'interp': s.interpolation.name,
                'w': s.width,
              },
          ],
        },
    ],
  };

  final json = utf8.encode(jsonEncode(header));
  int padded(int n) => (n + 3) & ~3;
  var length = 4 + 4 + 4 + padded(json.length) + 4;
  for (final b in blobs) {
    length += 4 + padded(b.length);
  }
  final out = Uint8List(length);
  final view = ByteData.sublistView(out);
  out.setAll(0, _magic);
  view.setUint32(4, version, Endian.little);
  view.setUint32(8, json.length, Endian.little);
  out.setAll(12, json);
  var at = 12 + padded(json.length);
  view.setUint32(at, blobs.length, Endian.little);
  at += 4;
  for (final b in blobs) {
    view.setUint32(at, b.length, Endian.little);
    out.setAll(at + 4, b);
    at += 4 + padded(b.length);
  }
  return out;
}