Begin glTF parser
This commit is contained in:
parent
f893690dd0
commit
81abf17469
@ -11,7 +11,7 @@ export type Camera = OrthographicCamera | PerspectiveCamera;
|
||||
export interface OrthographicCameraProps {
|
||||
readonly name?: string;
|
||||
|
||||
readonly verticalSize: number;
|
||||
readonly halfVerticalSize: number;
|
||||
readonly nearPlane: number;
|
||||
readonly farPlane: number;
|
||||
}
|
||||
@ -39,13 +39,13 @@ export class OrthographicCamera {
|
||||
|
||||
constructor({
|
||||
name = "",
|
||||
verticalSize,
|
||||
halfVerticalSize,
|
||||
nearPlane,
|
||||
farPlane,
|
||||
}: OrthographicCameraProps) {
|
||||
this._name = name;
|
||||
|
||||
this._halfVerticalSize = verticalSize;
|
||||
this._halfVerticalSize = halfVerticalSize;
|
||||
this._nearPlane = nearPlane;
|
||||
this._farPlane = farPlane;
|
||||
|
||||
|
@ -1,3 +1,9 @@
|
||||
/*!
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
* obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export function degToRad(angleDeg: number): number {
|
||||
return angleDeg * Math.PI / 180;
|
||||
}
|
||||
|
510
src/gltf.ts
Normal file
510
src/gltf.ts
Normal file
@ -0,0 +1,510 @@
|
||||
/*!
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public License,
|
||||
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
||||
* obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import * as data from "./data";
|
||||
|
||||
/* INITIAL SUPPORT PLAN
|
||||
*
|
||||
* Basic properties:
|
||||
* - extensionsRequired:
|
||||
* - issues error when any extension not supported at least partially
|
||||
* - extensionsUsed: ignored
|
||||
* - accessors: used indirectly
|
||||
* - read when converting mesh
|
||||
* - sparse: no support
|
||||
* - issues error
|
||||
* - animations: no support
|
||||
* - issues warning
|
||||
* - no animations emitted
|
||||
* - asset:
|
||||
* - version: verified
|
||||
* - rest: ignored
|
||||
* - buffers: used indirectly
|
||||
* - read when converting mesh
|
||||
* - uri: no support
|
||||
* - issues error
|
||||
* - bufferViews: used indirectly
|
||||
* - read when converting mesh
|
||||
* - cameras:
|
||||
* - orthographic:
|
||||
* - xmag: ignored
|
||||
* - ymag: converted to halfVerticalSize
|
||||
* - perspective:
|
||||
* - aspectRatio: ignored
|
||||
* - issues warning when provided
|
||||
* - images:
|
||||
* - uri: no support
|
||||
* - issues error
|
||||
* - materials:
|
||||
* - name: full support
|
||||
* - pbrMetallicRoughness:
|
||||
* - baseColorFactor: full support
|
||||
* - baseColorTexture: partial support
|
||||
* - forced texCoord 0
|
||||
* - issues error when different provided
|
||||
* - metallicFactor: full support
|
||||
* - roughnessFactor: full support
|
||||
* - metallicRoughnessTexture: partial support
|
||||
* - forced texCoord 0
|
||||
* - issues error when different provided
|
||||
* - normalTexture: partial support
|
||||
* - scale: full support
|
||||
* - forced texCoord 0
|
||||
* - issues error when different provided
|
||||
* - occlusionTexture: partial support
|
||||
* - strength: full support
|
||||
* - forced texCoord 1
|
||||
* - issues error when different provided
|
||||
* - emissiveTexture: partial support
|
||||
* - forced texCoord 0
|
||||
* - issues error when different provided
|
||||
* - emissiveFactor: full support
|
||||
* - alphaMode:
|
||||
* - OPAQUE: full support
|
||||
* - MASK: no support
|
||||
* - issues error
|
||||
* - BLEND: partial support
|
||||
* - decoded, but not implemented
|
||||
* - doubleSided: prtial support
|
||||
* - decoded, but not implemented
|
||||
*
|
||||
* Extensions:
|
||||
* - KHR_lights_punctual
|
||||
* - name: full support
|
||||
* - color/intensity: full support
|
||||
* - converted to color = color * intensity
|
||||
* - type:
|
||||
* - directional: full support
|
||||
* - point: full support
|
||||
* - spot: no support
|
||||
* - issues error
|
||||
* - range: no support
|
||||
* - issues warning
|
||||
* - always infite range
|
||||
* - KHR_materials_emissive_strength: full support
|
||||
* - converted to emissive = emissive * strength
|
||||
* - KHR_materials_ior: full support
|
||||
* - when not provided, glTF's default is used (1.5) intead of oktaeder's (1.45)
|
||||
* - KHR_materials_ior: full support
|
||||
* - probably
|
||||
*/
|
||||
|
||||
export interface ParseResult {
|
||||
readonly cameras: readonly data.Camera[];
|
||||
readonly materials: readonly data.Material[];
|
||||
readonly lights: readonly data.Light[];
|
||||
readonly scenes: readonly data.Scene[];
|
||||
readonly scene: data.Scene | null;
|
||||
|
||||
readonly warnings: readonly ParseError[];
|
||||
readonly errors: readonly ParseError[];
|
||||
}
|
||||
|
||||
export interface ParseErrorProps {
|
||||
message: string;
|
||||
position?: JsonPosition | undefined;
|
||||
severity: ParseErrorSeverity;
|
||||
options?: ErrorOptions | undefined;
|
||||
}
|
||||
|
||||
export type ParseErrorSeverity =
|
||||
| "warning"
|
||||
| "error"
|
||||
;
|
||||
|
||||
export class ParseError extends Error {
|
||||
|
||||
override message: string;
|
||||
position: JsonPosition | undefined;
|
||||
severity: ParseErrorSeverity;
|
||||
|
||||
constructor({
|
||||
message,
|
||||
position,
|
||||
severity,
|
||||
options,
|
||||
}: ParseErrorProps) {
|
||||
super(message, options);
|
||||
|
||||
this.message = message;
|
||||
this.position = position;
|
||||
this.severity = severity;
|
||||
}
|
||||
}
|
||||
|
||||
export interface JsonPosition {
|
||||
readonly line: number;
|
||||
readonly column: number;
|
||||
readonly path: number;
|
||||
}
|
||||
|
||||
export interface ParseOptions {
|
||||
/**
|
||||
* When `true`, the parser will throw with a `ParseError` on the first error
|
||||
* encountered. This includes warnings when `treatWarningsAsErrors` is
|
||||
* `true`. When `false`, the parser will always return `ParseResult` and is
|
||||
* never expected to throw. Failures are then communicated with the
|
||||
* `ParseResult.errors` array.
|
||||
*
|
||||
* When this option is `true`, `stopOnFirstError` has no effect.
|
||||
* @default true
|
||||
*/
|
||||
readonly throwOnError?: boolean;
|
||||
/**
|
||||
* When `true`, the parser will stop processing on the first error
|
||||
* encountered. This includes warnings when `treatWarningsAsErrors` is
|
||||
* `true`. When `false`, the parser will continue processing when it
|
||||
* encounters an error that it consideres recoverable.
|
||||
*
|
||||
* This option has no effect when `throwOnError` is `true`.
|
||||
* @default true
|
||||
*/
|
||||
readonly stopOnFirstError?: boolean;
|
||||
/**
|
||||
* When `true`, the parser will treat any encountered warning as a failure
|
||||
* for the purpose of the other options. Note that regardless of this
|
||||
* option, the warnings will always be returned in the
|
||||
* `ParseResult.warnings` array and they will always have their `severity`
|
||||
* property equal to `"warning"`.
|
||||
* @default false
|
||||
*/
|
||||
readonly treatWarningsAsErrors?: boolean;
|
||||
}
|
||||
|
||||
export async function parse(gltf: ArrayBufferView, {
|
||||
throwOnError = true,
|
||||
stopOnFirstError = true,
|
||||
treatWarningsAsErrors = false,
|
||||
}: ParseOptions = {}): Promise<ParseResult> {
|
||||
|
||||
const cameras: data.Camera[] = [];
|
||||
const materials: data.Material[] = [];
|
||||
const lights: data.Light[] = [];
|
||||
const scenes: data.Scene[] = [];
|
||||
const scene: data.Scene | null = null;
|
||||
|
||||
const warnings: ParseError[] = [];
|
||||
const errors: ParseError[] = [];
|
||||
|
||||
function makeParseResult(): ParseResult {
|
||||
return Object.freeze({
|
||||
cameras: Object.freeze(cameras),
|
||||
materials: Object.freeze(materials),
|
||||
lights: Object.freeze(lights),
|
||||
scenes: Object.freeze(scenes),
|
||||
scene,
|
||||
|
||||
warnings: Object.freeze(warnings),
|
||||
errors: Object.freeze(errors),
|
||||
});
|
||||
}
|
||||
|
||||
let gltfDataView = new DataView(gltf.buffer, gltf.byteOffset, gltf.byteLength);
|
||||
|
||||
// --- GLB HEADER ----------------------------------------------------------
|
||||
|
||||
if (gltfDataView.byteLength < 12) {
|
||||
const message = `glTF buffer view is too short to be a valid binary glTF container. Binary glTF begins with a 12-byte header, but the provided buffer view has byte length of ${gltf.byteLength}`;
|
||||
const error = new ParseError({ message, severity: "error" });
|
||||
if (throwOnError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
errors.push(error);
|
||||
// unrecoverable error
|
||||
return makeParseResult();
|
||||
}
|
||||
|
||||
const magic = gltfDataView.getUint32(0, true);
|
||||
const version = gltfDataView.getUint32(4, true);
|
||||
let length = gltfDataView.getUint32(8, true);
|
||||
|
||||
if (magic !== 0x46546C67) {
|
||||
const message = `glTF container has invalid magic bytes. The first four bytes must have a value of 0x46546C67 when read as little endian unsigned integer, but in the provided buffer view they have the value of ${u32toHexString(magic)}`;
|
||||
const error = new ParseError({ message, severity: "error" });
|
||||
if (throwOnError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
errors.push(error);
|
||||
/* NOTE This error is considered unrecoverable, because it is very
|
||||
* likely that when the magic bytes are wrong, the provided buffer
|
||||
* view points to a completely different format or garbage data and
|
||||
* it would be pointless to continue parsing in this case.
|
||||
*/
|
||||
return makeParseResult();
|
||||
}
|
||||
|
||||
if (version !== 2) {
|
||||
const message = `Unsupported binary glTF container format. The bytes 4-8 define the binary glTF conatiner format version when read as little endian unsigned integer. Only version 2 is supported, but in the provided buffer they have the value of ${version}`;
|
||||
const error = new ParseError({ message, severity: "error" });
|
||||
if (throwOnError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
errors.push(error);
|
||||
// unrecoverable error
|
||||
return makeParseResult();
|
||||
}
|
||||
|
||||
if (length !== gltf.byteLength) {
|
||||
const message = `Invalid glTF container length. The bytes 8-12 define the length in bytes of the entirety of the binary glTF container when read as little endian unsigned integer. The container byte length is defined as ${length}, but the provided buffer view has byte length of ${gltf.byteLength}`;
|
||||
const error = new ParseError({ message, severity: "error" });
|
||||
if (throwOnError) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
errors.push(error);
|
||||
// recovery: use the lower length value and pretend its the actual length
|
||||
length = Math.min(length, gltf.byteLength);
|
||||
gltfDataView = new DataView(gltf.buffer, gltf.byteOffset, length);
|
||||
}
|
||||
|
||||
let rest = new DataView(gltf.buffer, gltf.byteOffset + 12, gltf.byteLength - 12);
|
||||
|
||||
// --- JSON CHUNK ----------------------------------------------------------
|
||||
|
||||
throw new Error("TODO");
|
||||
|
||||
// --- BIN CHUNK -----------------------------------------------------------
|
||||
}
|
||||
|
||||
function u32toHexString(value: number) {
|
||||
return "0x" + ("00000000" + value.toString(16)).slice(-8);
|
||||
}
|
||||
|
||||
// --- GLTF DATA STRUCTURES ----------------------------------------------------
|
||||
|
||||
export interface Gltf {
|
||||
extensionsRequired?: [string, ...string[]];
|
||||
accessors?: [Accessor, ...Accessor[]];
|
||||
asset: Asset;
|
||||
buffers?: [Buffer, ...Buffer[]];
|
||||
bufferViews?: [BufferView, ...BufferView[]];
|
||||
cameras?: [Camera, ...Camera[]];
|
||||
images?: [Image, ...Image[]];
|
||||
materials?: [Material, ...Material[]];
|
||||
meshes?: [Mesh, ...Mesh[]];
|
||||
nodes?: [Node, ...Node[]];
|
||||
samplers?: [Sampler, ...Sampler[]];
|
||||
scene?: number;
|
||||
scenes?: [number, ...number[]];
|
||||
textures?: [Texture, ...Texture[]];
|
||||
}
|
||||
|
||||
export interface Accessor {
|
||||
bufferView?: number;
|
||||
/** @default 0 */
|
||||
byteOffset?: number;
|
||||
componentType: ComponentType;
|
||||
/** @default false */
|
||||
normalized?: boolean;
|
||||
count: number;
|
||||
type: AccessorType;
|
||||
}
|
||||
|
||||
export enum ComponentType {
|
||||
Byte = 5120,
|
||||
UnsignedByte = 5121,
|
||||
Short = 5122,
|
||||
UnsignedShort = 5123,
|
||||
UnsignedInt = 5125,
|
||||
Float = 5126,
|
||||
}
|
||||
|
||||
export type AccessorType =
|
||||
| "SCALAR"
|
||||
| "VEC2"
|
||||
| "VEC3"
|
||||
| "VEC4"
|
||||
| "MAT2"
|
||||
| "MAT3"
|
||||
| "MAT4"
|
||||
;
|
||||
|
||||
export interface Asset {
|
||||
version: `${string}.${string}`;
|
||||
minVersion: `${string}.${string}`;
|
||||
}
|
||||
|
||||
export interface Buffer {
|
||||
uri?: string;
|
||||
byteLength: number;
|
||||
}
|
||||
|
||||
export interface BufferView {
|
||||
buffer: number;
|
||||
/** @default 0 */
|
||||
byteOffset?: number;
|
||||
byteLength: number;
|
||||
byteStride?: number;
|
||||
}
|
||||
|
||||
export type Camera =
|
||||
| CameraOrthographic
|
||||
| CameraPerspective
|
||||
;
|
||||
|
||||
export interface CameraOrthographic {
|
||||
orthographic: Orthographic;
|
||||
type: "orthographic";
|
||||
}
|
||||
|
||||
export interface CameraPerspective {
|
||||
orthographic: Perspective;
|
||||
type: "perspective";
|
||||
}
|
||||
|
||||
export interface Orthographic {
|
||||
xmag: number;
|
||||
ymag: number;
|
||||
zfar: number;
|
||||
znear: number;
|
||||
}
|
||||
|
||||
export interface Perspective {
|
||||
aspectRatio?: number;
|
||||
yfov: number;
|
||||
zfar?: number;
|
||||
znear: number;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
uri?: string;
|
||||
mimeType?: ImageMimeType;
|
||||
bufferView?: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type ImageMimeType =
|
||||
| "image/jpeg"
|
||||
| "image/png"
|
||||
;
|
||||
|
||||
export interface Material {
|
||||
name?: string;
|
||||
extensions?: MaterialExtensions;
|
||||
pbrMetallicRoughness?: MaterialPbrMetallicRoughness;
|
||||
normalTexture?: NormalTextureInfo;
|
||||
occlusionTexture?: OcclusionTextureInfo;
|
||||
emissiveTexture?: TextureInfo;
|
||||
emissiveFactor?: [r: number, g: number, b: number];
|
||||
alphaMode?: AlphaMode;
|
||||
/** @default false */
|
||||
doubleSided?: boolean;
|
||||
}
|
||||
|
||||
export interface MaterialExtensions {
|
||||
KHR_materials_emissive_strength?: KHR_materials_emissive_strength;
|
||||
KHR_materials_ior?: KHR_materials_ior;
|
||||
}
|
||||
|
||||
export interface KHR_materials_emissive_strength {
|
||||
/** @default 1 */
|
||||
emissiveStrength?: number;
|
||||
}
|
||||
|
||||
export interface KHR_materials_ior {
|
||||
/** @default 1.5 */
|
||||
ior?: number;
|
||||
}
|
||||
|
||||
export interface MaterialPbrMetallicRoughness {
|
||||
/** @default [1, 1, 1, 1] */
|
||||
baseColorFactor?: [r: number, b: number, g: number, partialCoverage: number];
|
||||
baseColorTexture?: TextureInfo;
|
||||
/** @default 1 */
|
||||
metallicFactor?: number;
|
||||
/** @default 1 */
|
||||
roughnessFactor?: number;
|
||||
metallicRoughnessTexture?: TextureInfo;
|
||||
}
|
||||
|
||||
export interface TextureInfo {
|
||||
index: number;
|
||||
/** @default 0 */
|
||||
texCoord?: number;
|
||||
}
|
||||
|
||||
export interface NormalTextureInfo extends TextureInfo {
|
||||
/** @default 1 */
|
||||
scale?: number;
|
||||
}
|
||||
|
||||
export interface OcclusionTextureInfo extends TextureInfo {
|
||||
/** @default 1 */
|
||||
strength?: number;
|
||||
}
|
||||
|
||||
export type AlphaMode =
|
||||
| "OPAQUE"
|
||||
| "MASK"
|
||||
| "BLEND"
|
||||
;
|
||||
|
||||
export interface Mesh {
|
||||
primitives: [Primitive, ...Primitive[]];
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface Primitive {
|
||||
attributes: {
|
||||
POSITION?: number,
|
||||
NORMAL?: number,
|
||||
TANGENT?: number,
|
||||
TEXCOORD_0?: number,
|
||||
TEXCOORD_1?: number,
|
||||
};
|
||||
indices?: number;
|
||||
material?: number;
|
||||
/** @default PrimitiveMode.Triangles */
|
||||
mode?: PrimitiveMode;
|
||||
}
|
||||
|
||||
export enum PrimitiveMode {
|
||||
Points = 0,
|
||||
Lines = 1,
|
||||
LineLoop = 2,
|
||||
LineStrip = 3,
|
||||
Triangles = 4,
|
||||
TriangleStrip = 5,
|
||||
TriangleFan = 6,
|
||||
}
|
||||
|
||||
export interface Sampler {
|
||||
magFilter?: MagFilter;
|
||||
minFilter?: MinFilter;
|
||||
/** @default WrappingMode.Repeat */
|
||||
wrapS?: WrappingMode;
|
||||
/** @default WrappingMode.Repeat */
|
||||
wrapT?: WrappingMode;
|
||||
}
|
||||
|
||||
export enum MagFilter {
|
||||
Nearest = 9728,
|
||||
Linear = 9729,
|
||||
}
|
||||
|
||||
export enum MinFilter {
|
||||
Nearest = 9728,
|
||||
Linear = 9729,
|
||||
NearestMipmapNearest = 9984,
|
||||
LinearMipmapNearest = 9985,
|
||||
NearestMipmapLinear = 9986,
|
||||
LinearMipmapLinear = 9987,
|
||||
}
|
||||
|
||||
export enum WrappingMode {
|
||||
ClampToEdge = 33071,
|
||||
MirroredRepeat = 33648,
|
||||
Repeat = 10497,
|
||||
}
|
||||
|
||||
export interface Texture {
|
||||
sampler?: number;
|
||||
source?: number;
|
||||
name?: string;
|
||||
}
|
Loading…
Reference in New Issue
Block a user