Add local glTF viewer and visual coverage
Amp-Thread-ID: https://ampcode.com/threads/T-01a02d1c-27fc-7761-9f51-ed97b2dd4e3e Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { Mesh } from "../Mesh";
|
||||
import type { Scene } from "../Scene";
|
||||
import { DirectionalLight } from "../lights/DirectionalLight";
|
||||
import { PointLight } from "../lights/PointLight";
|
||||
import { SpotLight } from "../lights/SpotLight";
|
||||
import { PBRMaterial } from "../materials/PBRMaterial";
|
||||
import { Texture } from "../materials/Texture";
|
||||
|
||||
@@ -49,12 +52,20 @@ export async function importGltf(scene: Scene, url: string | URL) {
|
||||
for (const material of result.materials) {
|
||||
if (material.baseColorTexture >= 0) srgb.add(material.baseColorTexture);
|
||||
if (material.emissiveTexture >= 0) srgb.add(material.emissiveTexture);
|
||||
if (material.flags & 16 && material.metallicRoughnessTexture >= 0)
|
||||
srgb.add(material.metallicRoughnessTexture);
|
||||
if (material.specularColorTexture >= 0)
|
||||
srgb.add(material.specularColorTexture);
|
||||
}
|
||||
textures = result.textures.map(
|
||||
({ image }: { image: ImageBitmap }, index: number) =>
|
||||
(
|
||||
{ image, sampler }: { image: ImageBitmap; sampler: any },
|
||||
index: number,
|
||||
) =>
|
||||
new Texture(scene, {
|
||||
source: image,
|
||||
format: srgb.has(index) ? "rgba8unorm-srgb" : "rgba8unorm",
|
||||
sampler,
|
||||
}),
|
||||
);
|
||||
await Promise.all(textures.map((texture: Texture) => texture.ready));
|
||||
@@ -63,10 +74,19 @@ export async function importGltf(scene: Scene, url: string | URL) {
|
||||
new PBRMaterial(scene, {
|
||||
...options,
|
||||
baseColorTexture: textures[options.baseColorTexture],
|
||||
metallicRoughnessTexture:
|
||||
textures[options.metallicRoughnessTexture],
|
||||
metallicRoughnessTexture: textures[options.metallicRoughnessTexture],
|
||||
normalTexture: textures[options.normalTexture],
|
||||
emissiveTexture: textures[options.emissiveTexture],
|
||||
occlusionTexture: textures[options.occlusionTexture],
|
||||
transmissionTexture: textures[options.transmissionTexture],
|
||||
thicknessTexture: textures[options.thicknessTexture],
|
||||
clearcoatTexture: textures[options.clearcoatTexture],
|
||||
iridescenceTexture: textures[options.iridescenceTexture],
|
||||
iridescenceThicknessTexture:
|
||||
textures[options.iridescenceThicknessTexture],
|
||||
anisotropyTexture: textures[options.anisotropyTexture],
|
||||
specularTexture: textures[options.specularTexture],
|
||||
specularColorTexture: textures[options.specularColorTexture],
|
||||
}),
|
||||
);
|
||||
await Promise.all(materials.map((material: PBRMaterial) => material.ready));
|
||||
@@ -83,12 +103,20 @@ export async function importGltf(scene: Scene, url: string | URL) {
|
||||
...(primitive.normals ? { normals: primitive.normals } : {}),
|
||||
...(primitive.tangents ? { tangents: primitive.tangents } : {}),
|
||||
...(primitive.uvs ? { uvs: primitive.uvs } : {}),
|
||||
...(primitive.uvs2 ? { uvs2: primitive.uvs2 } : {}),
|
||||
...(primitive.colors ? { colors: primitive.colors } : {}),
|
||||
},
|
||||
}),
|
||||
);
|
||||
await Promise.all(meshes.map((mesh) => mesh.ready));
|
||||
return meshes;
|
||||
const lights = (result.lights ?? []).map((options: any) => {
|
||||
if (options.type === "directional")
|
||||
return new DirectionalLight(scene, options);
|
||||
if (options.type === "spot") return new SpotLight(scene, options);
|
||||
return new PointLight(scene, options);
|
||||
});
|
||||
await Promise.all(lights.map((light: any) => light.ready));
|
||||
return Object.assign(meshes, { camera: result.camera });
|
||||
});
|
||||
await Promise.all(
|
||||
result.textures.map((_: unknown, index: number) =>
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
export {};
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const widths: Record<string, number> = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4 };
|
||||
const widths: Record<string, number> = {
|
||||
SCALAR: 1,
|
||||
VEC2: 2,
|
||||
VEC3: 3,
|
||||
VEC4: 4,
|
||||
MAT2: 4,
|
||||
MAT3: 9,
|
||||
MAT4: 16,
|
||||
};
|
||||
const sizes: Record<number, number> = {
|
||||
5120: 1,
|
||||
5121: 1,
|
||||
@@ -29,79 +37,165 @@ function component(view: DataView, offset: number, type: number) {
|
||||
throw new Error("GLTF_COMPONENT");
|
||||
}
|
||||
|
||||
function rotate(rotor: number[], value: number[]) {
|
||||
const [rx, ry, rz, rw] = rotor;
|
||||
const [x, y, z] = value;
|
||||
const tx = 2 * (ry * z - rz * y);
|
||||
const ty = 2 * (rz * x - rx * z);
|
||||
const tz = 2 * (rx * y - ry * x);
|
||||
return [
|
||||
x + rw * tx + ry * tz - rz * ty,
|
||||
y + rw * ty + rz * tx - rx * tz,
|
||||
z + rw * tz + rx * ty - ry * tx,
|
||||
];
|
||||
}
|
||||
const identity = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
||||
|
||||
function multiply(a: number[], b: number[]) {
|
||||
return [
|
||||
a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1],
|
||||
a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0],
|
||||
a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3],
|
||||
a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2],
|
||||
];
|
||||
}
|
||||
|
||||
function decompose(matrix: number[]) {
|
||||
const scale = [
|
||||
Math.hypot(matrix[0], matrix[1], matrix[2]),
|
||||
Math.hypot(matrix[4], matrix[5], matrix[6]),
|
||||
Math.hypot(matrix[8], matrix[9], matrix[10]),
|
||||
];
|
||||
const [sx, sy, sz] = scale;
|
||||
const [m00, m01, m02] = [matrix[0] / sx, matrix[4] / sy, matrix[8] / sz];
|
||||
const [m10, m11, m12] = [matrix[1] / sx, matrix[5] / sy, matrix[9] / sz];
|
||||
const [m20, m21, m22] = [matrix[2] / sx, matrix[6] / sy, matrix[10] / sz];
|
||||
let rotor: number[];
|
||||
if (m00 + m11 + m22 > 0) {
|
||||
const s = Math.sqrt(1 + m00 + m11 + m22) * 2;
|
||||
rotor = [(m21 - m12) / s, (m02 - m20) / s, (m10 - m01) / s, s / 4];
|
||||
} else if (m00 > m11 && m00 > m22) {
|
||||
const s = Math.sqrt(1 + m00 - m11 - m22) * 2;
|
||||
rotor = [s / 4, (m01 + m10) / s, (m02 + m20) / s, (m21 - m12) / s];
|
||||
} else if (m11 > m22) {
|
||||
const s = Math.sqrt(1 + m11 - m00 - m22) * 2;
|
||||
rotor = [(m01 + m10) / s, s / 4, (m12 + m21) / s, (m02 - m20) / s];
|
||||
} else {
|
||||
const s = Math.sqrt(1 + m22 - m00 - m11) * 2;
|
||||
rotor = [(m02 + m20) / s, (m12 + m21) / s, s / 4, (m10 - m01) / s];
|
||||
}
|
||||
return { position: matrix.slice(12, 15), rotor, scale };
|
||||
function multiply(a: ArrayLike<number>, b: ArrayLike<number>) {
|
||||
const result = new Array<number>(16);
|
||||
for (let column = 0; column < 4; column++)
|
||||
for (let row = 0; row < 4; row++) {
|
||||
let value = 0;
|
||||
for (let lane = 0; lane < 4; lane++)
|
||||
value += a[lane * 4 + row] * b[column * 4 + lane];
|
||||
result[column * 4 + row] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function transform(node: any) {
|
||||
return node.matrix
|
||||
? decompose(node.matrix)
|
||||
: {
|
||||
position: node.translation ?? [0, 0, 0],
|
||||
rotor: node.rotation ?? [0, 0, 0, 1],
|
||||
scale: node.scale ?? [1, 1, 1],
|
||||
};
|
||||
if (node.matrix) return Array.from(node.matrix);
|
||||
const [x, y, z, w] = node.rotation ?? [0, 0, 0, 1];
|
||||
const [sx, sy, sz] = node.scale ?? [1, 1, 1];
|
||||
const [tx, ty, tz] = node.translation ?? [0, 0, 0];
|
||||
const xx = x * x;
|
||||
const yy = y * y;
|
||||
const zz = z * z;
|
||||
const xy = x * y;
|
||||
const xz = x * z;
|
||||
const yz = y * z;
|
||||
const wx = w * x;
|
||||
const wy = w * y;
|
||||
const wz = w * z;
|
||||
return [
|
||||
(1 - 2 * (yy + zz)) * sx,
|
||||
2 * (xy + wz) * sx,
|
||||
2 * (xz - wy) * sx,
|
||||
0,
|
||||
2 * (xy - wz) * sy,
|
||||
(1 - 2 * (xx + zz)) * sy,
|
||||
2 * (yz + wx) * sy,
|
||||
0,
|
||||
2 * (xz + wy) * sz,
|
||||
2 * (yz - wx) * sz,
|
||||
(1 - 2 * (xx + yy)) * sz,
|
||||
0,
|
||||
tx,
|
||||
ty,
|
||||
tz,
|
||||
1,
|
||||
];
|
||||
}
|
||||
|
||||
function compose(parent: any, local: any) {
|
||||
const position = rotate(
|
||||
parent.rotor,
|
||||
local.position.map(
|
||||
(value: number, lane: number) => value * parent.scale[lane],
|
||||
),
|
||||
function determinant(matrix: ArrayLike<number>) {
|
||||
return (
|
||||
matrix[0] * (matrix[5] * matrix[10] - matrix[6] * matrix[9]) -
|
||||
matrix[4] * (matrix[1] * matrix[10] - matrix[2] * matrix[9]) +
|
||||
matrix[8] * (matrix[1] * matrix[6] - matrix[2] * matrix[5])
|
||||
);
|
||||
return {
|
||||
position: position.map((value, lane) => value + parent.position[lane]),
|
||||
rotor: multiply(parent.rotor, local.rotor),
|
||||
scale: local.scale.map(
|
||||
(value: number, lane: number) => value * parent.scale[lane],
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function point(matrix: ArrayLike<number>, source: ArrayLike<number>) {
|
||||
return [
|
||||
matrix[0] * source[0] +
|
||||
matrix[4] * source[1] +
|
||||
matrix[8] * source[2] +
|
||||
matrix[12],
|
||||
matrix[1] * source[0] +
|
||||
matrix[5] * source[1] +
|
||||
matrix[9] * source[2] +
|
||||
matrix[13],
|
||||
matrix[2] * source[0] +
|
||||
matrix[6] * source[1] +
|
||||
matrix[10] * source[2] +
|
||||
matrix[14],
|
||||
];
|
||||
}
|
||||
|
||||
function vector(matrix: ArrayLike<number>, source: ArrayLike<number>) {
|
||||
const x =
|
||||
matrix[0] * source[0] + matrix[4] * source[1] + matrix[8] * source[2];
|
||||
const y =
|
||||
matrix[1] * source[0] + matrix[5] * source[1] + matrix[9] * source[2];
|
||||
const z =
|
||||
matrix[2] * source[0] + matrix[6] * source[1] + matrix[10] * source[2];
|
||||
const length = Math.hypot(x, y, z) || 1;
|
||||
return [x / length, y / length, z / length];
|
||||
}
|
||||
|
||||
function direction(matrix: ArrayLike<number>, source: ArrayLike<number>) {
|
||||
const a = matrix[0];
|
||||
const b = matrix[4];
|
||||
const c = matrix[8];
|
||||
const d = matrix[1];
|
||||
const e = matrix[5];
|
||||
const f = matrix[9];
|
||||
const g = matrix[2];
|
||||
const h = matrix[6];
|
||||
const i = matrix[10];
|
||||
const inverse = 1 / determinant(matrix);
|
||||
const x =
|
||||
((e * i - f * h) * source[0] +
|
||||
(f * g - d * i) * source[1] +
|
||||
(d * h - e * g) * source[2]) *
|
||||
inverse;
|
||||
const y =
|
||||
((c * h - b * i) * source[0] +
|
||||
(a * i - c * g) * source[1] +
|
||||
(b * g - a * h) * source[2]) *
|
||||
inverse;
|
||||
const z =
|
||||
((b * f - c * e) * source[0] +
|
||||
(c * d - a * f) * source[1] +
|
||||
(a * e - b * d) * source[2]) *
|
||||
inverse;
|
||||
const length = Math.hypot(x, y, z) || 1;
|
||||
return [x / length, y / length, z / length];
|
||||
}
|
||||
|
||||
function rotor(matrix: ArrayLike<number>) {
|
||||
const sx = Math.hypot(matrix[0], matrix[1], matrix[2]) || 1;
|
||||
const sy = Math.hypot(matrix[4], matrix[5], matrix[6]) || 1;
|
||||
const sz = Math.hypot(matrix[8], matrix[9], matrix[10]) || 1;
|
||||
const m00 = matrix[0] / sx;
|
||||
const m01 = matrix[4] / sy;
|
||||
const m02 = matrix[8] / sz;
|
||||
const m10 = matrix[1] / sx;
|
||||
const m11 = matrix[5] / sy;
|
||||
const m12 = matrix[9] / sz;
|
||||
const m20 = matrix[2] / sx;
|
||||
const m21 = matrix[6] / sy;
|
||||
const m22 = matrix[10] / sz;
|
||||
const trace = m00 + m11 + m22;
|
||||
let x: number;
|
||||
let y: number;
|
||||
let z: number;
|
||||
let w: number;
|
||||
if (trace > 0) {
|
||||
const scale = Math.sqrt(trace + 1) * 2;
|
||||
x = (m21 - m12) / scale;
|
||||
y = (m02 - m20) / scale;
|
||||
z = (m10 - m01) / scale;
|
||||
w = scale / 4;
|
||||
} else if (m00 > m11 && m00 > m22) {
|
||||
const scale = Math.sqrt(1 + m00 - m11 - m22) * 2;
|
||||
x = scale / 4;
|
||||
y = (m01 + m10) / scale;
|
||||
z = (m02 + m20) / scale;
|
||||
w = (m21 - m12) / scale;
|
||||
} else if (m11 > m22) {
|
||||
const scale = Math.sqrt(1 + m11 - m00 - m22) * 2;
|
||||
x = (m01 + m10) / scale;
|
||||
y = scale / 4;
|
||||
z = (m12 + m21) / scale;
|
||||
w = (m02 - m20) / scale;
|
||||
} else {
|
||||
const scale = Math.sqrt(1 + m22 - m00 - m11) * 2;
|
||||
x = (m02 + m20) / scale;
|
||||
y = (m12 + m21) / scale;
|
||||
z = scale / 4;
|
||||
w = (m10 - m01) / scale;
|
||||
}
|
||||
const length = Math.hypot(x, y, z, w) || 1;
|
||||
return [x / length, y / length, z / length, w / length];
|
||||
}
|
||||
|
||||
function parse(bytes: Uint8Array) {
|
||||
@@ -127,7 +221,127 @@ function parse(bytes: Uint8Array) {
|
||||
return { document, binary };
|
||||
}
|
||||
|
||||
async function load(url: string) {
|
||||
function computeNormals(positions: Float32Array, indices: Uint32Array) {
|
||||
const normals = new Float32Array(positions.length);
|
||||
for (let face = 0; face + 2 < indices.length; face += 3) {
|
||||
const a = indices[face] * 3;
|
||||
const b = indices[face + 1] * 3;
|
||||
const c = indices[face + 2] * 3;
|
||||
const ab = [
|
||||
positions[b] - positions[a],
|
||||
positions[b + 1] - positions[a + 1],
|
||||
positions[b + 2] - positions[a + 2],
|
||||
];
|
||||
const ac = [
|
||||
positions[c] - positions[a],
|
||||
positions[c + 1] - positions[a + 1],
|
||||
positions[c + 2] - positions[a + 2],
|
||||
];
|
||||
const cross = [
|
||||
ab[1] * ac[2] - ab[2] * ac[1],
|
||||
ab[2] * ac[0] - ab[0] * ac[2],
|
||||
ab[0] * ac[1] - ab[1] * ac[0],
|
||||
];
|
||||
for (const vertex of [a, b, c])
|
||||
for (let lane = 0; lane < 3; lane++)
|
||||
normals[vertex + lane] += cross[lane];
|
||||
}
|
||||
for (let vertex = 0; vertex < normals.length; vertex += 3) {
|
||||
const length =
|
||||
Math.hypot(normals[vertex], normals[vertex + 1], normals[vertex + 2]) ||
|
||||
1;
|
||||
for (let lane = 0; lane < 3; lane++) normals[vertex + lane] /= length;
|
||||
}
|
||||
return normals;
|
||||
}
|
||||
|
||||
function computeTangents(
|
||||
positions: Float32Array,
|
||||
normals: Float32Array,
|
||||
uvs: Float32Array,
|
||||
indices: Uint32Array,
|
||||
) {
|
||||
const first = new Float32Array(positions.length);
|
||||
const second = new Float32Array(positions.length);
|
||||
for (let face = 0; face + 2 < indices.length; face += 3) {
|
||||
const ia = indices[face];
|
||||
const ib = indices[face + 1];
|
||||
const ic = indices[face + 2];
|
||||
const a = ia * 3;
|
||||
const b = ib * 3;
|
||||
const c = ic * 3;
|
||||
const au = ia * 2;
|
||||
const bu = ib * 2;
|
||||
const cu = ic * 2;
|
||||
const x1 = positions[b] - positions[a];
|
||||
const x2 = positions[c] - positions[a];
|
||||
const y1 = positions[b + 1] - positions[a + 1];
|
||||
const y2 = positions[c + 1] - positions[a + 1];
|
||||
const z1 = positions[b + 2] - positions[a + 2];
|
||||
const z2 = positions[c + 2] - positions[a + 2];
|
||||
const s1 = uvs[bu] - uvs[au];
|
||||
const s2 = uvs[cu] - uvs[au];
|
||||
const t1 = uvs[bu + 1] - uvs[au + 1];
|
||||
const t2 = uvs[cu + 1] - uvs[au + 1];
|
||||
const divisor = s1 * t2 - s2 * t1;
|
||||
if (Math.abs(divisor) < 1e-10) continue;
|
||||
const r = 1 / divisor;
|
||||
const tangent = [
|
||||
(t2 * x1 - t1 * x2) * r,
|
||||
(t2 * y1 - t1 * y2) * r,
|
||||
(t2 * z1 - t1 * z2) * r,
|
||||
];
|
||||
const bitangent = [
|
||||
(s1 * x2 - s2 * x1) * r,
|
||||
(s1 * y2 - s2 * y1) * r,
|
||||
(s1 * z2 - s2 * z1) * r,
|
||||
];
|
||||
for (const vertex of [a, b, c])
|
||||
for (let lane = 0; lane < 3; lane++) {
|
||||
first[vertex + lane] += tangent[lane];
|
||||
second[vertex + lane] += bitangent[lane];
|
||||
}
|
||||
}
|
||||
const tangents = new Float32Array((positions.length / 3) * 4);
|
||||
for (let vertex = 0; vertex < positions.length / 3; vertex++) {
|
||||
const at = vertex * 3;
|
||||
const output = vertex * 4;
|
||||
const normal = normals.subarray(at, at + 3);
|
||||
const source = first.subarray(at, at + 3);
|
||||
const projection =
|
||||
normal[0] * source[0] + normal[1] * source[1] + normal[2] * source[2];
|
||||
let x = source[0] - normal[0] * projection;
|
||||
let y = source[1] - normal[1] * projection;
|
||||
let z = source[2] - normal[2] * projection;
|
||||
const length = Math.hypot(x, y, z) || 1;
|
||||
x /= length;
|
||||
y /= length;
|
||||
z /= length;
|
||||
tangents[output] = x;
|
||||
tangents[output + 1] = y;
|
||||
tangents[output + 2] = z;
|
||||
tangents[output + 3] =
|
||||
normal[0] * (y * second[at + 2] - z * second[at + 1]) +
|
||||
normal[1] * (z * second[at] - x * second[at + 2]) +
|
||||
normal[2] * (x * second[at + 1] - y * second[at]) <
|
||||
0
|
||||
? -1
|
||||
: 1;
|
||||
}
|
||||
return tangents;
|
||||
}
|
||||
|
||||
function color4(values: Float32Array, width: number) {
|
||||
if (width === 4) return values;
|
||||
const result = new Float32Array((values.length / width) * 4);
|
||||
for (let vertex = 0; vertex < values.length / width; vertex++) {
|
||||
result.set(values.subarray(vertex * width, vertex * width + 3), vertex * 4);
|
||||
result[vertex * 4 + 3] = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function loadGltf(url: string) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP_${response.status}`);
|
||||
const { document, binary } = parse(
|
||||
@@ -159,152 +373,522 @@ async function load(url: string) {
|
||||
return result.blob();
|
||||
}),
|
||||
);
|
||||
const samplerDescriptor = (id: number | undefined) => {
|
||||
const sampler = id === undefined ? {} : (document.samplers?.[id] ?? {});
|
||||
const addressMode = (mode: number | undefined) =>
|
||||
mode === 33071
|
||||
? "clamp-to-edge"
|
||||
: mode === 33648
|
||||
? "mirror-repeat"
|
||||
: "repeat";
|
||||
return {
|
||||
magFilter: sampler.magFilter === 9728 ? "nearest" : "linear",
|
||||
minFilter:
|
||||
sampler.minFilter === 9728 ||
|
||||
sampler.minFilter === 9984 ||
|
||||
sampler.minFilter === 9986
|
||||
? "nearest"
|
||||
: "linear",
|
||||
mipmapFilter:
|
||||
sampler.minFilter === 9984 || sampler.minFilter === 9985
|
||||
? "nearest"
|
||||
: "linear",
|
||||
addressModeU: addressMode(sampler.wrapS),
|
||||
addressModeV: addressMode(sampler.wrapT),
|
||||
};
|
||||
};
|
||||
const textures = await Promise.all(
|
||||
(document.textures ?? []).map(async (texture: any) => ({
|
||||
image: await createImageBitmap(imageBlobs[texture.source]),
|
||||
sampler: samplerDescriptor(texture.sampler),
|
||||
})),
|
||||
);
|
||||
|
||||
const normalized = (value: number, type: number) => {
|
||||
if (type === 5120) return Math.max(value / 127, -1);
|
||||
if (type === 5121) return value / 255;
|
||||
if (type === 5122) return Math.max(value / 32_767, -1);
|
||||
if (type === 5123) return value / 65_535;
|
||||
return value;
|
||||
};
|
||||
const accessor = (id: number, integer = false) => {
|
||||
const source = document.accessors[id];
|
||||
const width = widths[source.type];
|
||||
const size = sizes[source.componentType];
|
||||
const bufferView = document.bufferViews[source.bufferView];
|
||||
const bytes = buffers[bufferView.buffer];
|
||||
const start = (bufferView.byteOffset ?? 0) + (source.byteOffset ?? 0);
|
||||
const stride = bufferView.byteStride ?? width * size;
|
||||
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
||||
const values = integer
|
||||
? new Uint32Array(source.count * width)
|
||||
: new Float32Array(source.count * width);
|
||||
if (
|
||||
!source.normalized &&
|
||||
stride === width * size &&
|
||||
(bytes.byteOffset + start) % size === 0
|
||||
) {
|
||||
const packed = new arrays[source.componentType](
|
||||
if (source.bufferView !== undefined) {
|
||||
const bufferView = document.bufferViews[source.bufferView];
|
||||
const bytes = buffers[bufferView.buffer];
|
||||
const start = (bufferView.byteOffset ?? 0) + (source.byteOffset ?? 0);
|
||||
const stride = bufferView.byteStride ?? width * size;
|
||||
const view = new DataView(
|
||||
bytes.buffer,
|
||||
bytes.byteOffset + start,
|
||||
source.count * width,
|
||||
bytes.byteOffset,
|
||||
bytes.byteLength,
|
||||
);
|
||||
values.set(packed);
|
||||
return values;
|
||||
}
|
||||
for (let item = 0; item < source.count; item++)
|
||||
for (let lane = 0; lane < width; lane++) {
|
||||
let value = component(
|
||||
view,
|
||||
start + item * stride + lane * size,
|
||||
source.componentType,
|
||||
if (
|
||||
!source.sparse &&
|
||||
!source.normalized &&
|
||||
stride === width * size &&
|
||||
(bytes.byteOffset + start) % size === 0
|
||||
) {
|
||||
values.set(
|
||||
new arrays[source.componentType](
|
||||
bytes.buffer,
|
||||
bytes.byteOffset + start,
|
||||
source.count * width,
|
||||
),
|
||||
);
|
||||
if (!integer && source.normalized) {
|
||||
const maximum =
|
||||
source.componentType === 5121
|
||||
? 255
|
||||
: source.componentType === 5123
|
||||
? 65535
|
||||
: 1;
|
||||
value /= maximum;
|
||||
} else
|
||||
for (let item = 0; item < source.count; item++)
|
||||
for (let lane = 0; lane < width; lane++) {
|
||||
let value = component(
|
||||
view,
|
||||
start + item * stride + lane * size,
|
||||
source.componentType,
|
||||
);
|
||||
if (!integer && source.normalized)
|
||||
value = normalized(value, source.componentType);
|
||||
values[item * width + lane] = value;
|
||||
}
|
||||
}
|
||||
if (source.sparse) {
|
||||
const sparse = source.sparse;
|
||||
const indexView = document.bufferViews[sparse.indices.bufferView];
|
||||
const indexBytes = buffers[indexView.buffer];
|
||||
const indexStart =
|
||||
(indexView.byteOffset ?? 0) + (sparse.indices.byteOffset ?? 0);
|
||||
const indexData = new DataView(
|
||||
indexBytes.buffer,
|
||||
indexBytes.byteOffset,
|
||||
indexBytes.byteLength,
|
||||
);
|
||||
const indexSize = sizes[sparse.indices.componentType];
|
||||
const valueView = document.bufferViews[sparse.values.bufferView];
|
||||
const valueBytes = buffers[valueView.buffer];
|
||||
const valueStart =
|
||||
(valueView.byteOffset ?? 0) + (sparse.values.byteOffset ?? 0);
|
||||
const valueData = new DataView(
|
||||
valueBytes.buffer,
|
||||
valueBytes.byteOffset,
|
||||
valueBytes.byteLength,
|
||||
);
|
||||
for (let item = 0; item < sparse.count; item++) {
|
||||
const target = component(
|
||||
indexData,
|
||||
indexStart + item * indexSize,
|
||||
sparse.indices.componentType,
|
||||
);
|
||||
for (let lane = 0; lane < width; lane++) {
|
||||
let value = component(
|
||||
valueData,
|
||||
valueStart + (item * width + lane) * size,
|
||||
source.componentType,
|
||||
);
|
||||
if (!integer && source.normalized)
|
||||
value = normalized(value, source.componentType);
|
||||
values[target * width + lane] = value;
|
||||
}
|
||||
values[item * width + lane] = value;
|
||||
}
|
||||
}
|
||||
return values;
|
||||
};
|
||||
|
||||
const materials = (document.materials ?? []).map((material: any) => {
|
||||
const pbr = material.pbrMetallicRoughness ?? {};
|
||||
const textureInfo = (info: any) => {
|
||||
if (!info) return undefined;
|
||||
const extension = info.extensions?.KHR_texture_transform ?? {};
|
||||
return {
|
||||
baseColor: pbr.baseColorFactor ?? [1, 1, 1, 1],
|
||||
metallic: pbr.metallicFactor ?? 0,
|
||||
roughness: pbr.roughnessFactor ?? 0.7,
|
||||
texture: info.index,
|
||||
texCoord: extension.texCoord ?? info.texCoord ?? 0,
|
||||
offset: extension.offset ?? [0, 0],
|
||||
scale: extension.scale ?? [1, 1],
|
||||
rotation: extension.rotation ?? 0,
|
||||
};
|
||||
};
|
||||
const materials = (document.materials ?? []).map((material: any) => {
|
||||
const extensions = material.extensions ?? {};
|
||||
const specGloss = extensions.KHR_materials_pbrSpecularGlossiness;
|
||||
const pbr = material.pbrMetallicRoughness ?? {};
|
||||
const transmission = extensions.KHR_materials_transmission ?? {};
|
||||
const volume = extensions.KHR_materials_volume ?? {};
|
||||
const clearcoat = extensions.KHR_materials_clearcoat ?? {};
|
||||
const sheen = extensions.KHR_materials_sheen ?? {};
|
||||
const specular = extensions.KHR_materials_specular ?? {};
|
||||
const iridescence = extensions.KHR_materials_iridescence ?? {};
|
||||
const anisotropy = extensions.KHR_materials_anisotropy ?? {};
|
||||
const alphaMode =
|
||||
material.alphaMode === "MASK"
|
||||
? 1
|
||||
: material.alphaMode === "BLEND"
|
||||
? 2
|
||||
: 0;
|
||||
const flags =
|
||||
alphaMode |
|
||||
(material.doubleSided ? 4 : 0) |
|
||||
(extensions.KHR_materials_unlit ? 8 : 0) |
|
||||
(specGloss ? 16 : 0);
|
||||
const infos = [
|
||||
textureInfo(specGloss?.diffuseTexture ?? pbr.baseColorTexture),
|
||||
textureInfo(
|
||||
specGloss?.specularGlossinessTexture ?? pbr.metallicRoughnessTexture,
|
||||
),
|
||||
textureInfo(material.normalTexture),
|
||||
textureInfo(material.emissiveTexture),
|
||||
textureInfo(material.occlusionTexture),
|
||||
textureInfo(transmission.transmissionTexture),
|
||||
textureInfo(volume.thicknessTexture),
|
||||
textureInfo(clearcoat.clearcoatTexture),
|
||||
textureInfo(iridescence.iridescenceTexture),
|
||||
textureInfo(iridescence.iridescenceThicknessTexture),
|
||||
textureInfo(anisotropy.anisotropyTexture),
|
||||
textureInfo(specular.specularTexture),
|
||||
textureInfo(specular.specularColorTexture),
|
||||
];
|
||||
return {
|
||||
baseColor: specGloss?.diffuseFactor ??
|
||||
pbr.baseColorFactor ?? [1, 1, 1, 1],
|
||||
metallic: specGloss ? 0 : (pbr.metallicFactor ?? 1),
|
||||
roughness: specGloss
|
||||
? 1 - (specGloss.glossinessFactor ?? 1)
|
||||
: (pbr.roughnessFactor ?? 1),
|
||||
emissive: material.emissiveFactor ?? [0, 0, 0],
|
||||
normalScale: material.normalTexture?.scale ?? 1,
|
||||
alphaCutoff: material.alphaCutoff ?? 0.5,
|
||||
baseColorTexture: pbr.baseColorTexture?.index ?? -1,
|
||||
metallicRoughnessTexture: pbr.metallicRoughnessTexture?.index ?? -1,
|
||||
normalTexture: material.normalTexture?.index ?? -1,
|
||||
emissiveTexture: material.emissiveTexture?.index ?? -1,
|
||||
flags,
|
||||
occlusionStrength: material.occlusionTexture?.strength ?? 1,
|
||||
transmission: transmission.transmissionFactor ?? 0,
|
||||
ior: extensions.KHR_materials_ior?.ior ?? 1.5,
|
||||
thickness: volume.thicknessFactor ?? 0,
|
||||
attenuationDistance: volume.attenuationDistance ?? 0,
|
||||
attenuationColor: volume.attenuationColor ?? [1, 1, 1],
|
||||
emissiveStrength:
|
||||
extensions.KHR_materials_emissive_strength?.emissiveStrength ?? 1,
|
||||
clearcoat: clearcoat.clearcoatFactor ?? 0,
|
||||
clearcoatRoughness: clearcoat.clearcoatRoughnessFactor ?? 0,
|
||||
specular: specular.specularFactor ?? 1,
|
||||
sheenColor: sheen.sheenColorFactor ?? [0, 0, 0],
|
||||
sheenRoughness: sheen.sheenRoughnessFactor ?? 0,
|
||||
specularColor: specGloss?.specularFactor ?? [1, 1, 1],
|
||||
iridescence: iridescence.iridescenceFactor ?? 0,
|
||||
iridescenceIor: iridescence.iridescenceIor ?? 1.3,
|
||||
iridescenceThicknessMinimum:
|
||||
iridescence.iridescenceThicknessMinimum ?? 100,
|
||||
iridescenceThicknessMaximum:
|
||||
iridescence.iridescenceThicknessMaximum ?? 400,
|
||||
anisotropy: anisotropy.anisotropyStrength ?? 0,
|
||||
anisotropyRotation: anisotropy.anisotropyRotation ?? 0,
|
||||
specularExtensionColor: specular.specularColorFactor ?? [1, 1, 1],
|
||||
textureInfos: infos,
|
||||
baseColorTexture: infos[0]?.texture ?? -1,
|
||||
metallicRoughnessTexture: infos[1]?.texture ?? -1,
|
||||
normalTexture: infos[2]?.texture ?? -1,
|
||||
emissiveTexture: infos[3]?.texture ?? -1,
|
||||
occlusionTexture: infos[4]?.texture ?? -1,
|
||||
transmissionTexture: infos[5]?.texture ?? -1,
|
||||
thicknessTexture: infos[6]?.texture ?? -1,
|
||||
clearcoatTexture: infos[7]?.texture ?? -1,
|
||||
iridescenceTexture: infos[8]?.texture ?? -1,
|
||||
iridescenceThicknessTexture: infos[9]?.texture ?? -1,
|
||||
anisotropyTexture: infos[10]?.texture ?? -1,
|
||||
specularTexture: infos[11]?.texture ?? -1,
|
||||
specularColorTexture: infos[12]?.texture ?? -1,
|
||||
};
|
||||
});
|
||||
|
||||
const nodes = document.nodes ?? [];
|
||||
const parents = new Map<number, number>();
|
||||
for (let id = 0; id < nodes.length; id++)
|
||||
for (const child of nodes[id].children ?? []) parents.set(child, id);
|
||||
const worlds = new Map<number, number[]>();
|
||||
const world = (id: number): number[] => {
|
||||
const existing = worlds.get(id);
|
||||
if (existing) return existing;
|
||||
const local = transform(nodes[id] ?? {});
|
||||
const parent = parents.get(id);
|
||||
const result =
|
||||
parent === undefined ? local : multiply(world(parent), local);
|
||||
worlds.set(id, result);
|
||||
return result;
|
||||
};
|
||||
for (let id = 0; id < nodes.length; id++) world(id);
|
||||
|
||||
const primitives: any[] = [];
|
||||
const emitMesh = (meshId: number, transform: any) => {
|
||||
const emitMesh = (meshId: number, nodeId: number, matrix: number[]) => {
|
||||
const mesh = document.meshes?.[meshId];
|
||||
const node = nodes[nodeId] ?? {};
|
||||
const skin =
|
||||
node.skin === undefined ? undefined : document.skins?.[node.skin];
|
||||
const inverseBindMatrices =
|
||||
skin?.inverseBindMatrices === undefined
|
||||
? undefined
|
||||
: (accessor(skin.inverseBindMatrices) as Float32Array);
|
||||
const jointMatrices = skin?.joints.map((joint: number, index: number) =>
|
||||
multiply(
|
||||
world(joint),
|
||||
inverseBindMatrices?.subarray(index * 16, index * 16 + 16) ?? identity,
|
||||
),
|
||||
);
|
||||
for (const primitive of mesh?.primitives ?? []) {
|
||||
if (
|
||||
(primitive.mode ?? 4) !== 4 ||
|
||||
primitive.attributes.POSITION === undefined
|
||||
)
|
||||
continue;
|
||||
const positions = accessor(primitive.attributes.POSITION);
|
||||
const positions = accessor(primitive.attributes.POSITION) as Float32Array;
|
||||
let normals =
|
||||
primitive.attributes.NORMAL === undefined
|
||||
? undefined
|
||||
: (accessor(primitive.attributes.NORMAL) as Float32Array);
|
||||
let tangents =
|
||||
primitive.attributes.TANGENT === undefined
|
||||
? undefined
|
||||
: (accessor(primitive.attributes.TANGENT) as Float32Array);
|
||||
const uvs =
|
||||
primitive.attributes.TEXCOORD_0 === undefined
|
||||
? new Float32Array((positions.length / 3) * 2)
|
||||
: (accessor(primitive.attributes.TEXCOORD_0) as Float32Array);
|
||||
const uvs2 =
|
||||
primitive.attributes.TEXCOORD_1 === undefined
|
||||
? undefined
|
||||
: (accessor(primitive.attributes.TEXCOORD_1) as Float32Array);
|
||||
const indices =
|
||||
primitive.indices === undefined
|
||||
? Uint32Array.from(
|
||||
{ length: positions.length / 3 },
|
||||
(_, index) => index,
|
||||
)
|
||||
: accessor(primitive.indices, true);
|
||||
: (accessor(primitive.indices, true) as Uint32Array);
|
||||
const weights = node.weights ?? mesh.weights ?? [];
|
||||
for (
|
||||
let target = 0;
|
||||
target < (primitive.targets?.length ?? 0);
|
||||
target++
|
||||
) {
|
||||
const weight = weights[target] ?? 0;
|
||||
if (!weight) continue;
|
||||
for (const [semantic, values] of [
|
||||
["POSITION", positions],
|
||||
["NORMAL", normals],
|
||||
["TANGENT", tangents],
|
||||
] as const) {
|
||||
const targetAccessor = primitive.targets[target][semantic];
|
||||
if (targetAccessor === undefined || !values) continue;
|
||||
const delta = accessor(targetAccessor) as Float32Array;
|
||||
if (semantic === "TANGENT") {
|
||||
for (let vertex = 0; vertex < delta.length / 3; vertex++)
|
||||
for (let lane = 0; lane < 3; lane++)
|
||||
values[vertex * 4 + lane] += delta[vertex * 3 + lane] * weight;
|
||||
} else {
|
||||
for (let index = 0; index < delta.length; index++)
|
||||
values[index] += delta[index] * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const jointAccessor = primitive.attributes.JOINTS_0;
|
||||
const weightAccessor = primitive.attributes.WEIGHTS_0;
|
||||
if (
|
||||
jointMatrices &&
|
||||
jointAccessor !== undefined &&
|
||||
weightAccessor !== undefined
|
||||
) {
|
||||
const joints = accessor(jointAccessor, true) as Uint32Array;
|
||||
const weights = accessor(weightAccessor) as Float32Array;
|
||||
for (let vertex = 0; vertex < positions.length / 3; vertex++) {
|
||||
const position = [0, 0, 0];
|
||||
const normal = [0, 0, 0];
|
||||
const tangent = [0, 0, 0];
|
||||
let total = 0;
|
||||
for (let influence = 0; influence < 4; influence++) {
|
||||
const weight = weights[vertex * 4 + influence];
|
||||
const joint = jointMatrices[joints[vertex * 4 + influence]];
|
||||
if (!weight || !joint) continue;
|
||||
const skinnedPosition = point(
|
||||
joint,
|
||||
positions.subarray(vertex * 3, vertex * 3 + 3),
|
||||
);
|
||||
const skinnedNormal = normals
|
||||
? direction(joint, normals.subarray(vertex * 3, vertex * 3 + 3))
|
||||
: undefined;
|
||||
const skinnedTangent = tangents
|
||||
? vector(joint, tangents.subarray(vertex * 4, vertex * 4 + 3))
|
||||
: undefined;
|
||||
for (let lane = 0; lane < 3; lane++) {
|
||||
position[lane] += skinnedPosition[lane] * weight;
|
||||
if (skinnedNormal) normal[lane] += skinnedNormal[lane] * weight;
|
||||
if (skinnedTangent)
|
||||
tangent[lane] += skinnedTangent[lane] * weight;
|
||||
}
|
||||
total += weight;
|
||||
}
|
||||
if (!total) continue;
|
||||
positions.set(
|
||||
position.map((value) => value / total),
|
||||
vertex * 3,
|
||||
);
|
||||
if (normals) {
|
||||
const length = Math.hypot(...normal) || 1;
|
||||
normals.set(
|
||||
normal.map((value) => value / length),
|
||||
vertex * 3,
|
||||
);
|
||||
}
|
||||
if (tangents) {
|
||||
const length = Math.hypot(...tangent) || 1;
|
||||
tangents.set(
|
||||
tangent.map((value) => value / length),
|
||||
vertex * 4,
|
||||
);
|
||||
}
|
||||
}
|
||||
matrix = identity;
|
||||
}
|
||||
|
||||
const mirrored = determinant(matrix) < 0;
|
||||
for (let vertex = 0; vertex < positions.length; vertex += 3)
|
||||
positions.set(
|
||||
point(matrix, positions.subarray(vertex, vertex + 3)),
|
||||
vertex,
|
||||
);
|
||||
if (normals)
|
||||
for (let vertex = 0; vertex < normals.length; vertex += 3)
|
||||
normals.set(
|
||||
direction(matrix, normals.subarray(vertex, vertex + 3)),
|
||||
vertex,
|
||||
);
|
||||
if (tangents)
|
||||
for (let vertex = 0; vertex < tangents.length; vertex += 4) {
|
||||
tangents.set(
|
||||
vector(matrix, tangents.subarray(vertex, vertex + 3)),
|
||||
vertex,
|
||||
);
|
||||
if (mirrored) tangents[vertex + 3] *= -1;
|
||||
}
|
||||
if (mirrored)
|
||||
for (let face = 0; face + 2 < indices.length; face += 3)
|
||||
[indices[face + 1], indices[face + 2]] = [
|
||||
indices[face + 2],
|
||||
indices[face + 1],
|
||||
];
|
||||
normals ??= computeNormals(positions, indices);
|
||||
const material = materials[primitive.material ?? -1];
|
||||
if (!tangents && material?.normalTexture >= 0)
|
||||
tangents = computeTangents(positions, normals, uvs, indices);
|
||||
const colorAccessor = primitive.attributes.COLOR_0;
|
||||
primitives.push({
|
||||
positions,
|
||||
normals,
|
||||
tangents,
|
||||
uvs,
|
||||
uvs2,
|
||||
indices,
|
||||
...(primitive.attributes.NORMAL === undefined
|
||||
...(colorAccessor === undefined
|
||||
? {}
|
||||
: { normals: accessor(primitive.attributes.NORMAL) }),
|
||||
...(primitive.attributes.TANGENT === undefined
|
||||
? {}
|
||||
: { tangents: accessor(primitive.attributes.TANGENT) }),
|
||||
...(primitive.attributes.TEXCOORD_0 === undefined
|
||||
? {}
|
||||
: { uvs: accessor(primitive.attributes.TEXCOORD_0) }),
|
||||
...(primitive.attributes.COLOR_0 === undefined
|
||||
? {}
|
||||
: { colors: accessor(primitive.attributes.COLOR_0) }),
|
||||
: {
|
||||
colors: color4(
|
||||
accessor(colorAccessor) as Float32Array,
|
||||
widths[document.accessors[colorAccessor].type],
|
||||
),
|
||||
}),
|
||||
material: primitive.material ?? -1,
|
||||
...transform,
|
||||
position: [0, 0, 0],
|
||||
rotor: [0, 0, 0, 1],
|
||||
scale: [1, 1, 1],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const nodes = document.nodes ?? [];
|
||||
const scene = document.scenes?.[document.scene ?? 0];
|
||||
const children = new Set(nodes.flatMap((node: any) => node.children ?? []));
|
||||
const roots =
|
||||
scene?.nodes ??
|
||||
nodes
|
||||
.map((_: any, id: number) => id)
|
||||
.filter((id: number) => !children.has(id));
|
||||
const visit = (
|
||||
id: number,
|
||||
parent = {
|
||||
position: [0, 0, 0],
|
||||
rotor: [0, 0, 0, 1],
|
||||
scale: [1, 1, 1],
|
||||
},
|
||||
) => {
|
||||
const node = nodes[id] ?? {};
|
||||
const world = compose(parent, transform(node));
|
||||
if (node.mesh !== undefined) emitMesh(node.mesh, world);
|
||||
for (const child of node.children ?? []) visit(child, world);
|
||||
const emitNode = (id: number) => {
|
||||
const node = nodes[id];
|
||||
if (node.mesh === undefined) return;
|
||||
const instances = node.extensions?.EXT_mesh_gpu_instancing?.attributes;
|
||||
if (!instances) return emitMesh(node.mesh, id, world(id));
|
||||
const translations =
|
||||
instances.TRANSLATION === undefined
|
||||
? undefined
|
||||
: (accessor(instances.TRANSLATION) as Float32Array);
|
||||
const rotations =
|
||||
instances.ROTATION === undefined
|
||||
? undefined
|
||||
: (accessor(instances.ROTATION) as Float32Array);
|
||||
const scales =
|
||||
instances.SCALE === undefined
|
||||
? undefined
|
||||
: (accessor(instances.SCALE) as Float32Array);
|
||||
const count = translations
|
||||
? translations.length / 3
|
||||
: rotations
|
||||
? rotations.length / 4
|
||||
: scales
|
||||
? scales.length / 3
|
||||
: 0;
|
||||
for (let instance = 0; instance < count; instance++)
|
||||
emitMesh(
|
||||
node.mesh,
|
||||
id,
|
||||
multiply(
|
||||
world(id),
|
||||
transform({
|
||||
translation: translations?.subarray(instance * 3, instance * 3 + 3),
|
||||
rotation: rotations?.subarray(instance * 4, instance * 4 + 4),
|
||||
scale: scales?.subarray(instance * 3, instance * 3 + 3),
|
||||
}),
|
||||
),
|
||||
);
|
||||
};
|
||||
for (const root of roots) visit(root);
|
||||
for (let id = 0; id < nodes.length; id++) emitNode(id);
|
||||
if (!nodes.length)
|
||||
for (let id = 0; id < (document.meshes ?? []).length; id++)
|
||||
emitMesh(id, {});
|
||||
return { materials, primitives, textures };
|
||||
emitMesh(id, -1, identity);
|
||||
const lightDefinitions =
|
||||
document.extensions?.KHR_lights_punctual?.lights ?? [];
|
||||
const lights = nodes.flatMap((node: any, id: number) => {
|
||||
const lightId = node.extensions?.KHR_lights_punctual?.light;
|
||||
const light = lightId === undefined ? undefined : lightDefinitions[lightId];
|
||||
if (!light) return [];
|
||||
const matrix = world(id);
|
||||
return [
|
||||
{
|
||||
type: light.type,
|
||||
color: light.color ?? [1, 1, 1],
|
||||
intensity: light.intensity ?? 1,
|
||||
range: light.range ?? 1_000_000,
|
||||
innerAngle: light.spot?.innerConeAngle ?? 0,
|
||||
outerAngle: light.spot?.outerConeAngle ?? Math.PI / 4,
|
||||
position: [matrix[12], matrix[13], matrix[14]],
|
||||
rotor: rotor(matrix),
|
||||
},
|
||||
];
|
||||
});
|
||||
const cameraNode = nodes.findIndex((node: any) => node.camera !== undefined);
|
||||
const cameraDefinition =
|
||||
cameraNode < 0 ? undefined : document.cameras?.[nodes[cameraNode].camera];
|
||||
const camera = cameraDefinition
|
||||
? {
|
||||
direction: vector(world(cameraNode), [0, 0, 1]),
|
||||
fov: cameraDefinition.perspective?.yfov,
|
||||
}
|
||||
: undefined;
|
||||
return { camera, lights, materials, primitives, textures };
|
||||
}
|
||||
|
||||
addEventListener("message", async ({ data }) => {
|
||||
try {
|
||||
const result = await load(data.url);
|
||||
const transfers = result.primitives.flatMap((primitive: any) =>
|
||||
["positions", "indices", "normals", "tangents", "uvs", "colors"]
|
||||
.map((name) => primitive[name]?.buffer)
|
||||
.filter(Boolean),
|
||||
);
|
||||
transfers.push(...result.textures.map((texture: any) => texture.image));
|
||||
(postMessage as any)({ request: data.request, result }, transfers);
|
||||
} catch (error) {
|
||||
postMessage({
|
||||
request: data.request,
|
||||
error: error instanceof Error ? error.message : "GLTF_IMPORT",
|
||||
});
|
||||
}
|
||||
});
|
||||
if (typeof addEventListener === "function")
|
||||
addEventListener("message", async ({ data }) => {
|
||||
try {
|
||||
const result = await loadGltf(data.url);
|
||||
const transfers = result.primitives.flatMap((primitive: any) =>
|
||||
["positions", "indices", "normals", "tangents", "uvs", "uvs2", "colors"]
|
||||
.map((name) => primitive[name]?.buffer)
|
||||
.filter(Boolean),
|
||||
);
|
||||
transfers.push(...result.textures.map((texture: any) => texture.image));
|
||||
(postMessage as any)({ request: data.request, result }, transfers);
|
||||
} catch (error) {
|
||||
postMessage({
|
||||
request: data.request,
|
||||
error: error instanceof Error ? error.message : "GLTF_IMPORT",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user