validateGltfExport function
Future<List<String> >
validateGltfExport(
- Uint8List bytes, {
- AssetUriResolver? resolveUri,
- double tolerance = 1e-5,
Reads bytes as glTF/GLB and checks every accessor's declared min/max
against the values it actually holds.
resolveUri is only needed for a .gltf file with external buffers; a
self-contained .glb needs none. tolerance absorbs the float roundtrip
through JSON text, not a real disagreement — the writer this checks
against computes bounds in the same double precision it prints them in,
so a real bug moves a component by far more than this.
Returns one problem string per accessor component found wrong, empty when
nothing is. Accessors with no declared min/max are not required to
have one and are silently skipped, as are integer-component accessors —
this writer only ever puts bounds on POSITION, which is always float.
Implementation
Future<List<String>> validateGltfExport(
Uint8List bytes, {
AssetUriResolver? resolveUri,
double tolerance = 1e-5,
}) async {
final container = GlbContainer.parse(bytes);
final accessorsJson = container.json['accessors'];
if (accessorsJson is! List) return const <String>[];
final buffers = await container.resolveBuffers(resolveUri: resolveUri);
final reader = GltfAccessorReader(json: container.json, buffers: buffers);
final problems = <String>[];
for (var i = 0; i < accessorsJson.length; i++) {
final accessor = accessorsJson[i];
if (accessor is! Map) continue;
final declaredMin = accessor['min'];
final declaredMax = accessor['max'];
if (declaredMin is! List && declaredMax is! List) continue;
if (!reader.hasBufferView(i)) continue;
if (reader.componentTypeOf(i) != GltfComponentType.float) continue;
final count = reader.countOf(i);
if (count == 0) continue;
final components = reader.typeOf(i).componentCount;
final values = reader.readAsFloats(i);
final actualMin = List<double>.filled(components, double.infinity);
final actualMax = List<double>.filled(components, double.negativeInfinity);
for (var e = 0; e < count; e++) {
for (var c = 0; c < components; c++) {
final v = values[e * components + c];
if (v < actualMin[c]) actualMin[c] = v;
if (v > actualMax[c]) actualMax[c] = v;
}
}
if (declaredMin is List) {
for (var c = 0; c < components && c < declaredMin.length; c++) {
final declared = (declaredMin[c] as num).toDouble();
if ((declared - actualMin[c]).abs() > tolerance) {
problems.add(
'accessors[$i].min[$c] says $declared but the data\'s own '
'minimum is ${actualMin[c]}.',
);
}
}
}
if (declaredMax is List) {
for (var c = 0; c < components && c < declaredMax.length; c++) {
final declared = (declaredMax[c] as num).toDouble();
if ((declared - actualMax[c]).abs() > tolerance) {
problems.add(
'accessors[$i].max[$c] says $declared but the data\'s own '
'maximum is ${actualMax[c]}.',
);
}
}
}
}
return problems;
}