From e868ae187901cda4c5eb832196ad5061de77c8a7 Mon Sep 17 00:00:00 2001 From: Szymon Nowakowski Date: Tue, 28 Nov 2023 21:19:22 +0100 Subject: [PATCH] glTF decoder --- src/gltf.ts | 766 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 713 insertions(+), 53 deletions(-) diff --git a/src/gltf.ts b/src/gltf.ts index 1bc9c58..1fd2d9d 100644 --- a/src/gltf.ts +++ b/src/gltf.ts @@ -5,6 +5,8 @@ */ import * as data from "./data"; +import { Renderer } from "./oktaeder"; +import * as resources from "./resources"; /* INITIAL SUPPORT PLAN * @@ -15,7 +17,7 @@ import * as data from "./data"; * - accessors: used indirectly * - read when converting mesh * - sparse: no support - * - issues error + * - issues error * - animations: no support * - issues warning * - no animations emitted @@ -25,65 +27,65 @@ import * as data from "./data"; * - buffers: used indirectly * - read when converting mesh * - uri: no support - * - issues error + * - issues error * - bufferViews: used indirectly * - read when converting mesh * - cameras: * - orthographic: - * - xmag: ignored - * - ymag: converted to halfVerticalSize + * - xmag: ignored + * - ymag: converted to halfVerticalSize * - perspective: - * - aspectRatio: ignored - * - issues warning when provided + * - aspectRatio: ignored + * - issues warning when provided * - images: * - uri: no support - * - issues error + * - issues error * - materials: * - name: full support * - pbrMetallicRoughness: - * - baseColorFactor: full support - * - baseColorTexture: partial support - * - forced texCoord 0 - * - issues error when different provided - * - metallicFactor: full support - * - roughnessFactor: full support - * - metallicRoughnessTexture: partial support - * - forced texCoord 0 - * - issues error when different provided + * - baseColorFactor: full support + * - baseColorTexture: partial support + * - forced texCoord 0 + * - issues error when different provided + * - metallicFactor: full support + * - roughnessFactor: full support + * - metallicRoughnessTexture: partial support + * - forced texCoord 0 + * - issues error when different provided * - normalTexture: partial support - * - scale: full support - * - forced texCoord 0 - * - issues error when different provided + * - scale: full support + * - forced texCoord 0 + * - issues error when different provided * - occlusionTexture: partial support - * - strength: full support - * - forced texCoord 1 - * - issues error when different provided + * - strength: full support + * - forced texCoord 1 + * - issues error when different provided * - emissiveTexture: partial support - * - forced texCoord 0 - * - issues error when different provided + * - forced texCoord 0 + * - issues error when different provided * - emissiveFactor: full support * - alphaMode: - * - OPAQUE: full support - * - MASK: no support - * - issues error - * - BLEND: partial support - * - decoded, but not implemented + * - OPAQUE: full support + * - MASK: no support + * - issues error + * - BLEND: partial support + * - decoded, but not implemented * - doubleSided: prtial support - * - decoded, but not implemented + * - decoded, but not implemented * * Extensions: * - KHR_lights_punctual * - name: full support * - color/intensity: full support - * - converted to color = color * intensity + * - converted to color = color * intensity * - type: - * - directional: full support - * - point: full support - * - spot: no support - * - issues error + * - directional: full support + * - point: full support + * - spot: no support + * - issues error * - range: no support - * - issues warning - * - always infite range + * - issues warning + * - always infite range * - KHR_materials_emissive_strength: full support * - converted to emissive = emissive * strength * - KHR_materials_ior: full support @@ -94,7 +96,7 @@ import * as data from "./data"; export interface ParseResult { readonly cameras: readonly data.Camera[]; - readonly materials: readonly data.Material[]; + readonly materials: readonly resources.Material[]; readonly lights: readonly data.Light[]; readonly scenes: readonly data.Scene[]; readonly scene: data.Scene | null; @@ -105,7 +107,7 @@ export interface ParseResult { export interface ParseErrorProps { message: string; - position?: JsonPosition | undefined; + jsonPath?: string | undefined; severity: ParseErrorSeverity; options?: ErrorOptions | undefined; } @@ -118,30 +120,25 @@ export type ParseErrorSeverity = export class ParseError extends Error { override message: string; - position: JsonPosition | undefined; + jsonPath: string | undefined; severity: ParseErrorSeverity; constructor({ message, - position, + jsonPath, severity, options, }: ParseErrorProps) { super(message, options); this.message = message; - this.position = position; + this.jsonPath = jsonPath; this.severity = severity; } } -export interface JsonPosition { - readonly line: number; - readonly column: number; - readonly path: number; -} - export interface ParseOptions { + readonly renderer: Renderer; /** * When `true`, the parser will throw with a `ParseError` on the first error * encountered. This includes warnings when `treatWarningsAsErrors` is @@ -175,13 +172,14 @@ export interface ParseOptions { } export async function parse(gltf: ArrayBufferView, { + renderer, throwOnError = true, stopOnFirstError = true, treatWarningsAsErrors = false, -}: ParseOptions = {}): Promise { +}: ParseOptions): Promise { const cameras: data.Camera[] = []; - const materials: data.Material[] = []; + const materials: data.DynamicMaterial[] = []; const lights: data.Light[] = []; const scenes: data.Scene[] = []; const scene: data.Scene | null = null; @@ -325,13 +323,17 @@ export type AccessorType = | "MAT4" ; +export interface Version { + major: number; + minor: number; +} + export interface Asset { - version: `${string}.${string}`; - minVersion: `${string}.${string}`; + version: Version; + minVersion?: Version; } export interface Buffer { - uri?: string; byteLength: number; } @@ -508,3 +510,661 @@ export interface Texture { source?: number; name?: string; } + +function isInUint32Range(value: number): boolean { + return value >= 0 && value < 4294967296; +} + +function parseAccessor(path: string, value: unknown): Accessor | ParseError { + if (typeof value !== "object" || value === null) + throw new Error("Expected v to be an object"); + if (value.componentType !== 5120 && value.componentType !== 5121 && value.componentType !== 5122 && value.componentType !== 5123 && value.componentType !== 5125 && value.componentType !== 5126) + throw new Error("Expected v.componentType to be one of ComponentType.Byte, ComponentType.UnsignedByte, ComponentType.Short, ComponentType.UnsignedShort, ComponentType.UnsignedInt, ComponentType.Float"); + if (value.type !== "SCALAR" && value.type !== "VEC2" && value.type !== "VEC3" && value.type !== "VEC4" && value.type !== "MAT2" && value.type !== "MAT3" && value.type !== "MAT4") + throw new Error("Expected v.type to be one of \"SCALAR\", \"VEC2\", \"VEC3\", \"VEC4\", \"MAT2\", \"MAT3\", \"MAT4\""); + if (typeof value.count !== "number") + throw new Error("Expected v.count to be a number"); + if (value.bufferView !== undefined && typeof value.bufferView !== "number") + throw new Error("Expected v.bufferView to be a number"); + if (value.byteOffset !== undefined && typeof value.byteOffset !== "number") + throw new Error("Expected v.byteOffset to be a number"); + if (value.normalized !== undefined && typeof value.normalized !== "boolean") + throw new Error("Expected v.normalized to be a boolean"); +} + +function parseComponentType(jsonPath: string, value: unknown): ComponentType | ParseError { + if (value !== 5120 && value !== 5121 && value !== 5122 && value !== 5123 && value !== 5125 && value !== 5126) { + const message = `Invalid component type. Only values of 5120, 5121, 5122, 5123, 5125 and 5126 are valid component types. Property at ${jsonPath} is supposed to be a component type, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + return value; +} + +function parseAccessorType(jsonPath: string, value: unknown): AccessorType | ParseError { + if (value !== "SCALAR" && value !== "VEC2" && value !== "VEC3" && value !== "VEC4" && value !== "MAT2" && value !== "MAT3" && value !== "MAT4") { + const message = `Invalid accessor type. Only values of "SCALAR", "VEC2", "VEC3", "VEC4", "MAT2", "MAT3", "MAT4" are valid component types. Property at ${jsonPath} is supposed to be an accessor type, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + return value; +} + +function parseAsset(jsonPath: string, value: unknown): Asset | ParseError { + if (typeof value !== "object" || value === null) { + const message = `Invalid asset. Property at ${jsonPath} is supposed to be an object, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + const object = value as Record; + + if (typeof object["version"] !== "string") { + const path = jsonPath + ".version"; + const message = `Invalid asset. Property at ${path} is supposed to be a string, but has value of ${JSON.stringify(object["version"])}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + if ("minVersion" in object && typeof object["minVersion"] !== "string") { + const path = jsonPath + ".minVersion"; + const message = `Invalid asset. Optional property at ${path} is supposed to be a string, but has value of ${JSON.stringify(object["minVersion"])}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + const version = parseVersion(jsonPath + ".version", object["version"]); + if (version instanceof ParseError) return version; + + const minVersion = "minVersion" in object + ? parseVersion(jsonPath + ".minVersion", object["minVersion"] as string /* type checked above */) + : undefined; + if (minVersion instanceof ParseError) return minVersion; + + return { + version, + ...(minVersion && { minVersion }), + }; +} + +const VERSION_REGEX = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/; + +function parseVersion(jsonPath: string, value: string): Version | ParseError { + if (typeof value !== "string") { + const message = `Invalid version. Property at ${jsonPath} is supposed to be a string, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + const match = VERSION_REGEX.exec(value); + if (match === null) { + const message = `Invalid version. Property at ${jsonPath} is supposed to be in the form of ., but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + const major = Number(match[1]); + if (!isInUint32Range(major)) { + const message = `Invalid version. Property at ${jsonPath} is supposed to be in the form of ., but has value of ${JSON.stringify(value)}. There ain't no way glTF has ${major} major versions.` + return new ParseError({ message, jsonPath, severity: "error" }); + } + + const minor = Number(match[2]); + if (!isInUint32Range(major)) { + const message = `Invalid version. Property at ${jsonPath} is supposed to be in the form of ., but has value of ${JSON.stringify(value)}. There ain't no way glTF has ${minor} minor versions.` + return new ParseError({ message, jsonPath, severity: "error" }); + } + + return { major, minor }; +} + +function parseBuffer(jsonPath: string, value: unknown): Buffer | ParseError { + if (typeof value !== "object" || value === null) { + const message = `Invalid buffer. Property at ${jsonPath} is supposed to be an object, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + if ("uri" in value) { + jsonPath += ".uri"; + const message = `Unsupported feature. Property at ${jsonPath} exists, which means that this buffer is supposed to be loaded via a URI. Loading external resources via URIs is not currently supported.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + if (!("byteLength" in value)) { + jsonPath += ".byteLength"; + const message = `Invalid buffer. Property at ${jsonPath} is undefined, but is required.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + if (typeof value.byteLength !== "number") { + jsonPath += ".byteLength"; + const message = `Invalid buffer. Property at ${jsonPath} is supposed to be a number, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + if (!Number.isInteger(value.byteLength)) { + jsonPath += ".byteLength"; + const message = `Invalid buffer. Property at ${jsonPath} is supposed to be an integer, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + if (!isInUint32Range(value.byteLength)) { + jsonPath += ".byteLength"; + const message = `Invalid buffer. Property at ${jsonPath} is supposed to be an integer, but has value of ${JSON.stringify(value)}, which is an integer, but there ain't no way you have a buffer with ${value.byteLength} bytes.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + return value as Buffer; +} + +function parseBufferView(jsonPath: string, value: unknown): BufferView | ParseError { + if (typeof value !== "object" || value === null) + throw new Error("Expected v to be an object"); + if (typeof value.buffer !== "number") + throw new Error("Expected v.buffer to be a number"); + if (typeof value.byteLength !== "number") + throw new Error("Expected v.byteLength to be a number"); + if (value.byteOffset !== undefined && typeof value.byteOffset !== "number") + throw new Error("Expected v.byteOffset to be a number"); + if (value.byteStride !== undefined && typeof value.byteStride !== "number") + throw new Error("Expected v.byteStride to be a number"); +} + +function parseCamera(jsonPath: string, value: unknown): Camera | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be one of CameraOrthographic, CameraPerspective"); + else { + if (v.type !== "orthographic") + if (v.type !== "perspective") + throw new Error("Expected v to be one of CameraOrthographic, CameraPerspective"); + else { + const { + orthographic: orthographic_5 + } = v; + if (typeof orthographic_5 !== "object" || orthographic_5 === null) + throw new Error("Expected v.orthographic to be an object"); + if (typeof orthographic_5.yfov !== "number") + throw new Error("Expected v.orthographic.yfov to be a number"); + if (typeof orthographic_5.znear !== "number") + throw new Error("Expected v.orthographic.znear to be a number"); + if (orthographic_5.aspectRatio !== undefined && typeof orthographic_5.aspectRatio !== "number") + throw new Error("Expected v.orthographic.aspectRatio to be a number"); + if (orthographic_5.zfar !== undefined && typeof orthographic_5.zfar !== "number") + throw new Error("Expected v.orthographic.zfar to be a number"); + } + else { + const { + orthographic: orthographic_6 + } = v; + if (typeof orthographic_6 !== "object" || orthographic_6 === null) + throw new Error("Expected v.orthographic to be an object"); + if (typeof orthographic_6.xmag !== "number") + throw new Error("Expected v.orthographic.xmag to be a number"); + if (typeof orthographic_6.ymag !== "number") + throw new Error("Expected v.orthographic.ymag to be a number"); + if (typeof orthographic_6.zfar !== "number") + throw new Error("Expected v.orthographic.zfar to be a number"); + if (typeof orthographic_6.znear !== "number") + throw new Error("Expected v.orthographic.znear to be a number"); + } + } +} + +function parseCameraOrthographic(jsonPath: string, value: unknown): CameraOrthographic | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + const { + orthographic: orthographic_7 + } = v; + if (v.type !== "orthographic") + throw new Error("Expected v.type to be equal to \"orthographic\""); + if (typeof orthographic_7 !== "object" || orthographic_7 === null) + throw new Error("Expected v.orthographic to be an object"); + if (typeof orthographic_7.xmag !== "number") + throw new Error("Expected v.orthographic.xmag to be a number"); + if (typeof orthographic_7.ymag !== "number") + throw new Error("Expected v.orthographic.ymag to be a number"); + if (typeof orthographic_7.zfar !== "number") + throw new Error("Expected v.orthographic.zfar to be a number"); + if (typeof orthographic_7.znear !== "number") + throw new Error("Expected v.orthographic.znear to be a number"); +} + +function parseCameraPerspective(jsonPath: string, value: unknown): CameraPerspective | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + const { + orthographic: orthographic_8 + } = v; + if (v.type !== "perspective") + throw new Error("Expected v.type to be equal to \"perspective\""); + if (typeof orthographic_8 !== "object" || orthographic_8 === null) + throw new Error("Expected v.orthographic to be an object"); + if (typeof orthographic_8.yfov !== "number") + throw new Error("Expected v.orthographic.yfov to be a number"); + if (typeof orthographic_8.znear !== "number") + throw new Error("Expected v.orthographic.znear to be a number"); + if (orthographic_8.aspectRatio !== undefined && typeof orthographic_8.aspectRatio !== "number") + throw new Error("Expected v.orthographic.aspectRatio to be a number"); + if (orthographic_8.zfar !== undefined && typeof orthographic_8.zfar !== "number") + throw new Error("Expected v.orthographic.zfar to be a number"); +} + +function parseOrthographic(jsonPath: string, value: unknown): Orthographic | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (typeof v.xmag !== "number") + throw new Error("Expected v.xmag to be a number"); + if (typeof v.ymag !== "number") + throw new Error("Expected v.ymag to be a number"); + if (typeof v.zfar !== "number") + throw new Error("Expected v.zfar to be a number"); + if (typeof v.znear !== "number") + throw new Error("Expected v.znear to be a number"); +} + +function parsePerspective(jsonPath: string, value: unknown): Perspective | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (typeof v.yfov !== "number") + throw new Error("Expected v.yfov to be a number"); + if (typeof v.znear !== "number") + throw new Error("Expected v.znear to be a number"); + if (v.aspectRatio !== undefined && typeof v.aspectRatio !== "number") + throw new Error("Expected v.aspectRatio to be a number"); + if (v.zfar !== undefined && typeof v.zfar !== "number") + throw new Error("Expected v.zfar to be a number"); +} + +function parseImage(jsonPath: string, value: unknown): Image | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (v.mimeType !== undefined && v.mimeType !== "image/jpeg" && v.mimeType !== "image/png") + throw new Error("Expected v.mimeType to be one of \"image/jpeg\", \"image/png\", undefined"); + if (v.uri !== undefined && typeof v.uri !== "string") + throw new Error("Expected v.uri to be a string"); + if (v.bufferView !== undefined && typeof v.bufferView !== "number") + throw new Error("Expected v.bufferView to be a number"); + if (v.name !== undefined && typeof v.name !== "string") + throw new Error("Expected v.name to be a string"); +} + +const VALID_IMAGE_MIME_TYPES: readonly unknown[] = Object.freeze(["image/jpeg", "image/png"] satisfies ImageMimeType[]); + +function parseImageMimeType(jsonPath: string, value: unknown): ImageMimeType | ParseError { + if (VALID_IMAGE_MIME_TYPES.includes(value)) { + return value as ImageMimeType; + } + + const message = `Invalid image MIME type. Only MIME types "image/jpeg" and "image/png" are supported, but property at ${jsonPath} has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); +} + +function parseMaterial(jsonPath: string, value: unknown): Material | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + const { + emissiveFactor: emissiveFactor_3, + emissiveTexture: emissiveTexture_3, + normalTexture: normalTexture_3, + occlusionTexture: occlusionTexture_3, + extensions: extensions_3, + pbrMetallicRoughness: pbrMetallicRoughness_3 + } = v; + if (v.alphaMode !== undefined && v.alphaMode !== "OPAQUE" && v.alphaMode !== "MASK" && v.alphaMode !== "BLEND") + throw new Error("Expected v.alphaMode to be one of \"OPAQUE\", \"MASK\", \"BLEND\", undefined"); + if (v.name !== undefined && typeof v.name !== "string") + throw new Error("Expected v.name to be a string"); + if (v.doubleSided !== undefined && typeof v.doubleSided !== "boolean") + throw new Error("Expected v.doubleSided to be a boolean"); + if (emissiveFactor_3 !== undefined) { + if (!Array.isArray(emissiveFactor_3)) + throw new Error("Expected v.emissiveFactor to be an array"); + if (typeof emissiveFactor_3[0] !== "number") + throw new Error("Expected v.emissiveFactor[0] to be a number"); + if (typeof emissiveFactor_3[1] !== "number") + throw new Error("Expected v.emissiveFactor[1] to be a number"); + if (typeof emissiveFactor_3[2] !== "number") + throw new Error("Expected v.emissiveFactor[2] to be a number");; + } + if (emissiveTexture_3 !== undefined) { + if (typeof emissiveTexture_3 !== "object" || emissiveTexture_3 === null) + throw new Error("Expected v.emissiveTexture to be an object"); + if (typeof emissiveTexture_3.index !== "number") + throw new Error("Expected v.emissiveTexture.index to be a number"); + if (emissiveTexture_3.texCoord !== undefined && typeof emissiveTexture_3.texCoord !== "number") + throw new Error("Expected v.emissiveTexture.texCoord to be a number");; + } + if (normalTexture_3 !== undefined) { + if (typeof normalTexture_3 !== "object" || normalTexture_3 === null) + throw new Error("Expected v.normalTexture to be an object"); + if (typeof normalTexture_3.index !== "number") + throw new Error("Expected v.normalTexture.index to be a number"); + if (normalTexture_3.scale !== undefined && typeof normalTexture_3.scale !== "number") + throw new Error("Expected v.normalTexture.scale to be a number"); + if (normalTexture_3.texCoord !== undefined && typeof normalTexture_3.texCoord !== "number") + throw new Error("Expected v.normalTexture.texCoord to be a number");; + } + if (occlusionTexture_3 !== undefined) { + if (typeof occlusionTexture_3 !== "object" || occlusionTexture_3 === null) + throw new Error("Expected v.occlusionTexture to be an object"); + if (typeof occlusionTexture_3.index !== "number") + throw new Error("Expected v.occlusionTexture.index to be a number"); + if (occlusionTexture_3.strength !== undefined && typeof occlusionTexture_3.strength !== "number") + throw new Error("Expected v.occlusionTexture.strength to be a number"); + if (occlusionTexture_3.texCoord !== undefined && typeof occlusionTexture_3.texCoord !== "number") + throw new Error("Expected v.occlusionTexture.texCoord to be a number");; + } + if (extensions_3 !== undefined) { + if (typeof extensions_3 !== "object" || extensions_3 === null) + throw new Error("Expected v.extensions to be an object"); + const { + KHR_materials_emissive_strength: KHR_materials_emissive_strength_3, + KHR_materials_ior: KHR_materials_ior_3 + } = extensions_3; + if (KHR_materials_emissive_strength_3 !== undefined) { + if (typeof KHR_materials_emissive_strength_3 !== "object" || KHR_materials_emissive_strength_3 === null) + throw new Error("Expected v.extensions.KHR_materials_emissive_strength to be an object"); + if (KHR_materials_emissive_strength_3.emissiveStrength !== undefined && typeof KHR_materials_emissive_strength_3.emissiveStrength !== "number") + throw new Error("Expected v.extensions.KHR_materials_emissive_strength.emissiveStrength to be a number");; + } + if (KHR_materials_ior_3 !== undefined) { + if (typeof KHR_materials_ior_3 !== "object" || KHR_materials_ior_3 === null) + throw new Error("Expected v.extensions.KHR_materials_ior to be an object"); + if (KHR_materials_ior_3.ior !== undefined && typeof KHR_materials_ior_3.ior !== "number") + throw new Error("Expected v.extensions.KHR_materials_ior.ior to be a number");; + }; + } + if (pbrMetallicRoughness_3 !== undefined) { + if (typeof pbrMetallicRoughness_3 !== "object" || pbrMetallicRoughness_3 === null) + throw new Error("Expected v.pbrMetallicRoughness to be an object"); + const { + baseColorFactor: baseColorFactor_3, + baseColorTexture: baseColorTexture_3, + metallicRoughnessTexture: metallicRoughnessTexture_3 + } = pbrMetallicRoughness_3; + if (pbrMetallicRoughness_3.metallicFactor !== undefined && typeof pbrMetallicRoughness_3.metallicFactor !== "number") + throw new Error("Expected v.pbrMetallicRoughness.metallicFactor to be a number"); + if (pbrMetallicRoughness_3.roughnessFactor !== undefined && typeof pbrMetallicRoughness_3.roughnessFactor !== "number") + throw new Error("Expected v.pbrMetallicRoughness.roughnessFactor to be a number"); + if (baseColorFactor_3 !== undefined) { + if (!Array.isArray(baseColorFactor_3)) + throw new Error("Expected v.pbrMetallicRoughness.baseColorFactor to be an array"); + if (typeof baseColorFactor_3[0] !== "number") + throw new Error("Expected v.pbrMetallicRoughness.baseColorFactor[0] to be a number"); + if (typeof baseColorFactor_3[1] !== "number") + throw new Error("Expected v.pbrMetallicRoughness.baseColorFactor[1] to be a number"); + if (typeof baseColorFactor_3[2] !== "number") + throw new Error("Expected v.pbrMetallicRoughness.baseColorFactor[2] to be a number"); + if (typeof baseColorFactor_3[3] !== "number") + throw new Error("Expected v.pbrMetallicRoughness.baseColorFactor[3] to be a number");; + } + if (baseColorTexture_3 !== undefined) { + if (typeof baseColorTexture_3 !== "object" || baseColorTexture_3 === null) + throw new Error("Expected v.pbrMetallicRoughness.baseColorTexture to be an object"); + if (typeof baseColorTexture_3.index !== "number") + throw new Error("Expected v.pbrMetallicRoughness.baseColorTexture.index to be a number"); + if (baseColorTexture_3.texCoord !== undefined && typeof baseColorTexture_3.texCoord !== "number") + throw new Error("Expected v.pbrMetallicRoughness.baseColorTexture.texCoord to be a number");; + } + if (metallicRoughnessTexture_3 !== undefined) { + if (typeof metallicRoughnessTexture_3 !== "object" || metallicRoughnessTexture_3 === null) + throw new Error("Expected v.pbrMetallicRoughness.metallicRoughnessTexture to be an object"); + if (typeof metallicRoughnessTexture_3.index !== "number") + throw new Error("Expected v.pbrMetallicRoughness.metallicRoughnessTexture.index to be a number"); + if (metallicRoughnessTexture_3.texCoord !== undefined && typeof metallicRoughnessTexture_3.texCoord !== "number") + throw new Error("Expected v.pbrMetallicRoughness.metallicRoughnessTexture.texCoord to be a number");; + }; + } +} + +function parseMaterialExtensions(jsonPath: string, value: unknown): MaterialExtensions | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + const { + KHR_materials_emissive_strength: KHR_materials_emissive_strength_4, + KHR_materials_ior: KHR_materials_ior_4 + } = v; + if (KHR_materials_emissive_strength_4 !== undefined) { + if (typeof KHR_materials_emissive_strength_4 !== "object" || KHR_materials_emissive_strength_4 === null) + throw new Error("Expected v.KHR_materials_emissive_strength to be an object"); + if (KHR_materials_emissive_strength_4.emissiveStrength !== undefined && typeof KHR_materials_emissive_strength_4.emissiveStrength !== "number") + throw new Error("Expected v.KHR_materials_emissive_strength.emissiveStrength to be a number");; + } + if (KHR_materials_ior_4 !== undefined) { + if (typeof KHR_materials_ior_4 !== "object" || KHR_materials_ior_4 === null) + throw new Error("Expected v.KHR_materials_ior to be an object"); + if (KHR_materials_ior_4.ior !== undefined && typeof KHR_materials_ior_4.ior !== "number") + throw new Error("Expected v.KHR_materials_ior.ior to be a number");; + } +} + +function parseKHR_materials_emissive_strength(jsonPath: string, value: unknown): KHR_materials_emissive_strength | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (v.emissiveStrength !== undefined && typeof v.emissiveStrength !== "number") + throw new Error("Expected v.emissiveStrength to be a number"); +} + +function parseKHR_materials_ior(jsonPath: string, value: unknown): KHR_materials_ior | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (v.ior !== undefined && typeof v.ior !== "number") + throw new Error("Expected v.ior to be a number"); +} + +function parseMaterialPbrMetallicRoughness(jsonPath: string, value: unknown): MaterialPbrMetallicRoughness | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + const { + baseColorFactor: baseColorFactor_4, + baseColorTexture: baseColorTexture_4, + metallicRoughnessTexture: metallicRoughnessTexture_4 + } = v; + if (v.metallicFactor !== undefined && typeof v.metallicFactor !== "number") + throw new Error("Expected v.metallicFactor to be a number"); + if (v.roughnessFactor !== undefined && typeof v.roughnessFactor !== "number") + throw new Error("Expected v.roughnessFactor to be a number"); + if (baseColorFactor_4 !== undefined) { + if (!Array.isArray(baseColorFactor_4)) + throw new Error("Expected v.baseColorFactor to be an array"); + if (typeof baseColorFactor_4[0] !== "number") + throw new Error("Expected v.baseColorFactor[0] to be a number"); + if (typeof baseColorFactor_4[1] !== "number") + throw new Error("Expected v.baseColorFactor[1] to be a number"); + if (typeof baseColorFactor_4[2] !== "number") + throw new Error("Expected v.baseColorFactor[2] to be a number"); + if (typeof baseColorFactor_4[3] !== "number") + throw new Error("Expected v.baseColorFactor[3] to be a number");; + } + if (baseColorTexture_4 !== undefined) { + if (typeof baseColorTexture_4 !== "object" || baseColorTexture_4 === null) + throw new Error("Expected v.baseColorTexture to be an object"); + if (typeof baseColorTexture_4.index !== "number") + throw new Error("Expected v.baseColorTexture.index to be a number"); + if (baseColorTexture_4.texCoord !== undefined && typeof baseColorTexture_4.texCoord !== "number") + throw new Error("Expected v.baseColorTexture.texCoord to be a number");; + } + if (metallicRoughnessTexture_4 !== undefined) { + if (typeof metallicRoughnessTexture_4 !== "object" || metallicRoughnessTexture_4 === null) + throw new Error("Expected v.metallicRoughnessTexture to be an object"); + if (typeof metallicRoughnessTexture_4.index !== "number") + throw new Error("Expected v.metallicRoughnessTexture.index to be a number"); + if (metallicRoughnessTexture_4.texCoord !== undefined && typeof metallicRoughnessTexture_4.texCoord !== "number") + throw new Error("Expected v.metallicRoughnessTexture.texCoord to be a number");; + } +} + +function parseTextureInfo(jsonPath: string, value: unknown): TextureInfo | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (typeof v.index !== "number") + throw new Error("Expected v.index to be a number"); + if (v.texCoord !== undefined && typeof v.texCoord !== "number") + throw new Error("Expected v.texCoord to be a number"); +} + +function parseNormalTextureInfo(jsonPath: string, value: unknown): NormalTextureInfo | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (typeof v.index !== "number") + throw new Error("Expected v.index to be a number"); + if (v.scale !== undefined && typeof v.scale !== "number") + throw new Error("Expected v.scale to be a number"); + if (v.texCoord !== undefined && typeof v.texCoord !== "number") + throw new Error("Expected v.texCoord to be a number"); +} + +function parseOcclusionTextureInfo(jsonPath: string, value: unknown): OcclusionTextureInfo | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (typeof v.index !== "number") + throw new Error("Expected v.index to be a number"); + if (v.strength !== undefined && typeof v.strength !== "number") + throw new Error("Expected v.strength to be a number"); + if (v.texCoord !== undefined && typeof v.texCoord !== "number") + throw new Error("Expected v.texCoord to be a number"); +} + +function parseAlphaMode(jsonPath: string, value: unknown): AlphaMode | ParseError { + if (v !== "OPAQUE" && v !== "MASK" && v !== "BLEND") + throw new Error("Expected v to be one of \"OPAQUE\", \"MASK\", \"BLEND\""); +} + +function parseMesh(jsonPath: string, value: unknown): Mesh | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + const { + primitives: primitives_3 + } = v; + if (v.name !== undefined && typeof v.name !== "string") + throw new Error("Expected v.name to be a string"); + if (!Array.isArray(primitives_3)) + throw new Error("Expected v.primitives to be an array"); + const [t_23, t_24] = primitives_3; + if (typeof t_23 !== "object" || t_23 === null) + throw new Error("Expected v.primitives[0] to be an object"); + const { + attributes: attributes_5 + } = t_23; + if (t_23.indices !== undefined && typeof t_23.indices !== "number") + throw new Error("Expected v.primitives[0].indices to be a number"); + if (t_23.material !== undefined && typeof t_23.material !== "number") + throw new Error("Expected v.primitives[0].material to be a number"); + if (t_23.mode !== undefined && t_23.mode !== 1 && t_23.mode !== 2 && t_23.mode !== 3 && t_23.mode !== 4 && t_23.mode !== 5 && t_23.mode !== 6 && t_23.mode !== 0) + throw new Error("Expected v.primitives[0].mode to be one of PrimitiveMode.Lines, PrimitiveMode.LineLoop, PrimitiveMode.LineStrip, PrimitiveMode.Triangles, PrimitiveMode.TriangleStrip, PrimitiveMode.TriangleFan, PrimitiveMode.Points, undefined"); + if (typeof attributes_5 !== "object" || attributes_5 === null) + throw new Error("Expected v.primitives[0].attributes to be an object"); + if (attributes_5.POSITION !== undefined && typeof attributes_5.POSITION !== "number") + throw new Error("Expected v.primitives[0].attributes.POSITION to be a number"); + if (attributes_5.NORMAL !== undefined && typeof attributes_5.NORMAL !== "number") + throw new Error("Expected v.primitives[0].attributes.NORMAL to be a number"); + if (attributes_5.TANGENT !== undefined && typeof attributes_5.TANGENT !== "number") + throw new Error("Expected v.primitives[0].attributes.TANGENT to be a number"); + if (attributes_5.TEXCOORD_0 !== undefined && typeof attributes_5.TEXCOORD_0 !== "number") + throw new Error("Expected v.primitives[0].attributes.TEXCOORD_0 to be a number"); + if (attributes_5.TEXCOORD_1 !== undefined && typeof attributes_5.TEXCOORD_1 !== "number") + throw new Error("Expected v.primitives[0].attributes.TEXCOORD_1 to be a number"); + if (typeof t_24 !== "object" || t_24 === null) + throw new Error("Expected v.primitives[1] to be an object"); + const { + attributes: attributes_6 + } = t_24; + if (t_24.indices !== undefined && typeof t_24.indices !== "number") + throw new Error("Expected v.primitives[1].indices to be a number"); + if (t_24.material !== undefined && typeof t_24.material !== "number") + throw new Error("Expected v.primitives[1].material to be a number"); + if (t_24.mode !== undefined && t_24.mode !== 1 && t_24.mode !== 2 && t_24.mode !== 3 && t_24.mode !== 4 && t_24.mode !== 5 && t_24.mode !== 6 && t_24.mode !== 0) + throw new Error("Expected v.primitives[1].mode to be one of PrimitiveMode.Lines, PrimitiveMode.LineLoop, PrimitiveMode.LineStrip, PrimitiveMode.Triangles, PrimitiveMode.TriangleStrip, PrimitiveMode.TriangleFan, PrimitiveMode.Points, undefined"); + if (typeof attributes_6 !== "object" || attributes_6 === null) + throw new Error("Expected v.primitives[1].attributes to be an object"); + if (attributes_6.POSITION !== undefined && typeof attributes_6.POSITION !== "number") + throw new Error("Expected v.primitives[1].attributes.POSITION to be a number"); + if (attributes_6.NORMAL !== undefined && typeof attributes_6.NORMAL !== "number") + throw new Error("Expected v.primitives[1].attributes.NORMAL to be a number"); + if (attributes_6.TANGENT !== undefined && typeof attributes_6.TANGENT !== "number") + throw new Error("Expected v.primitives[1].attributes.TANGENT to be a number"); + if (attributes_6.TEXCOORD_0 !== undefined && typeof attributes_6.TEXCOORD_0 !== "number") + throw new Error("Expected v.primitives[1].attributes.TEXCOORD_0 to be a number"); + if (attributes_6.TEXCOORD_1 !== undefined && typeof attributes_6.TEXCOORD_1 !== "number") + throw new Error("Expected v.primitives[1].attributes.TEXCOORD_1 to be a number"); +} + +function parsePrimitive(jsonPath: string, value: unknown): Primitive | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + const { + attributes: attributes_7 + } = v; + if (v.indices !== undefined && typeof v.indices !== "number") + throw new Error("Expected v.indices to be a number"); + if (v.material !== undefined && typeof v.material !== "number") + throw new Error("Expected v.material to be a number"); + if (v.mode !== undefined && v.mode !== 1 && v.mode !== 2 && v.mode !== 3 && v.mode !== 4 && v.mode !== 5 && v.mode !== 6 && v.mode !== 0) + throw new Error("Expected v.mode to be one of PrimitiveMode.Lines, PrimitiveMode.LineLoop, PrimitiveMode.LineStrip, PrimitiveMode.Triangles, PrimitiveMode.TriangleStrip, PrimitiveMode.TriangleFan, PrimitiveMode.Points, undefined"); + if (typeof attributes_7 !== "object" || attributes_7 === null) + throw new Error("Expected v.attributes to be an object"); + if (attributes_7.POSITION !== undefined && typeof attributes_7.POSITION !== "number") + throw new Error("Expected v.attributes.POSITION to be a number"); + if (attributes_7.NORMAL !== undefined && typeof attributes_7.NORMAL !== "number") + throw new Error("Expected v.attributes.NORMAL to be a number"); + if (attributes_7.TANGENT !== undefined && typeof attributes_7.TANGENT !== "number") + throw new Error("Expected v.attributes.TANGENT to be a number"); + if (attributes_7.TEXCOORD_0 !== undefined && typeof attributes_7.TEXCOORD_0 !== "number") + throw new Error("Expected v.attributes.TEXCOORD_0 to be a number"); + if (attributes_7.TEXCOORD_1 !== undefined && typeof attributes_7.TEXCOORD_1 !== "number") + throw new Error("Expected v.attributes.TEXCOORD_1 to be a number"); +} + +function parsePrimitiveMode(jsonPath: string, value: unknown): PrimitiveMode | ParseError { + if (v !== 1 && v !== 2 && v !== 3 && v !== 4 && v !== 5 && v !== 6 && v !== 0) + throw new Error("Expected v to be one of PrimitiveMode.Lines, PrimitiveMode.LineLoop, PrimitiveMode.LineStrip, PrimitiveMode.Triangles, PrimitiveMode.TriangleStrip, PrimitiveMode.TriangleFan, PrimitiveMode.Points"); +} + +function parseSampler(jsonPath: string, value: unknown): Sampler | ParseError { + if (typeof v !== "object" || v === null) + throw new Error("Expected v to be an object"); + if (v.magFilter !== undefined && v.magFilter !== 9728 && v.magFilter !== 9729) + throw new Error("Expected v.magFilter to be one of MagFilter.Nearest, MagFilter.Linear, undefined"); + if (v.minFilter !== undefined && v.minFilter !== 9728 && v.minFilter !== 9729 && v.minFilter !== 9984 && v.minFilter !== 9985 && v.minFilter !== 9986 && v.minFilter !== 9987) + throw new Error("Expected v.minFilter to be one of MinFilter.Nearest, MinFilter.Linear, MinFilter.NearestMipmapNearest, MinFilter.LinearMipmapNearest, MinFilter.NearestMipmapLinear, MinFilter.LinearMipmapLinear, undefined"); + if (v.wrapS !== undefined && v.wrapS !== 33071 && v.wrapS !== 33648 && v.wrapS !== 10497) + throw new Error("Expected v.wrapS to be one of WrappingMode.ClampToEdge, WrappingMode.MirroredRepeat, WrappingMode.Repeat, undefined"); + if (v.wrapT !== undefined && v.wrapT !== 33071 && v.wrapT !== 33648 && v.wrapT !== 10497) + throw new Error("Expected v.wrapT to be one of WrappingMode.ClampToEdge, WrappingMode.MirroredRepeat, WrappingMode.Repeat, undefined"); +} + +function parseMagFilter(jsonPath: string, value: unknown): MagFilter | ParseError { + if (value !== 9728 && value !== 9729) { + const message = `Invalid magnification filter. Only values of 9728 and 9729 are valid component types. Property at ${jsonPath} is supposed to be a magnification filter, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + return value; +} + +function parseMinFilter(jsonPath: string, value: unknown): MinFilter | ParseError { + if (value !== 9728 && value !== 9729 && value !== 9984 && value !== 9985 && value !== 9986 && value !== 9987) { + const message = `Invalid minification filter. Only values of 9728, 9729, 9984, 9985, 9986 and 9987 are valid component types. Property at ${jsonPath} is supposed to be a minification filter, but has value of ${JSON.stringify(value)}.`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + return value; +} + +function parseWrappingMode(jsonPath: string, value: unknown): WrappingMode | ParseError { + if (value !== 33071 && value !== 33648 && value !== 10497) { + const message = `Invalid wrapping mode. Only values of 33071, 33648 and 10497 are valid wrapping modes. Property at ${jsonPath} is supposed to be a wrapping mode, but has value of ${JSON.stringify(value)}`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + return value; +} + +function parseTexture(jsonPath: string, value: unknown): Texture | ParseError { + if (typeof value !== "object" || value === null) { + const message = `Invalid texture. Property at ${jsonPath} is supposed to be an object, but has value of ${JSON.stringify(value)}`; + return new ParseError({ message, jsonPath, severity: "error" }); + } + + const object = value as Record; + + if (value.sampler !== undefined && typeof value.sampler !== "number") + throw new Error("Expected v.sampler to be a number"); + if (value.source !== undefined && typeof value.source !== "number") + throw new Error("Expected v.source to be a number"); + if (value.name !== undefined && typeof value.name !== "string") + throw new Error("Expected v.name to be a string"); +}