Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Szymon Nowakowski | 1541d0900b |
|
@ -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;
|
||||||
`}
|
`}
|
||||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue