write static method
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;
}