Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			1541d0900b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1541d0900b | 
							
								
								
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +0,0 @@ | |||||||
| *.webm filter=lfs diff=lfs merge=lfs -text |  | ||||||
| *.png filter=lfs diff=lfs merge=lfs -text |  | ||||||
| @@ -1,8 +1,7 @@ | |||||||
| # oktaeder | # oktaeder | ||||||
| 3D rendering library for WebGPU | 3D rendering library for WebGPU | ||||||
|  |  | ||||||
| <video src="https://gitea.renati.me/renati/oktaeder/media/branch/main/oktaeder.webm" autoplay controls loop> | [oktaeder.webm](https://github.com/iszn11/oktaeder/assets/7891270/5dbcb03a-608f-41b8-860e-8e9c8e09e242) | ||||||
| </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,6 +1,4 @@ | |||||||
| /// <reference types="../node_modules/@webgpu/types" /> | import { Color, DirectionalLight, Mesh, Node, PerspectiveCamera, PointLight, Quaternion, Scene, Submesh, Vector3 } from "../src/data/index"; | ||||||
|  |  | ||||||
| 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"; | ||||||
|  |  | ||||||
| @@ -18,74 +16,38 @@ const camera = new PerspectiveCamera({ | |||||||
| 	farPlane: Infinity, | 	farPlane: Infinity, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 12, texCoord: true }); | const vertexBuffer = renderer.createVertexBuffer({ vertexCount: 6 }); | ||||||
| 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, | ||||||
| 		-1, 0, 0, | 		0, 1, 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, 2, 1, | 	0, 4, 3, | ||||||
| 	3, 4, 2, | 	4, 1, 3, | ||||||
|  | 	1, 5, 3, | ||||||
|  | 	5, 0, 3, | ||||||
|  | 	4, 0, 2, | ||||||
|  | 	1, 4, 2, | ||||||
| 	5, 1, 2, | 	5, 1, 2, | ||||||
| 	2, 0, 3, | 	0, 5, 2, | ||||||
| 	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: 0, | 	metallic: 1, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const node = new Node({ mesh, materials: [material] }); | const node = new Node({ mesh, materials: [material] }); | ||||||
| @@ -94,12 +56,24 @@ const scene = new Scene({ | |||||||
| 	nodes: [ | 	nodes: [ | ||||||
| 		node, | 		node, | ||||||
| 		new Node({ | 		new Node({ | ||||||
| 			translation: new Vector3(0, 1, -1), | 			translation: new Vector3(-1, 1, 0), | ||||||
| 			light: new PointLight({ color: new Color(1, 1, 1) }), | 			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(1, 1, 1) }), | 			light: new PointLight({ color: new Color(0, 1, 0) }), | ||||||
|  | 		}), | ||||||
|  | 		new Node({ | ||||||
|  | 			translation: new Vector3(1, 1, 0), | ||||||
|  | 			light: new PointLight({ color: new Color(0, 0, 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), | ||||||
| @@ -117,14 +91,6 @@ 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)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								example/uvmap.png
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								oktaeder.webm
									 (Stored with Git LFS)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								oktaeder.webm
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -22,12 +22,12 @@ | |||||||
| 		"build": "tsc --build" | 		"build": "tsc --build" | ||||||
| 	}, | 	}, | ||||||
| 	"dependencies": { | 	"dependencies": { | ||||||
| 		"tslib": "^2.6.2" | 		"tslib": "^2.6.1" | ||||||
| 	}, | 	}, | ||||||
| 	"devDependencies": { | 	"devDependencies": { | ||||||
| 		"@webgpu/types": "^0.1.40", | 		"@webgpu/types": "^0.1.34", | ||||||
| 		"esbuild": "^0.20.2", | 		"esbuild": "^0.19.2", | ||||||
| 		"typescript": "^5.4.2" | 		"typescript": "5.1.6" | ||||||
| 	}, | 	}, | ||||||
| 	"exports": { | 	"exports": { | ||||||
| 		".": { | 		".": { | ||||||
|   | |||||||
							
								
								
									
										693
									
								
								src/gltf.ts
									
									
									
									
									
								
							
							
						
						
									
										693
									
								
								src/gltf.ts
									
									
									
									
									
								
							| @@ -5,6 +5,7 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| 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 | ||||||
| @@ -106,7 +107,7 @@ export interface ParseResult { | |||||||
|  |  | ||||||
| export interface ParseErrorProps { | export interface ParseErrorProps { | ||||||
| 	message: string; | 	message: string; | ||||||
| 	position?: JsonPosition | undefined; | 	jsonPath?: string | undefined; | ||||||
| 	severity: ParseErrorSeverity; | 	severity: ParseErrorSeverity; | ||||||
| 	options?: ErrorOptions | undefined; | 	options?: ErrorOptions | undefined; | ||||||
| } | } | ||||||
| @@ -119,30 +120,25 @@ export type ParseErrorSeverity = | |||||||
| export class ParseError extends Error { | export class ParseError extends Error { | ||||||
|  |  | ||||||
| 	override message: string; | 	override message: string; | ||||||
| 	position: JsonPosition | undefined; | 	jsonPath: string | undefined; | ||||||
| 	severity: ParseErrorSeverity; | 	severity: ParseErrorSeverity; | ||||||
|  |  | ||||||
| 	constructor({ | 	constructor({ | ||||||
| 		message, | 		message, | ||||||
| 		position, | 		jsonPath, | ||||||
| 		severity, | 		severity, | ||||||
| 		options, | 		options, | ||||||
| 	}: ParseErrorProps) { | 	}: ParseErrorProps) { | ||||||
| 		super(message, options); | 		super(message, options); | ||||||
|  |  | ||||||
| 		this.message = message; | 		this.message = message; | ||||||
| 		this.position = position; | 		this.jsonPath = jsonPath; | ||||||
| 		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 | ||||||
| @@ -176,13 +172,14 @@ 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: resources.Material[] = []; | 	const materials: data.DynamicMaterial[] = []; | ||||||
| 	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; | ||||||
| @@ -268,10 +265,6 @@ 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 ----------------------------------------------------------- | ||||||
| @@ -330,13 +323,17 @@ export type AccessorType = | |||||||
| 	| "MAT4" | 	| "MAT4" | ||||||
| 	; | 	; | ||||||
|  |  | ||||||
|  | export interface Version { | ||||||
|  | 	major: number; | ||||||
|  | 	minor: number; | ||||||
|  | } | ||||||
|  |  | ||||||
| export interface Asset { | export interface Asset { | ||||||
| 	version: `${string}.${string}`; | 	version: Version; | ||||||
| 	minVersion: `${string}.${string}`; | 	minVersion?: Version; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface Buffer { | export interface Buffer { | ||||||
| 	uri?: string; |  | ||||||
| 	byteLength: number; | 	byteLength: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -513,3 +510,661 @@ 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, 255, 255] */ | 	/** 1×1 rgba8unorm texture of [128, 128, 128, 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, 255, 255])); | 		this._textureNormal.writeFull(new Uint8Array([128, 128, 128, 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, | ||||||
| 		compilationHints: [ | 		hints: { | ||||||
| 			{ entryPoint: "vert", layout: renderer._pipelineLayout }, | 			"vert": { layout: renderer._pipelineLayout }, | ||||||
| 			{ entryPoint: "frag", layout: renderer._pipelineLayout }, | 			"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 = textureSample(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); | 		let baseColorPartialCoverageTexel = texture(_BaseColorPartialCoverageTexture, _Sampler, fragment.texCoord); | ||||||
| 		baseColor *= baseColorPartialCoverageTexel.rgb; | 		baseColor *= baseColorPartialCoverageTexel.rgb; | ||||||
| 		partialCoverage *= baseColorPartialCoverageTexel.a; | 		partialCoverage *= baseColorPartialCoverageTexel.a; | ||||||
| 		let roughnessMetallicTexel = textureSample(_RoughnessMetallicTexture, _Sampler, fragment.texCoord); | 		let roughnessMetallicTexel = texture(_RoughnessMetallicTexture, _Sampler, fragment.texCoord); | ||||||
| 		roughness *= roughnessMetallicTexel.g; | 		roughness *= roughnessMetallicTexel.g; | ||||||
| 		metallic *= roughnessMetallicTexel.b; | 		metallic *= roughnessMetallicTexel.b; | ||||||
| 		let emissiveTexel = textureSample(_EmissiveTexture, _Sampler, fragment.texCoord); | 		let emissiveTexel = texture(_EmissiveTexture, _Sampler, fragment.texCoord); | ||||||
| 		emissive *= emissiveTexel.rgb; | 		emissive *= emissiveTexel.rgb; | ||||||
| 	` : ""} | 	` : ""} | ||||||
| 	${lightTexCoord ? ` | 	${lightTexCoord ? ` | ||||||
| 		let occlusionTexel = textureSample(_OcclusionTexture, _Sampler, fragment.lightTexCoord); | 		let occlusionTexel = texture(_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 = textureSample(_NormalTexture, _Sampler, fragment.texCoord); | 		let normalTextureTexel = texture(_NormalTexture, _Sampler, fragment.texCoord); | ||||||
| 		var normalTS = normalTextureTexel.xyz * 2.0 - 1.0; | 		var normalTS = normalTextureTexel.xyz * 2.0 - 1.0; | ||||||
| 		normalTS = vec3(normalTS.xy * _Material.normalScale, normalTS.z); | 		normalTS.xy *= _Material.normalScale; | ||||||
| 		let actualNormalVS = normalize(matrixTStoVS * normalTS); | 		let actualNormalVS = normalize(matrixTStoVS * geometricNormalVS); | ||||||
| 	` : ` | 	` : ` | ||||||
| 		let actualNormalVS = geometricNormalVS; | 		let actualNormalVS = geometricNormalVS; | ||||||
| 	`} | 	`} | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								tsconfig.tsbuildinfo
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								tsconfig.tsbuildinfo
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user