read static method

ModelData read(
  1. Uint8List bytes, {
  2. String? name,
})

The model in the cooked file bytes, named name for messages.

Implementation

static ModelData read(Uint8List bytes, {String? name}) {
  if (!isCooked(bytes)) {
    throw FormatException('${name ?? 'This file'} is not a cooked model.');
  }
  final view = ByteData.sublistView(bytes);
  final found = view.getUint32(4, Endian.little);
  if (found > version) {
    throw FormatException(
      '${name ?? 'This model'} was cooked by a newer version (format $found; this reads $version). Cook it again.',
    );
  }
  int padded(int n) => (n + 3) & ~3;
  final jsonLength = view.getUint32(8, Endian.little);
  final header =
      jsonDecode(
            utf8.decode(Uint8List.sublistView(bytes, 12, 12 + jsonLength)),
          )
          as Map<String, dynamic>;
  var at = 12 + padded(jsonLength);
  final count = view.getUint32(at, Endian.little);
  at += 4;
  final blobs = <Uint8List>[];
  for (var i = 0; i < count; i++) {
    final n = view.getUint32(at, Endian.little);
    blobs.add(Uint8List.sublistView(bytes, at + 4, at + 4 + n));
    at += 4 + padded(n);
  }

  // Typed views need their start aligned; a copy when it isn't.
  Uint8List aligned(int index) {
    final b = blobs[index];
    return b.offsetInBytes % 4 == 0 ? b : Uint8List.fromList(b);
  }

  Float32List floats(int index) {
    final b = aligned(index);
    return b.buffer.asFloat32List(b.offsetInBytes, b.lengthInBytes ~/ 4);
  }

  Uint32List uints(int index) {
    final b = aligned(index);
    return b.buffer.asUint32List(b.offsetInBytes, b.lengthInBytes ~/ 4);
  }

  List<double> nums(Object? v) => [
    for (final e in (v as List? ?? const [])) (e as num).toDouble(),
  ];
  Aabb3 box(Object? v) {
    final n = nums(v);
    return n.length == 6
        ? Aabb3.minMax(Vector3(n[0], n[1], n[2]), Vector3(n[3], n[4], n[5]))
        : Aabb3();
  }

  TextureRef? ref(Object? v) {
    if (v is! Map) return null;
    final tx = nums(v['tx']);
    return TextureRef(
      v['t'] as int,
      texCoord: v['uv'] as int? ?? 0,
      offset: tx.length == 5 ? (tx[0], tx[1]) : (0.0, 0.0),
      scale: tx.length == 5 ? (tx[2], tx[3]) : (1.0, 1.0),
      rotation: tx.length == 5 ? tx[4] : 0.0,
    );
  }

  List<int> ints(Object? v) => [
    for (final e in (v as List? ?? const [])) e as int,
  ];
  List<Map<String, dynamic>> maps(Object? v) =>
      (v as List? ?? const []).cast<Map<String, dynamic>>();

  return ModelData(
    name: name ?? header['name'] as String? ?? 'model',
    bounds: box(header['bounds']),
    roots: ints(header['roots']),
    warnings: [
      for (final w in (header['warnings'] as List? ?? const [])) w as String,
    ],
    meshes: [
      for (final mesh in maps(header['meshes']))
        MeshData(mesh['name'] as String?, [
          for (final p in maps(mesh['primitives']))
            PrimitiveData(
              vertices: floats(p['v'] as int),
              indices: uints(p['i'] as int),
              skin: p['s'] == null ? null : floats(p['s'] as int),
              material: p['m'] as int?,
              bounds: box(p['b']),
              hasTangents: p['tan'] as bool? ?? false,
            ),
        ]),
    ],
    materials: [
      for (final m in maps(header['materials']))
        () {
          final bc = nums(m['bc']), em = nums(m['em']);
          return MaterialData(
            name: m['name'] as String?,
            baseColor: (bc[0], bc[1], bc[2], bc[3]),
            baseColorTexture: ref(m['bct']),
            metallic: (m['mt'] as num).toDouble(),
            roughness: (m['rg'] as num).toDouble(),
            metallicRoughnessTexture: ref(m['mrt']),
            normalTexture: ref(m['nt']),
            normalScale: (m['ns'] as num).toDouble(),
            occlusionTexture: ref(m['ot']),
            occlusionStrength: (m['os'] as num).toDouble(),
            emissive: (em[0], em[1], em[2]),
            emissiveTexture: ref(m['et']),
            emissiveStrength: (m['es'] as num).toDouble(),
            alphaMode: AlphaMode.values.byName(m['am'] as String),
            alphaCutoff: (m['ac'] as num).toDouble(),
            doubleSided: m['ds'] as bool,
            unlit: m['ul'] as bool,
          );
        }(),
    ],
    textures: [
      for (final t in maps(header['textures']))
        () {
          final s = ints(t['sampler']);
          return TextureData(
            t['image'] as int?,
            SamplerData(
              magFilter: s[0],
              minFilter: s[1],
              wrapS: s[2],
              wrapT: s[3],
            ),
          );
        }(),
    ],
    images: [
      for (final image in maps(header['images']))
        image['levels'] is List
            ? ImageData(
                Uint8List(0),
                image['mime'] as String?,
                image['name'] as String?,
                levels: [
                  for (final l in image['levels'] as List)
                    RgbaImage(l[0] as int, l[1] as int, blobs[l[2] as int]),
                ],
                levelsSrgb: image['srgb'] as bool? ?? true,
              )
            : ImageData(
                blobs[image['bytes'] as int],
                image['mime'] as String?,
                image['name'] as String?,
              ),
    ],
    nodes: [
      for (final n in maps(header['nodes']))
        () {
          final t = nums(n['t']), r = nums(n['r']), s = nums(n['s']);
          return NodeData(
            name: n['name'] as String?,
            local: Matrix4.fromList(nums(n['local'])),
            children: ints(n['children']),
            mesh: n['mesh'] as int?,
            skin: n['skin'] as int?,
            light: n['light'] as int?,
            translation: t.length == 3 ? Vector3(t[0], t[1], t[2]) : null,
            rotation: r.length == 4
                ? Quaternion(r[0], r[1], r[2], r[3])
                : null,
            scale: s.length == 3 ? Vector3(s[0], s[1], s[2]) : null,
          );
        }(),
    ],
    lights: [
      for (final l in maps(header['lights']))
        () {
          final c = nums(l['color']);
          return LightData(
            type: LightType.values.byName(l['type'] as String),
            name: l['name'] as String?,
            color: (c[0], c[1], c[2]),
            intensity: (l['intensity'] as num).toDouble(),
            range: (l['range'] as num?)?.toDouble(),
            innerConeAngle: (l['inner'] as num).toDouble(),
            outerConeAngle: (l['outer'] as num).toDouble(),
          );
        }(),
    ],
    skins: [
      for (final s in maps(header['skins']))
        SkinData(
          name: s['name'] as String?,
          joints: ints(s['joints']),
          inverseBindMatrices: floats(s['ibm'] as int),
          skeleton: s['skeleton'] as int?,
        ),
    ],
    animations: [
      for (final a in maps(header['animations']))
        AnimationData(
          name: a['name'] as String,
          duration: (a['duration'] as num).toDouble(),
          channels: [
            for (final c in a['channels'] as List)
              AnimationChannelData(
                sampler: c[0] as int,
                node: c[1] as int,
                path: AnimationPath.values.byName(c[2] as String),
              ),
          ],
          samplers: [
            for (final s in maps(a['samplers']))
              AnimationSamplerData(
                input: floats(s['in'] as int),
                output: floats(s['out'] as int),
                interpolation: AnimationInterpolation.values.byName(
                  s['interp'] as String,
                ),
                width: s['w'] as int,
              ),
          ],
        ),
    ],
  );
}