Compare commits
	
		
			2 Commits
		
	
	
		
			1541d0900b
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d28d7896de | ||
| 78683f6115 | 
							
								
								
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | *.webm filter=lfs diff=lfs merge=lfs -text | ||||||
|  | *.png filter=lfs diff=lfs merge=lfs -text | ||||||
| @@ -1,7 +1,8 @@ | |||||||
| # oktaeder | # oktaeder | ||||||
| 3D rendering library for WebGPU | 3D rendering library for WebGPU | ||||||
|  |  | ||||||
| [oktaeder.webm](https://github.com/iszn11/oktaeder/assets/7891270/5dbcb03a-608f-41b8-860e-8e9c8e09e242) | <video src="https://gitea.renati.me/renati/oktaeder/media/branch/main/oktaeder.webm" autoplay controls loop> | ||||||
|  | </video> | ||||||
|  |  | ||||||
| This project ships with [bun.lockb](https://bun.sh/docs/install/lockfile) | This project ships with [bun.lockb](https://bun.sh/docs/install/lockfile) | ||||||
| lockfile for the [Bun](https://bun.sh/) JavaScript runtime. You should be able | lockfile for the [Bun](https://bun.sh/) JavaScript runtime. You should be able | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| import { Color, DirectionalLight, Mesh, Node, PerspectiveCamera, PointLight, Quaternion, Scene, Submesh, Vector3 } from "../src/data/index"; | /// <reference types="../node_modules/@webgpu/types" /> | ||||||
|  |  | ||||||
|  | import { Color, Mesh, Node, PerspectiveCamera, PointLight, Quaternion, Scene, Submesh, Vector3 } from "../src/data/index"; | ||||||
| import { Renderer, degToRad } from "../src/oktaeder"; | import { Renderer, degToRad } from "../src/oktaeder"; | ||||||
| import "./style.css"; | import "./style.css"; | ||||||
|  |  | ||||||
| @@ -16,38 +18,74 @@ const camera = new PerspectiveCamera({ | |||||||
| 	farPlane: Infinity, | 	farPlane: Infinity, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 6 }); | const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 12, texCoord: true }); | ||||||
| vertexBuffer.writeTypedArray(0, { | vertexBuffer.writeTypedArray(0, { | ||||||
| 	position: new Float32Array([ | 	position: new Float32Array([ | ||||||
|  | 		0, 0, 1, | ||||||
|  | 		1, 0, 0, | ||||||
|  | 		0, 1, 0, | ||||||
| 		-1, 0, 0, | 		-1, 0, 0, | ||||||
|  | 		0, 0, -1, | ||||||
|  | 		0, 0, -1, | ||||||
|  | 		0, 0, -1, | ||||||
| 		1, 0, 0, | 		1, 0, 0, | ||||||
| 		0, -1, 0, | 		0, -1, 0, | ||||||
| 		0, 1, 0, | 		-1, 0, 0, | ||||||
| 		0, 0, -1, | 		0, 0, -1, | ||||||
| 		0, 0, 1, | 		0, 0, 1, | ||||||
| 	]), | 	]), | ||||||
|  | 	texCoord: new Float32Array([ | ||||||
|  | 		0.5, 0.7113, | ||||||
|  | 		0.333333, 1, | ||||||
|  | 		0.166666, 0.7113, | ||||||
|  | 		0.333333, 0.4226, | ||||||
|  | 		0, 0.4226, | ||||||
|  | 		0, 1, | ||||||
|  | 		1, 1, | ||||||
|  | 		0.666666, 1, | ||||||
|  | 		0.833333, 0.7113, | ||||||
|  | 		0.666666, 0.4226, | ||||||
|  | 		1, 0.4226, | ||||||
|  | 		0.5, 0.7113, | ||||||
|  | 	]), | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const indexBuffer = renderer.createIndexBuffer({ indexCount: 24, indexFormat: "uint16" }); | const indexBuffer = renderer.createIndexBuffer({ indexCount: 24, indexFormat: "uint16" }); | ||||||
| indexBuffer.writeArray(0, [ | indexBuffer.writeArray(0, [ | ||||||
| 	0, 4, 3, | 	0, 2, 1, | ||||||
| 	4, 1, 3, | 	3, 4, 2, | ||||||
| 	1, 5, 3, |  | ||||||
| 	5, 0, 3, |  | ||||||
| 	4, 0, 2, |  | ||||||
| 	1, 4, 2, |  | ||||||
| 	5, 1, 2, | 	5, 1, 2, | ||||||
| 	0, 5, 2, | 	2, 0, 3, | ||||||
|  | 	6, 8, 7, | ||||||
|  | 	9, 8, 10, | ||||||
|  | 	7, 8, 11, | ||||||
|  | 	11, 8, 9, | ||||||
| ]); | ]); | ||||||
|  |  | ||||||
| const submesh: Submesh = { start: 0, length: 24 }; | const submesh: Submesh = { start: 0, length: 24 }; | ||||||
|  |  | ||||||
| const mesh = new Mesh({ vertexBuffer, indexBuffer, submeshes: [submesh] }); | const mesh = new Mesh({ vertexBuffer, indexBuffer, submeshes: [submesh] }); | ||||||
|  |  | ||||||
|  | const imageBitmap = await loadImageBitmap("/uvmap.png"); | ||||||
|  |  | ||||||
|  | const texture = renderer.createTexture({ | ||||||
|  | 	format: "srgb", | ||||||
|  | 	width: imageBitmap.width, | ||||||
|  | 	height: imageBitmap.height, | ||||||
|  | 	usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | renderer._device.queue.copyExternalImageToTexture( | ||||||
|  | 	{ source: imageBitmap, flipY: false }, | ||||||
|  | 	{ texture: texture._texture }, | ||||||
|  | 	{ width: imageBitmap.width, height: imageBitmap.height }, | ||||||
|  | ); | ||||||
|  |  | ||||||
| const material = renderer.createMaterial({ | const material = renderer.createMaterial({ | ||||||
| 	baseColor: Color.white(), | 	baseColor: Color.white(), | ||||||
|  | 	baseColorPartialCoverageTexture: texture, | ||||||
| 	roughness: 0.5, | 	roughness: 0.5, | ||||||
| 	metallic: 1, | 	metallic: 0, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const node = new Node({ mesh, materials: [material] }); | const node = new Node({ mesh, materials: [material] }); | ||||||
| @@ -55,25 +93,13 @@ const node = new Node({ mesh, materials: [material] }); | |||||||
| const scene = new Scene({ | const scene = new Scene({ | ||||||
| 	nodes: [ | 	nodes: [ | ||||||
| 		node, | 		node, | ||||||
| 		new Node({ |  | ||||||
| 			translation: new Vector3(-1, 1, 0), |  | ||||||
| 			light: new PointLight({ color: new Color(1, 0, 0) }), |  | ||||||
| 		}), |  | ||||||
| 		new Node({ | 		new Node({ | ||||||
| 			translation: new Vector3(0, 1, -1), | 			translation: new Vector3(0, 1, -1), | ||||||
| 			light: new PointLight({ color: new Color(0, 1, 0) }), | 			light: new PointLight({ color: new Color(1, 1, 1) }), | ||||||
| 		}), | 		}), | ||||||
| 		new Node({ | 		new Node({ | ||||||
| 			translation: new Vector3(1, 1, 0), | 			translation: new Vector3(0, -1, -1), | ||||||
| 			light: new PointLight({ color: new Color(0, 0, 1) }), | 			light: new PointLight({ color: new Color(1, 1, 1) }), | ||||||
| 		}), |  | ||||||
| 		new Node({ |  | ||||||
| 			translation: new Vector3(0, 1, 1), |  | ||||||
| 			light: new PointLight({ color: new Color(1, 1, 0) }), |  | ||||||
| 		}), |  | ||||||
| 		new Node({ |  | ||||||
| 			rotation: Quaternion.fromRotationYZ(degToRad(-90)), |  | ||||||
| 			light: new DirectionalLight({ color: new Color(0.5, 0.5, 0.5) }), |  | ||||||
| 		}), | 		}), | ||||||
| 		new Node({ | 		new Node({ | ||||||
| 			translation: new Vector3(0, 0.8, -3), | 			translation: new Vector3(0, 0.8, -3), | ||||||
| @@ -91,6 +117,14 @@ function onResize(this: Window) { | |||||||
|  |  | ||||||
| const _quaternion = Quaternion.identity(); | const _quaternion = Quaternion.identity(); | ||||||
|  |  | ||||||
|  | async function loadImageBitmap(url: string) { | ||||||
|  | 	const res = await fetch(url); | ||||||
|  | 	const blob = await res.blob(); | ||||||
|  | 	const imageBitmap = await createImageBitmap(blob, { colorSpaceConversion: "none" }); | ||||||
|  |  | ||||||
|  | 	return imageBitmap; | ||||||
|  | } | ||||||
|  |  | ||||||
| function draw(timeMs: number) { | function draw(timeMs: number) { | ||||||
| 	const time = 0.001 * timeMs; | 	const time = 0.001 * timeMs; | ||||||
| 	node.setRotation(_quaternion.setRotationZX(-0.5 * time)); | 	node.setRotation(_quaternion.setRotationZX(-0.5 * time)); | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								example/uvmap.png
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								example/uvmap.png
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								oktaeder.webm
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								oktaeder.webm
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -22,12 +22,12 @@ | |||||||
| 		"build": "tsc --build" | 		"build": "tsc --build" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"tslib": "^2.6.1" | 		"tslib": "^2.6.2" | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": { | 	"devDependencies": { | ||||||
| 		"@webgpu/types": "^0.1.34", | 		"@webgpu/types": "^0.1.40", | ||||||
| 		"esbuild": "^0.19.2", | 		"esbuild": "^0.20.2", | ||||||
| 		"typescript": "5.1.6" | 		"typescript": "^5.4.2" | ||||||
| 	}, | 	}, | ||||||
| 	"exports": { | 	"exports": { | ||||||
| 		".": { | 		".": { | ||||||
|   | |||||||
							
								
								
									
										767
									
								
								src/gltf.ts
									
									
									
									
									
								
							
							
						
						
									
										767
									
								
								src/gltf.ts
									
									
									
									
									
								
							| @@ -5,7 +5,6 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import * as data from "./data"; | import * as data from "./data"; | ||||||
| import { Renderer } from "./oktaeder"; |  | ||||||
| import * as resources from "./resources"; | import * as resources from "./resources"; | ||||||
|  |  | ||||||
| /* INITIAL SUPPORT PLAN | /* INITIAL SUPPORT PLAN | ||||||
| @@ -17,7 +16,7 @@ import * as resources from "./resources"; | |||||||
|  * - accessors: used indirectly |  * - accessors: used indirectly | ||||||
|  *   - read when converting mesh |  *   - read when converting mesh | ||||||
|  *   - sparse: no support |  *   - sparse: no support | ||||||
|  *	 - issues error |  *     - issues error | ||||||
|  * - animations: no support |  * - animations: no support | ||||||
|  *   - issues warning |  *   - issues warning | ||||||
|  *   - no animations emitted |  *   - no animations emitted | ||||||
| @@ -27,65 +26,65 @@ import * as resources from "./resources"; | |||||||
|  * - buffers: used indirectly |  * - buffers: used indirectly | ||||||
|  *   - read when converting mesh |  *   - read when converting mesh | ||||||
|  *   - uri: no support |  *   - uri: no support | ||||||
|  *	 - issues error |  *     - issues error | ||||||
|  * - bufferViews: used indirectly |  * - bufferViews: used indirectly | ||||||
|  *   - read when converting mesh |  *   - read when converting mesh | ||||||
|  * - cameras: |  * - cameras: | ||||||
|  *   - orthographic: |  *   - orthographic: | ||||||
|  *	 - xmag: ignored |  *     - xmag: ignored | ||||||
|  *	 - ymag: converted to halfVerticalSize |  *     - ymag: converted to halfVerticalSize | ||||||
|  *   - perspective: |  *   - perspective: | ||||||
|  *	 - aspectRatio: ignored |  *     - aspectRatio: ignored | ||||||
|  *	   - issues warning when provided |  *       - issues warning when provided | ||||||
|  * - images: |  * - images: | ||||||
|  *   - uri: no support |  *   - uri: no support | ||||||
|  *	 - issues error |  *     - issues error | ||||||
|  * - materials: |  * - materials: | ||||||
|  *   - name: full support |  *   - name: full support | ||||||
|  *   - pbrMetallicRoughness: |  *   - pbrMetallicRoughness: | ||||||
|  *	 - baseColorFactor: full support |  *     - baseColorFactor: full support | ||||||
|  *	 - baseColorTexture: partial support |  *     - baseColorTexture: partial support | ||||||
|  *	   - forced texCoord 0 |  *       - forced texCoord 0 | ||||||
|  *	   - issues error when different provided |  *       - issues error when different provided | ||||||
|  *	 - metallicFactor: full support |  *     - metallicFactor: full support | ||||||
|  *	 - roughnessFactor: full support |  *     - roughnessFactor: full support | ||||||
|  *	 - metallicRoughnessTexture: partial support |  *     - metallicRoughnessTexture: partial support | ||||||
|  *	   - forced texCoord 0 |  *       - forced texCoord 0 | ||||||
|  *	   - issues error when different provided |  *       - issues error when different provided | ||||||
|  *   - normalTexture: partial support |  *   - normalTexture: partial support | ||||||
|  *	 - scale: full support |  *     - scale: full support | ||||||
|  *	 - forced texCoord 0 |  *     - forced texCoord 0 | ||||||
|  *	 - issues error when different provided |  *     - issues error when different provided | ||||||
|  *   - occlusionTexture: partial support |  *   - occlusionTexture: partial support | ||||||
|  *	 - strength: full support |  *     - strength: full support | ||||||
|  *	 - forced texCoord 1 |  *     - forced texCoord 1 | ||||||
|  *	 - issues error when different provided |  *     - issues error when different provided | ||||||
|  *   - emissiveTexture: partial support |  *   - emissiveTexture: partial support | ||||||
|  *	 - forced texCoord 0 |  *     - forced texCoord 0 | ||||||
|  *	 - issues error when different provided |  *     - issues error when different provided | ||||||
|  *   - emissiveFactor: full support |  *   - emissiveFactor: full support | ||||||
|  *   - alphaMode: |  *   - alphaMode: | ||||||
|  *	 - OPAQUE: full support |  *     - OPAQUE: full support | ||||||
|  *	 - MASK: no support |  *     - MASK: no support | ||||||
|  *	   - issues error |  *       - issues error | ||||||
|  *	 - BLEND: partial support |  *     - BLEND: partial support | ||||||
|  *	   - decoded, but not implemented |  *       - decoded, but not implemented | ||||||
|  *   - doubleSided: prtial support |  *   - doubleSided: prtial support | ||||||
|  *	   - decoded, but not implemented |  *       - decoded, but not implemented | ||||||
|  * |  * | ||||||
|  * Extensions: |  * Extensions: | ||||||
|  * - KHR_lights_punctual |  * - KHR_lights_punctual | ||||||
|  *   - name: full support |  *   - name: full support | ||||||
|  *   - color/intensity: full support |  *   - color/intensity: full support | ||||||
|  *	 - converted to color = color * intensity |  *     - converted to color = color * intensity | ||||||
|  *   - type: |  *   - type: | ||||||
|  *	 - directional: full support |  *     - directional: full support | ||||||
|  *	 - point: full support |  *     - point: full support | ||||||
|  *	 - spot: no support |  *     - spot: no support | ||||||
|  *	   - issues error |  *       - issues error | ||||||
|  *   - range: no support |  *   - range: no support | ||||||
|  *	 - issues warning |  *     - issues warning | ||||||
|  *	 - always infite range |  *     - always infite range | ||||||
|  * - KHR_materials_emissive_strength: full support |  * - KHR_materials_emissive_strength: full support | ||||||
|  *   - converted to emissive = emissive * strength |  *   - converted to emissive = emissive * strength | ||||||
|  * - KHR_materials_ior: full support |  * - KHR_materials_ior: full support | ||||||
| @@ -107,7 +106,7 @@ export interface ParseResult { | |||||||
|  |  | ||||||
| export interface ParseErrorProps { | export interface ParseErrorProps { | ||||||
| 	message: string; | 	message: string; | ||||||
| 	jsonPath?: string | undefined; | 	position?: JsonPosition | undefined; | ||||||
| 	severity: ParseErrorSeverity; | 	severity: ParseErrorSeverity; | ||||||
| 	options?: ErrorOptions | undefined; | 	options?: ErrorOptions | undefined; | ||||||
| } | } | ||||||
| @@ -120,25 +119,30 @@ export type ParseErrorSeverity = | |||||||
| export class ParseError extends Error { | export class ParseError extends Error { | ||||||
|  |  | ||||||
| 	override message: string; | 	override message: string; | ||||||
| 	jsonPath: string | undefined; | 	position: JsonPosition | undefined; | ||||||
| 	severity: ParseErrorSeverity; | 	severity: ParseErrorSeverity; | ||||||
|  |  | ||||||
| 	constructor({ | 	constructor({ | ||||||
| 		message, | 		message, | ||||||
| 		jsonPath, | 		position, | ||||||
| 		severity, | 		severity, | ||||||
| 		options, | 		options, | ||||||
| 	}: ParseErrorProps) { | 	}: ParseErrorProps) { | ||||||
| 		super(message, options); | 		super(message, options); | ||||||
|  |  | ||||||
| 		this.message = message; | 		this.message = message; | ||||||
| 		this.jsonPath = jsonPath; | 		this.position = position; | ||||||
| 		this.severity = severity; | 		this.severity = severity; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface JsonPosition { | ||||||
|  | 	readonly line: number; | ||||||
|  | 	readonly column: number; | ||||||
|  | 	readonly path: number; | ||||||
|  | } | ||||||
|  |  | ||||||
| export interface ParseOptions { | export interface ParseOptions { | ||||||
| 	readonly renderer: Renderer; |  | ||||||
| 	/** | 	/** | ||||||
| 	 * When `true`, the parser will throw with a `ParseError` on the first error | 	 * When `true`, the parser will throw with a `ParseError` on the first error | ||||||
| 	 * encountered. This includes warnings when `treatWarningsAsErrors` is | 	 * encountered. This includes warnings when `treatWarningsAsErrors` is | ||||||
| @@ -172,14 +176,13 @@ export interface ParseOptions { | |||||||
| } | } | ||||||
|  |  | ||||||
| export async function parse(gltf: ArrayBufferView, { | export async function parse(gltf: ArrayBufferView, { | ||||||
| 	renderer, |  | ||||||
| 	throwOnError = true, | 	throwOnError = true, | ||||||
| 	stopOnFirstError = true, | 	stopOnFirstError = true, | ||||||
| 	treatWarningsAsErrors = false, | 	treatWarningsAsErrors = false, | ||||||
| }: ParseOptions): Promise<ParseResult> { | }: ParseOptions = {}): Promise<ParseResult> { | ||||||
|  |  | ||||||
| 	const cameras: data.Camera[] = []; | 	const cameras: data.Camera[] = []; | ||||||
| 	const materials: data.DynamicMaterial[] = []; | 	const materials: resources.Material[] = []; | ||||||
| 	const lights: data.Light[] = []; | 	const lights: data.Light[] = []; | ||||||
| 	const scenes: data.Scene[] = []; | 	const scenes: data.Scene[] = []; | ||||||
| 	const scene: data.Scene | null = null; | 	const scene: data.Scene | null = null; | ||||||
| @@ -265,6 +268,10 @@ export async function parse(gltf: ArrayBufferView, { | |||||||
|  |  | ||||||
| 	// --- JSON CHUNK ---------------------------------------------------------- | 	// --- JSON CHUNK ---------------------------------------------------------- | ||||||
|  |  | ||||||
|  | 	void(stopOnFirstError); | ||||||
|  | 	void(treatWarningsAsErrors); | ||||||
|  | 	void(rest); | ||||||
|  |  | ||||||
| 	throw new Error("TODO"); | 	throw new Error("TODO"); | ||||||
|  |  | ||||||
| 	// --- BIN CHUNK ----------------------------------------------------------- | 	// --- BIN CHUNK ----------------------------------------------------------- | ||||||
| @@ -323,17 +330,13 @@ export type AccessorType = | |||||||
| 	| "MAT4" | 	| "MAT4" | ||||||
| 	; | 	; | ||||||
|  |  | ||||||
| export interface Version { |  | ||||||
| 	major: number; |  | ||||||
| 	minor: number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export interface Asset { | export interface Asset { | ||||||
| 	version: Version; | 	version: `${string}.${string}`; | ||||||
| 	minVersion?: Version; | 	minVersion: `${string}.${string}`; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface Buffer { | export interface Buffer { | ||||||
|  | 	uri?: string; | ||||||
| 	byteLength: number; | 	byteLength: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -510,661 +513,3 @@ export interface Texture { | |||||||
| 	source?: number; | 	source?: number; | ||||||
| 	name?: string; | 	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<string, unknown>; |  | ||||||
|  |  | ||||||
| 	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 <major>.<minor>, 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 <major>.<minor>, 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 <major>.<minor>, 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<string, unknown>; |  | ||||||
|  |  | ||||||
| 	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"); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ export class Renderer { | |||||||
| 	_textureWhite: Texture2D; | 	_textureWhite: Texture2D; | ||||||
| 	/** 1×1 rgba8unorm texture of [0, 0, 0, 255] */ | 	/** 1×1 rgba8unorm texture of [0, 0, 0, 255] */ | ||||||
| 	_textureBlack: Texture2D; | 	_textureBlack: Texture2D; | ||||||
| 	/** 1×1 rgba8unorm texture of [128, 128, 128, 255] */ | 	/** 1×1 rgba8unorm texture of [128, 128, 255, 255] */ | ||||||
| 	_textureNormal: Texture2D; | 	_textureNormal: Texture2D; | ||||||
|  |  | ||||||
| 	_depthBuffer: Texture2D; | 	_depthBuffer: Texture2D; | ||||||
| @@ -110,7 +110,7 @@ export class Renderer { | |||||||
| 			height: 1, | 			height: 1, | ||||||
| 			format: "linear", | 			format: "linear", | ||||||
| 		}); | 		}); | ||||||
| 		this._textureNormal.writeFull(new Uint8Array([128, 128, 128, 255])); | 		this._textureNormal.writeFull(new Uint8Array([128, 128, 255, 255])); | ||||||
|  |  | ||||||
| 		const framebufferTexture = this._context.getCurrentTexture(); | 		const framebufferTexture = this._context.getCurrentTexture(); | ||||||
| 		this._depthBuffer = new Texture2D(this, { | 		this._depthBuffer = new Texture2D(this, { | ||||||
|   | |||||||
| @@ -46,10 +46,10 @@ export function _createPipeline(renderer: Renderer, { | |||||||
|  |  | ||||||
| 	const shaderModule = renderer._device.createShaderModule({ | 	const shaderModule = renderer._device.createShaderModule({ | ||||||
| 		code: shaderCode, | 		code: shaderCode, | ||||||
| 		hints: { | 		compilationHints: [ | ||||||
| 			"vert": { layout: renderer._pipelineLayout }, | 			{ entryPoint: "vert", layout: renderer._pipelineLayout }, | ||||||
| 			"frag": { layout: renderer._pipelineLayout }, | 			{ entryPoint: "frag", layout: renderer._pipelineLayout }, | ||||||
| 		}, | 		], | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	let vertexLocation = 0; | 	let vertexLocation = 0; | ||||||
| @@ -318,17 +318,17 @@ fn frag(fragment: Varyings) -> @location(0) vec4<f32> { | |||||||
| 	var emissive = _Material.emissive; | 	var emissive = _Material.emissive; | ||||||
| 	var ior = _Material.ior; | 	var ior = _Material.ior; | ||||||
| 	${texCoord ? ` | 	${texCoord ? ` | ||||||
| 		let baseColorPartialCoverageTexel = texture(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); | 		let baseColorPartialCoverageTexel = textureSample(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); | ||||||
| 		baseColor *= baseColorPartialCoverageTexel.rgb; | 		baseColor *= baseColorPartialCoverageTexel.rgb; | ||||||
| 		partialCoverage *= baseColorPartialCoverageTexel.a; | 		partialCoverage *= baseColorPartialCoverageTexel.a; | ||||||
| 		let roughnessMetallicTexel = texture(_RoughnessMetallicTexture, _Sampler, fragment.texCoord); | 		let roughnessMetallicTexel = textureSample(_RoughnessMetallicTexture, _Sampler, fragment.texCoord); | ||||||
| 		roughness *= roughnessMetallicTexel.g; | 		roughness *= roughnessMetallicTexel.g; | ||||||
| 		metallic *= roughnessMetallicTexel.b; | 		metallic *= roughnessMetallicTexel.b; | ||||||
| 		let emissiveTexel = texture(_EmissiveTexture, _Sampler, fragment.texCoord); | 		let emissiveTexel = textureSample(_EmissiveTexture, _Sampler, fragment.texCoord); | ||||||
| 		emissive *= emissiveTexel.rgb; | 		emissive *= emissiveTexel.rgb; | ||||||
| 	` : ""} | 	` : ""} | ||||||
| 	${lightTexCoord ? ` | 	${lightTexCoord ? ` | ||||||
| 		let occlusionTexel = texture(_OcclusionTexture, _Sampler, fragment.lightTexCoord); | 		let occlusionTexel = textureSample(_OcclusionTexture, _Sampler, fragment.lightTexCoord); | ||||||
| 		occlusion += _Material.occlusionTextureStrength * (occlusionTexel.r - 1.0); | 		occlusion += _Material.occlusionTextureStrength * (occlusionTexel.r - 1.0); | ||||||
| 	` : ""} | 	` : ""} | ||||||
|  |  | ||||||
| @@ -348,10 +348,10 @@ fn frag(fragment: Varyings) -> @location(0) vec4<f32> { | |||||||
| 		` : ` | 		` : ` | ||||||
| 			let matrixTStoVS = screenSpaceMatrixTStoVS(positionVS, geometricNormalVS, fragment.texCoord); | 			let matrixTStoVS = screenSpaceMatrixTStoVS(positionVS, geometricNormalVS, fragment.texCoord); | ||||||
| 		`} | 		`} | ||||||
| 		let normalTextureTexel = texture(_NormalTexture, _Sampler, fragment.texCoord); | 		let normalTextureTexel = textureSample(_NormalTexture, _Sampler, fragment.texCoord); | ||||||
| 		var normalTS = normalTextureTexel.xyz * 2.0 - 1.0; | 		var normalTS = normalTextureTexel.xyz * 2.0 - 1.0; | ||||||
| 		normalTS.xy *= _Material.normalScale; | 		normalTS = vec3(normalTS.xy * _Material.normalScale, normalTS.z); | ||||||
| 		let actualNormalVS = normalize(matrixTStoVS * geometricNormalVS); | 		let actualNormalVS = normalize(matrixTStoVS * normalTS); | ||||||
| 	` : ` | 	` : ` | ||||||
| 		let actualNormalVS = geometricNormalVS; | 		let actualNormalVS = geometricNormalVS; | ||||||
| 	`} | 	`} | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user