gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
1,014 lines • 77.9 kB
JavaScript
import { throwWarning } from "../../utils/utils.mjs";
import { Vec3 } from "../../math/Vec3.mjs";
import { Mat4 } from "../../math/Mat4.mjs";
import { Object3D } from "../../core/objects3D/Object3D.mjs";
import { isCameraRenderer } from "../../core/renderers/utils.mjs";
import { Vec2 } from "../../math/Vec2.mjs";
import { BufferBinding } from "../../core/bindings/BufferBinding.mjs";
import { Mat3 } from "../../math/Mat3.mjs";
import { MediaTexture } from "../../core/textures/MediaTexture.mjs";
import { OrthographicCamera } from "../../core/cameras/OrthographicCamera.mjs";
import { PerspectiveCamera } from "../../core/cameras/PerspectiveCamera.mjs";
import { Sampler } from "../../core/samplers/Sampler.mjs";
import { Box3 } from "../../math/Box3.mjs";
import { vertexBufferAttributeLayouts } from "../../core/geometries/utils.mjs";
import { Geometry } from "../../core/geometries/Geometry.mjs";
import { IndexedGeometry } from "../../core/geometries/IndexedGeometry.mjs";
import { RenderMaterial } from "../../core/materials/RenderMaterial.mjs";
import { DirectionalLight } from "../../core/lights/DirectionalLight.mjs";
import { PointLight } from "../../core/lights/PointLight.mjs";
import { SpotLight } from "../../core/lights/SpotLight.mjs";
import { LitMesh } from "../meshes/LitMesh.mjs";
import { KeyframesAnimation } from "../animations/KeyframesAnimation.mjs";
import { TargetsAnimationsManager } from "../animations/TargetsAnimationsManager.mjs";
//#region src/extras/gltf/GLTFScenesManager.ts
const GL = typeof window !== "undefined" && WebGLRenderingContext || {
BYTE: 5120,
UNSIGNED_BYTE: 5121,
SHORT: 5122,
UNSIGNED_SHORT: 5123,
UNSIGNED_INT: 5125,
FLOAT: 5126,
TRIANGLES: 4,
TRIANGLE_STRIP: 5,
TRIANGLE_FAN: 6,
LINES: 1,
LINE_LOOP: 2,
LINE_STRIP: 3,
POINTS: 0,
CLAMP_TO_EDGE: 33071,
MIRRORED_REPEAT: 33648,
NEAREST: 9728,
LINEAR: 9729,
LINEAR_MIPMAP_NEAREST: 9985,
NEAREST_MIPMAP_LINEAR: 9986,
LINEAR_MIPMAP_LINEAR: 9987
};
/**
* Used to create a {@link GLTFScenesManager} from a given {@link GLTFLoader.gltf | gltf} object.
*
* Parse the {@link GLTFLoader.gltf | gltf} object, create all the {@link Sampler} and {@link Texture}, create all the {@link Object3D} nodes to compute the correct transformations and parent -> child relationships, create all the needed {@link MeshDescriptor} containing the {@link Geometry}, {@link LitMesh} parameters and so on.
*
* ## Loading Features
*
* - [x] Accessors
* - [x] Sparse accessors
* - [x] Buffers
* - [x] BufferViews
* - [x] Images
* - [x] Meshes
* - [x] Nodes
* - [x] Primitives
* - [x] Compute flat normals if normal attributes is missing
* - [x] Compute tangent space in fragment shader if tangent attributes is missing and a normal map is used (would be better/faster with [MikkTSpace](http://www.mikktspace.com/))
* - [x] Samplers
* - [x] Textures
* - [x] Animations
* - Paths
* - [x] Translation
* - [x] Rotation
* - [x] Scale
* - [x] Weights
* - Interpolation
* - [x] Step
* - [x] Linear
* - [x] CubicSpline
* - [x] Cameras
* - [x] OrthographicCamera
* - [x] PerspectiveCamera
* - [x] Materials
* - [x] Skins
* - [x] Morph targets
*
* ## Extensions
* - [x] KHR_animation_pointer (using GLTFPointerAnimationsManager extra class)
* - [ ] KHR_draco_mesh_compression
* - [x] KHR_lights_punctual
* - [x] KHR_materials_anisotropy
* - [x] KHR_materials_clearcoat
* - [x] KHR_materials_diffuse_transmission
* - [x] KHR_materials_dispersion
* - [x] KHR_materials_emissive_strength
* - [x] KHR_materials_ior
* - [x] KHR_materials_iridescence
* - [x] KHR_materials_sheen
* - [x] KHR_materials_specular
* - [x] KHR_materials_transmission
* - [x] KHR_materials_unlit
* - [x] KHR_materials_variants
* - [x] KHR_materials_volume
* - [x] KHR_mesh_quantization
* - [x] KHR_node_visibility
* - [ ] KHR_texture_basisu
* - [x] KHR_texture_transform
* - [ ] KHR_xmp_json_ld
* - [x] EXT_mesh_gpu_instancing
* - [ ] EXT_meshopt_compression
* - [x] EXT_texture_webp
*
* @example
* ```javascript
* const gltfLoader = new GLTFLoader()
* const gltf = await gltfLoader.loadFromUrl('path/to/model.gltf')
*
* // create a gltfScenesManager from the resulting 'gltf' object
* // assuming 'renderer' is a valid camera renderer or curtains instance
* const gltfScenesManager = new GLTFScenesManager({ renderer, gltf })
* gltfScenesManager.addMeshes()
* ```
*/
var GLTFScenesManager = class GLTFScenesManager {
/** The {@link PrimitiveInstances} Map, to group similar {@link LitMesh} by instances. */
#primitiveInstances;
/**
* {@link GLTFScenesManager} constructor.
* @param parameters - parameters used to create our {@link GLTFScenesManager}.
* @param parameters.renderer - our {@link CameraRenderer} class object.
* @param parameters.gltf - The {@link GLTFLoader.gltf | gltf} object used.
*/
constructor({ renderer, gltf }) {
renderer = isCameraRenderer(renderer, "GLTFScenesManager");
this.renderer = renderer;
this.gltf = gltf;
this.#primitiveInstances = /* @__PURE__ */ new Map();
this.pointerAnimationsManager = null;
this.scenesManager = {
node: new Object3D(),
nodes: /* @__PURE__ */ new Map(),
boundingBox: new Box3(),
samplers: [],
materialsTextures: [],
materialsParams: [],
scenes: [],
meshes: [],
meshesDescriptors: [],
animations: [],
cameras: [],
skins: [],
lights: []
};
this.createSamplers();
this.createMaterialTextures();
this.createMaterialsParams();
this.createLights();
this.createAnimations();
this.createScenes();
}
/**
* Get an attribute type, bufferFormat and size from its {@link GLTF.AccessorType | accessor type}.
* @param type - {@link GLTF.AccessorType | accessor type} to use.
* @returns - Corresponding type, bufferFormat and size.
*/
static getVertexAttributeParamsFromType(type) {
switch (type) {
case "VEC2": return {
type: "vec2f",
bufferFormat: "float32x2",
size: 2
};
case "VEC3": return {
type: "vec3f",
bufferFormat: "float32x3",
size: 3
};
case "VEC4": return {
type: "vec4f",
bufferFormat: "float32x4",
size: 4
};
case "MAT2": return {
type: "mat2x2f",
bufferFormat: "float32x2",
size: 6
};
case "MAT3": return {
type: "mat3x3f",
bufferFormat: "float32x3",
size: 9
};
case "MAT4": return {
type: "mat4x4f",
bufferFormat: "float32x4",
size: 16
};
default: return {
type: "f32",
bufferFormat: "float32",
size: 1
};
}
}
/**
* Get the corresponding typed array constructor based on the {@link GLTF.AccessorComponentType | accessor component type}.
* @param componentType - {@link GLTF.AccessorComponentType | accessor component type} to use.
* @returns - corresponding typed array constructor.
*/
static getTypedArrayConstructorFromComponentType(componentType) {
switch (componentType) {
case GL.BYTE: return Int8Array;
case GL.UNSIGNED_BYTE: return Uint8Array;
case GL.SHORT: return Int16Array;
case GL.UNSIGNED_SHORT: return Uint16Array;
case GL.UNSIGNED_INT: return Uint32Array;
case GL.FLOAT:
default: return Float32Array;
}
}
/**
* Get the {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createRenderPipeline#topology | GPUPrimitiveTopology} based on the {@link GLTF.MeshPrimitiveMode | WebGL primitive mode}.
* @param mode - {@link GLTF.MeshPrimitiveMode | WebGL primitive mode} to use.
* @returns - corresponding {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createRenderPipeline#topology | GPUPrimitiveTopology}.
*/
static gpuPrimitiveTopologyForMode(mode) {
switch (mode) {
case GL.TRIANGLE_STRIP:
case GL.TRIANGLE_FAN: return "triangle-strip";
case GL.LINES: return "line-list";
case GL.LINE_STRIP:
case GL.LINE_LOOP: return "line-strip";
case GL.POINTS: return "point-list";
case GL.TRIANGLES:
default: return "triangle-list";
}
}
/**
* Get the {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createSampler#addressmodeu | GPUAddressMode} based on the {@link GLTF.TextureWrapMode | WebGL texture wrap mode}.
* @param wrap - {@link GLTF.TextureWrapMode | WebGL texture wrap mode} to use.
* @returns - corresponding {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createSampler#addressmodeu | GPUAddressMode}.
*/
static gpuAddressModeForWrap(wrap) {
switch (wrap) {
case GL.CLAMP_TO_EDGE: return "clamp-to-edge";
case GL.MIRRORED_REPEAT: return "mirror-repeat";
default: return "repeat";
}
}
/**
* Create the {@link scenesManager} {@link TargetsAnimationsManager} if any animation is present in the {@link gltf}.
*/
createAnimations() {
this.gltf.animations?.forEach((animation, index) => {
this.scenesManager.animations.push(new TargetsAnimationsManager(this.renderer, { label: animation.name ?? "Animation " + index }));
});
}
/**
* Get a glTF animation keyframes and values {@link TypedArray} from the given {@link GLTF.IAnimationSampler | glTF animation sampler}.
* @param sampler - {@link GLTF.IAnimationSampler | glTF animation sampler} to retrieve from.
* @returns - Corresponding keyframes and values {@link TypedArray}.
*/
getAnimationKeyframesValues(sampler) {
const inputAccessor = this.gltf.accessors[sampler.input];
const keyframes = this.#getAccessorArray(inputAccessor);
const outputAccessor = this.gltf.accessors[sampler.output];
return {
keyframes,
values: this.#getAccessorArray(outputAccessor)
};
}
/**
* Create the {@link ScenesManager.lights | lights} defined by the `KHR_lights_punctual` extension if any.
*/
createLights() {
if (this.gltf.extensions && this.gltf.extensions["KHR_lights_punctual"]) {
let lightIndex = 0;
for (const light of this.gltf.extensions["KHR_lights_punctual"].lights) {
lightIndex++;
const label = light.name ?? `glTF ${light.type} light ${lightIndex}`;
if (light.type === "spot") {
const innerConeAngle = light.spot.innerConeAngle !== void 0 ? light.spot.innerConeAngle : 0;
const outerConeAngle = light.spot.outerConeAngle !== void 0 ? light.spot.outerConeAngle : Math.PI / 4;
this.scenesManager.lights.push(new SpotLight(this.renderer, {
label,
color: light.color !== void 0 ? new Vec3(light.color[0], light.color[1], light.color[2]) : new Vec3(1),
intensity: light.intensity !== void 0 ? light.intensity : 1,
range: light.range !== void 0 ? light.range : 0,
angle: outerConeAngle,
penumbra: 1 - innerConeAngle / outerConeAngle
}));
} else if (light.type === "directional") this.scenesManager.lights.push(new DirectionalLight(this.renderer, {
label,
color: light.color !== void 0 ? new Vec3(light.color[0], light.color[1], light.color[2]) : new Vec3(1),
intensity: light.intensity !== void 0 ? light.intensity : 1
}));
else if (light.type === "point") this.scenesManager.lights.push(new PointLight(this.renderer, {
label,
color: light.color !== void 0 ? new Vec3(light.color[0], light.color[1], light.color[2]) : new Vec3(1),
intensity: light.intensity !== void 0 ? light.intensity : 1,
range: light.range !== void 0 ? light.range : 0
}));
}
}
}
/**
* Create the {@link Sampler} and add them to the {@link ScenesManager.samplers | scenesManager samplers array}.
*/
createSamplers() {
if (this.gltf.samplers) for (const [index, sampler] of Object.entries(this.gltf.samplers)) {
const descriptor = {
label: sampler.name ?? "glTF sampler " + index,
name: "gltfSampler" + index,
addressModeU: GLTFScenesManager.gpuAddressModeForWrap(sampler.wrapS),
addressModeV: GLTFScenesManager.gpuAddressModeForWrap(sampler.wrapT)
};
if (!sampler.magFilter || sampler.magFilter === GL.LINEAR) descriptor.magFilter = "linear";
switch (sampler.minFilter) {
case GL.NEAREST: break;
case GL.LINEAR:
case GL.LINEAR_MIPMAP_NEAREST:
descriptor.minFilter = "linear";
break;
case GL.NEAREST_MIPMAP_LINEAR:
descriptor.mipmapFilter = "linear";
break;
case GL.LINEAR_MIPMAP_LINEAR:
default:
descriptor.minFilter = "linear";
descriptor.mipmapFilter = "linear";
break;
}
this.scenesManager.samplers.push(new Sampler(this.renderer, descriptor));
}
else this.scenesManager.samplers.push(new Sampler(this.renderer, {
label: "Default sampler",
name: "defaultSampler",
magFilter: "linear",
minFilter: "linear",
mipmapFilter: "linear"
}));
}
/**
* Create a {@link MediaTexture} based on the options.
* @param material - material using that texture.
* @param image - image source of the texture.
* @param name - name of the texture.
* @param useTransform - Whether the {@link MediaTexture} should handle transformations.
* @returns - newly created {@link MediaTexture}.
*/
createTexture(material, image, name, useTransform = false) {
const format = (() => {
switch (name) {
case "baseColorTexture":
case "emissiveTexture":
case "specularTexture":
case "specularColorTexture":
case "sheenTexture":
case "sheenColorTexture":
case "sheenRoughnessTexture":
case "diffuseTransmissionTexture":
case "diffuseTransmissionFactorTexture":
case "diffuseTransmissionColorTexture": return "rgba8unorm-srgb";
case "occlusionTexture":
case "transmissionTexture":
case "clearcoatFactorTexture":
case "iridescenceFactorTexture": return "r8unorm";
case "thicknessTexture":
case "transmissionThicknessTexture":
case "clearcoatTexture":
case "clearcoatRoughnessTexture":
case "iridescenceTexture":
case "iridescenceThicknessTexture": return "rg8unorm";
default: return "rgba8unorm";
}
})();
const texture = new MediaTexture(this.renderer, {
label: material.name ? material.name + ": " + name : name,
name,
format,
visibility: ["fragment"],
generateMips: true,
fixedSize: {
width: image.width,
height: image.height
},
useTransform
});
texture.useImageBitmap(image);
return texture;
}
/**
* Create the {ScenesManager.materialsTextures | scenesManager materialsTextures array} and each associated {@link types/gltf/GLTFScenesManager.MaterialTextureDescriptor | MaterialTextureDescriptor} and their respective {@link Texture}.
*/
createMaterialTextures() {
this.scenesManager.materialsTextures = [];
const createdTextures = [];
if (this.gltf.materials) for (const [materialIndex, material] of Object.entries(this.gltf.materials)) {
const materialTextures = {
material: materialIndex,
texturesDescriptors: []
};
const getUVAttributeName = (texture) => {
if (!texture.texCoord) return "uv";
return texture.texCoord !== 0 ? "uv" + texture.texCoord : "uv";
};
const createTexture = (gltfTextureInfo, name) => {
const index = gltfTextureInfo.index;
const gltfTexture = this.gltf.textures[index];
const source = gltfTexture.extensions && gltfTexture.extensions["EXT_texture_webp"] ? gltfTexture.extensions["EXT_texture_webp"].source : gltfTexture.source;
const basisTexture = gltfTexture.extensions && gltfTexture.extensions.KHR_texture_basisu;
if (source === void 0) {
if (!this.renderer.production) if (basisTexture) throwWarning(`GLTFScenesManager: Basis/compressed textures not supported. This texture could not be created: ${name}`);
else throwWarning(`GLTFScenesManager: No texture source provided. This texture could not be created: ${name}`);
return;
}
const image = this.gltf.imagesBitmaps[source];
if (!this.renderer.production && basisTexture) throwWarning(`GLTFScenesManager: Basis/compressed textures not supported. This texture will use a fallback image: '${name}'`);
const samplerIndex = (gltfTextureInfo.index !== void 0 && this.gltf.textures[gltfTextureInfo.index].sampler) ?? this.gltf.textures.find((t) => {
return (t.extensions && t.extensions["EXT_texture_webp"] ? t.extensions["EXT_texture_webp"].source : t.source) === index;
})?.sampler;
const sampler = this.scenesManager.samplers[samplerIndex ?? 0];
let textureTransform = gltfTextureInfo.extensions && gltfTextureInfo.extensions["KHR_texture_transform"];
if (!textureTransform && this.gltf.extensionsUsed && this.gltf.extensionsUsed.includes("KHR_animation_pointer")) textureTransform = {};
const texCoordAttributeName = getUVAttributeName(textureTransform && textureTransform.texCoord !== void 0 ? textureTransform : gltfTextureInfo);
const hasTexture = createdTextures.find((createdTexture) => createdTexture.index === index);
if (hasTexture) {
const reusedTexture = new MediaTexture(this.renderer, {
label: material.name ? material.name + ": " + name : name,
name,
visibility: ["fragment"],
generateMips: true,
fromTexture: hasTexture.texture,
...textureTransform && { useTransform: true }
});
if (textureTransform) {
const { offset, rotation, scale } = textureTransform;
if (offset !== void 0) reusedTexture.offset.set(offset[0], offset[1]);
if (rotation !== void 0) reusedTexture.rotation = rotation;
if (scale !== void 0) reusedTexture.scale.set(scale[0], scale[1]);
}
materialTextures.texturesDescriptors.push({
texture: reusedTexture,
sampler,
texCoordAttributeName
});
return;
}
const texture = this.createTexture(material, image, name, !!textureTransform);
if (textureTransform) {
const { offset, rotation, scale } = textureTransform;
if (offset !== void 0) texture.offset.set(offset[0], offset[1]);
if (rotation !== void 0) texture.rotation = rotation;
if (scale !== void 0) texture.scale.set(scale[0], scale[1]);
}
materialTextures.texturesDescriptors.push({
texture,
sampler,
texCoordAttributeName
});
createdTextures.push({
index,
texture
});
};
this.scenesManager.materialsTextures[materialIndex] = materialTextures;
if (material.pbrMetallicRoughness) {
if (material.pbrMetallicRoughness.baseColorTexture && material.pbrMetallicRoughness.baseColorTexture.index !== void 0) createTexture(material.pbrMetallicRoughness.baseColorTexture, "baseColorTexture");
if (material.pbrMetallicRoughness.metallicRoughnessTexture && material.pbrMetallicRoughness.metallicRoughnessTexture.index !== void 0) createTexture(material.pbrMetallicRoughness.metallicRoughnessTexture, "metallicRoughnessTexture");
}
if (material.normalTexture && material.normalTexture.index !== void 0) createTexture(material.normalTexture, "normalTexture");
if (material.occlusionTexture && material.occlusionTexture.index !== void 0) createTexture(material.occlusionTexture, "occlusionTexture");
if (material.emissiveTexture && material.emissiveTexture.index !== void 0) createTexture(material.emissiveTexture, "emissiveTexture");
const { extensions } = material;
const transmission = extensions && extensions.KHR_materials_transmission || null;
const specular = extensions && extensions.KHR_materials_specular || null;
const volume = extensions && extensions.KHR_materials_volume || null;
const sheen = extensions && extensions.KHR_materials_sheen || null;
const anisotropy = extensions && extensions.KHR_materials_anisotropy || null;
const clearcoat = extensions && extensions.KHR_materials_clearcoat || null;
const iridescence = extensions && extensions.KHR_materials_iridescence || null;
const diffuseTransmission = extensions && extensions.KHR_materials_diffuse_transmission || null;
if (specular && (specular.specularTexture || specular.specularColorTexture)) {
const { specularTexture, specularColorTexture } = specular;
if (specularTexture && specularColorTexture && specularTexture.index !== void 0 && specularTexture.index === specularColorTexture.index) createTexture(specularTexture, "specularTexture");
else {
if (specularTexture && specularTexture.index !== void 0) createTexture(specularTexture, "specularFactorTexture");
if (specularColorTexture && specularColorTexture.index !== void 0) createTexture(specularColorTexture, "specularColorTexture");
}
}
if (transmission && volume && transmission.transmissionTexture && volume.thicknessTexture && transmission.transmissionTexture.index !== void 0 && transmission.transmissionTexture.index === volume.thicknessTexture.index) createTexture(transmission.transmissionTexture, "transmissionThicknessTexture");
else {
if (transmission && transmission.transmissionTexture && transmission.transmissionTexture.index !== void 0) createTexture(transmission.transmissionTexture, "transmissionTexture");
if (volume && volume.thicknessTexture && volume.thicknessTexture.index !== void 0) createTexture(volume.thicknessTexture, "thicknessTexture");
}
if (sheen && (sheen.sheenColorTexture || sheen.sheenRoughnessTexture)) {
const { sheenColorTexture, sheenRoughnessTexture } = sheen;
if (sheenColorTexture && sheenRoughnessTexture && sheenColorTexture.index !== void 0 && sheenColorTexture.index === sheenRoughnessTexture.index) createTexture(sheenColorTexture, "sheenTexture");
else {
if (sheenColorTexture && sheenColorTexture.index !== void 0) createTexture(sheenColorTexture, "sheenColorTexture");
if (sheenRoughnessTexture && sheenRoughnessTexture.index !== void 0) createTexture(sheenRoughnessTexture, "sheenRoughnessTexture");
}
}
if (anisotropy && anisotropy.anisotropyTexture && anisotropy.anisotropyTexture.index !== void 0) createTexture(anisotropy.anisotropyTexture, "anisotropyTexture");
if (clearcoat && (clearcoat.clearcoatTexture || clearcoat.clearcoatRoughnessTexture || clearcoat.clearcoatNormalTexture)) {
const { clearcoatTexture, clearcoatRoughnessTexture, clearcoatNormalTexture } = clearcoat;
if (clearcoatTexture && clearcoatRoughnessTexture && clearcoatTexture.index !== void 0 && clearcoatTexture.index === clearcoatRoughnessTexture.index) createTexture(clearcoatTexture, "clearcoatTexture");
else {
if (clearcoatTexture && clearcoatTexture.index !== void 0) createTexture(clearcoatTexture, "clearcoatFactorTexture");
if (clearcoatRoughnessTexture && clearcoatRoughnessTexture.index !== void 0) createTexture(clearcoatRoughnessTexture, "clearcoatRoughnessTexture");
}
if (clearcoatNormalTexture && clearcoatNormalTexture.index !== void 0) createTexture(clearcoatNormalTexture, "clearcoatNormalTexture");
}
if (iridescence && (iridescence.iridescenceTexture || iridescence.iridescenceThicknessTexture)) {
const { iridescenceTexture, iridescenceThicknessTexture } = iridescence;
if (iridescenceTexture && iridescenceThicknessTexture && iridescenceTexture.index !== void 0 && iridescenceTexture.index === iridescenceThicknessTexture.index) createTexture(iridescenceTexture, "iridescenceTexture");
else {
if (iridescenceTexture && iridescenceTexture.index !== void 0) createTexture(iridescenceTexture, "iridescenceFactorTexture");
if (iridescenceThicknessTexture && iridescenceThicknessTexture.index !== void 0) createTexture(iridescenceThicknessTexture, "iridescenceThicknessTexture");
}
}
if (diffuseTransmission && (diffuseTransmission.diffuseTransmissionTexture || diffuseTransmission.diffuseTransmissionColorTexture)) {
const { diffuseTransmissionTexture, diffuseTransmissionColorTexture } = diffuseTransmission;
if (diffuseTransmissionTexture && diffuseTransmissionColorTexture && diffuseTransmissionTexture.index !== void 0 && diffuseTransmissionTexture.index === diffuseTransmissionColorTexture.index) createTexture(diffuseTransmissionTexture, "diffuseTransmissionTexture");
else {
if (diffuseTransmissionTexture && diffuseTransmissionTexture.index !== void 0) createTexture(diffuseTransmissionTexture, "diffuseTransmissionFactorTexture");
if (diffuseTransmissionColorTexture && diffuseTransmissionColorTexture.index !== void 0) createTexture(diffuseTransmissionColorTexture, "diffuseTransmissionColorTexture");
}
}
}
}
/**
* Get the {@link MeshDescriptorMaterialParams} for a given {@link GLTF.IMeshPrimitive.material | glTF primitive material index}.
* @param materialIndex - {@link GLTF.IMeshPrimitive.material | glTF primitive material index}.
* @param label - Optional label to use for the {@link RenderMaterial} created.
* @returns - Created {@link MeshDescriptorMaterialParams}.
*/
getMaterialBaseParameters(materialIndex, label = null) {
const materialParams = {};
const material = this.gltf.materials && this.gltf.materials[materialIndex] || {};
if (label) materialParams.label = label + (material.name ? " " + material.name : "");
else if (material.name) materialParams.label = material.name;
const { extensions } = material;
const dispersion = extensions && extensions.KHR_materials_dispersion || null;
const emissiveStrength = extensions && extensions.KHR_materials_emissive_strength || null;
const ior = extensions && extensions.KHR_materials_ior || null;
const transmission = extensions && extensions.KHR_materials_transmission || null;
const specular = extensions && extensions.KHR_materials_specular || null;
const volume = extensions && extensions.KHR_materials_volume || null;
const volumeScatter = extensions && extensions.KHR_materials_volume_scatter || null;
const sheen = extensions && extensions.KHR_materials_sheen || null;
const anisotropy = extensions && extensions.KHR_materials_anisotropy || null;
const clearcoat = extensions && extensions.KHR_materials_clearcoat || null;
const iridescence = extensions && extensions.KHR_materials_iridescence || null;
const diffuseTransmission = extensions && extensions.KHR_materials_diffuse_transmission || null;
if ((extensions && extensions.KHR_materials_pbrSpecularGlossiness || null) && !this.renderer.production) throwWarning("GLTFScenesManager: KHR_materials_pbrSpecularGlossiness is deprecated and therefore not supported.");
const litMeshMaterialParams = {
colorSpace: "linear",
color: material.pbrMetallicRoughness && material.pbrMetallicRoughness.baseColorFactor !== void 0 ? new Vec3(material.pbrMetallicRoughness.baseColorFactor[0], material.pbrMetallicRoughness.baseColorFactor[1], material.pbrMetallicRoughness.baseColorFactor[2]) : new Vec3(1),
opacity: material.pbrMetallicRoughness && material.pbrMetallicRoughness.baseColorFactor !== void 0 ? material.pbrMetallicRoughness.baseColorFactor[3] : 1,
alphaCutoff: material.alphaCutoff !== void 0 ? material.alphaCutoff : material.alphaMode === "MASK" ? .5 : 0,
metallic: material.pbrMetallicRoughness?.metallicFactor !== void 0 ? material.pbrMetallicRoughness.metallicFactor : 1,
roughness: material.pbrMetallicRoughness?.roughnessFactor !== void 0 ? material.pbrMetallicRoughness.roughnessFactor : 1,
normalScale: material.normalTexture?.scale === void 0 ? new Vec2(1) : new Vec2(material.normalTexture.scale),
occlusionIntensity: material.occlusionTexture?.strength === void 0 ? 1 : material.occlusionTexture.strength,
emissiveIntensity: emissiveStrength && emissiveStrength.emissiveStrength !== void 0 ? emissiveStrength.emissiveStrength : 1,
emissiveColor: material.emissiveFactor !== void 0 ? new Vec3(material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2]) : new Vec3(0),
specularIntensity: specular && specular.specularFactor !== void 0 ? specular.specularFactor : 1,
specularColor: specular && specular.specularColorFactor !== void 0 ? new Vec3(specular.specularColorFactor[0], specular.specularColorFactor[1], specular.specularColorFactor[2]) : new Vec3(1),
transmission: transmission && transmission.transmissionFactor !== void 0 ? transmission.transmissionFactor : 0,
ior: ior && ior.ior !== void 0 ? ior.ior : 1.5,
dispersion: dispersion && dispersion.dispersion !== void 0 ? dispersion.dispersion : 0,
thickness: volume && volume.thicknessFactor !== void 0 ? volume.thicknessFactor : 0,
attenuationDistance: volume && volume.attenuationDistance !== void 0 ? volume.attenuationDistance : Infinity,
attenuationColor: volume && volume.attenuationColor !== void 0 ? new Vec3(volume.attenuationColor[0], volume.attenuationColor[1], volume.attenuationColor[2]) : new Vec3(1),
...volumeScatter && {
...volumeScatter.multiscatterColor !== void 0 && { multiscatterColor: new Vec3(volumeScatter.multiscatterColor[0], volumeScatter.multiscatterColor[1], volumeScatter.multiscatterColor[2]) },
...volumeScatter.scatterAnisotropy !== void 0 !== void 0 && { scatterAnisotropy: volumeScatter.scatterAnisotropy }
},
...sheen && {
...sheen.sheenColorFactor !== void 0 && { sheenColor: new Vec3(sheen.sheenColorFactor[0], sheen.sheenColorFactor[1], sheen.sheenColorFactor[2]) },
...sheen.sheenRoughnessFactor !== void 0 !== void 0 && { sheenRoughness: sheen.sheenRoughnessFactor }
},
...anisotropy && {
...anisotropy.anisotropyStrength !== void 0 && { anisotropy: anisotropy.anisotropyStrength },
...anisotropy.anisotropyRotation !== void 0 && { anisotropyVector: new Vec2(Math.cos(anisotropy.anisotropyRotation), Math.sin(anisotropy.anisotropyRotation)) }
},
...clearcoat && {
...clearcoat.clearcoatFactor !== void 0 && { clearcoat: clearcoat.clearcoatFactor },
...clearcoat.clearcoatRoughnessFactor !== void 0 && { clearcoatRoughness: clearcoat.clearcoatRoughnessFactor }
},
...iridescence && {
...iridescence.iridescenceFactor !== void 0 && { iridescence: iridescence.iridescenceFactor },
iridescenceIOR: iridescence.iridescenceIor !== void 0 ? iridescence.iridescenceIor : 1.3,
iridescenceThicknessRange: new Vec2(iridescence.iridescenceThicknessMinimum !== void 0 ? iridescence.iridescenceThicknessMinimum : 100, iridescence.iridescenceThicknessMaximum !== void 0 ? iridescence.iridescenceThicknessMaximum : 400)
},
...diffuseTransmission && {
diffuseTransmission: diffuseTransmission.diffuseTransmissionFactor !== void 0 ? diffuseTransmission.diffuseTransmissionFactor : 0,
diffuseTransmissionColor: diffuseTransmission.diffuseTransmissionColorFactor !== void 0 ? new Vec3(diffuseTransmission.diffuseTransmissionColorFactor[0], diffuseTransmission.diffuseTransmissionColorFactor[1], diffuseTransmission.diffuseTransmissionColorFactor[2]) : new Vec3(1)
}
};
if (clearcoat && clearcoat.clearcoatNormalTexture && clearcoat.clearcoatNormalTexture.scale) litMeshMaterialParams.clearcoatNormalScale = new Vec2(clearcoat.clearcoatNormalTexture.scale);
materialParams.material = litMeshMaterialParams;
materialParams.cullMode = material.doubleSided ? "none" : "back";
if (material.alphaMode === "BLEND") {
materialParams.transparent = true;
materialParams.targets = [{ blend: {
color: {
srcFactor: "src-alpha",
dstFactor: "one-minus-src-alpha"
},
alpha: {
srcFactor: "one",
dstFactor: "one-minus-src-alpha"
}
} }];
}
return materialParams;
}
/**
* Create all the {@link MeshDescriptorMaterialParams} from the {@link GLTF.IMaterial | glTF materials}.
*/
createMaterialsParams() {
this.gltf.materials?.forEach((material, index) => {
this.scenesManager.materialsParams.push(this.getMaterialBaseParameters(index));
});
}
/**
* Create a {@link ChildDescriptor} from a parent {@link ChildDescriptor} and a {@link GLTF.INode | glTF Node}
* @param parent - parent {@link ChildDescriptor} to use.
* @param node - {@link GLTF.INode | glTF Node} to use.
* @param index - Index of the {@link GLTF.INode | glTF Node} to use.
*/
createNode(parent, node, index) {
const child = {
index,
name: node.name,
node: new Object3D(),
children: []
};
this.scenesManager.nodes.set(index, child.node);
parent.children.push(child);
child.node.parent = parent.node;
if (node.matrix) {
child.node.modelMatrix.setFromArray(new Float32Array(node.matrix));
child.node.matrices.model.shouldUpdate = false;
} else {
if (node.translation) child.node.position.set(node.translation[0], node.translation[1], node.translation[2]);
if (node.scale) child.node.scale.set(node.scale[0], node.scale[1], node.scale[2]);
if (node.rotation) child.node.quaternion.setFromArray(new Float32Array(node.rotation));
}
if (node.children) node.children.forEach((childNodeIndex) => {
const childNode = this.gltf.nodes[childNodeIndex];
this.createNode(child, childNode, childNodeIndex);
});
let instancesDescriptor = null;
if (node.mesh !== void 0) {
let instanceAttributes = null;
if (node.extensions && node.extensions.EXT_mesh_gpu_instancing) {
const { attributes } = node.extensions.EXT_mesh_gpu_instancing;
instanceAttributes = {
count: 0,
nodesTransformations: {}
};
for (const attribute of Object.entries(attributes)) {
const accessor = this.gltf.accessors[attribute[1]];
const attributeValues = this.#getAccessorArray(accessor);
instanceAttributes.count = accessor.count;
instanceAttributes.nodesTransformations[attribute[0].toLowerCase()] = attributeValues;
}
}
const mesh = this.gltf.meshes[node.mesh];
mesh.primitives.forEach((primitive, primitiveIndex) => {
const scenes = [];
if (this.gltf.scenes) this.gltf.scenes.forEach((scene, i) => {
if (scene.nodes.includes(index)) scenes.push({
name: scene.name ?? `scene${i}`,
index: i
});
});
const meshDescriptor = {
parent: child.node,
texturesDescriptors: [],
variantName: "Default",
parameters: { label: mesh.name ? mesh.name + " " + primitiveIndex : "glTF mesh " + primitiveIndex },
nodes: [],
scenes,
extensionsUsed: [],
alternateDescriptors: /* @__PURE__ */ new Map(),
alternateMaterials: /* @__PURE__ */ new Map()
};
instancesDescriptor = this.#primitiveInstances.get(primitive);
if (!instancesDescriptor) {
instancesDescriptor = {
instances: [],
nodes: [],
meshDescriptor
};
this.#primitiveInstances.set(primitive, instancesDescriptor);
}
instancesDescriptor.instances.push(node);
instancesDescriptor.nodes.push(child.node);
if (instanceAttributes && instanceAttributes.count) for (let i = 0; i < instanceAttributes.count; i++) {
const instanceNode = new Object3D();
if (instanceAttributes.nodesTransformations) {
const { translation, scale, rotation } = instanceAttributes.nodesTransformations;
if (translation) instanceNode.position.set(translation[i * 3], translation[i * 3 + 1], translation[i * 3 + 2]);
if (scale) instanceNode.scale.set(scale[i * 3], scale[i * 3 + 1], scale[i * 3 + 2]);
if (rotation) instanceNode.quaternion.setFromArray(Float32Array.from([
rotation[i * 4],
rotation[i * 4 + 1],
rotation[i * 4 + 2],
rotation[i * 4 + 3]
]));
}
instanceNode.parent = child.node;
instancesDescriptor.instances.push(node);
instancesDescriptor.nodes.push(instanceNode);
}
});
}
if (node.extensions && node.extensions.KHR_lights_punctual) {
const light = this.scenesManager.lights[node.extensions.KHR_lights_punctual.light];
light.position.set(0, 0, 0);
child.node.scale.set(1);
if (light instanceof DirectionalLight || light instanceof SpotLight) {
light.target.set(0, 0, -1);
const _updateWorldMatrix = child.node.updateWorldMatrix.bind(child.node);
child.node.updateWorldMatrix = (updateParents, updateChildren) => {
_updateWorldMatrix(updateParents, updateChildren);
light.updateTargetFromWorldMatrix();
};
}
light.parent = child.node;
}
if (node.camera !== void 0) {
child.node.scale.set(1);
const gltfCamera = this.gltf.cameras[node.camera];
if (gltfCamera.type === "perspective") {
let width = 0, height = 0;
if (gltfCamera.perspective.aspectRatio !== void 0) {
const minSize = Math.min(this.renderer.boundingRect.width, this.renderer.boundingRect.height);
width = minSize / gltfCamera.perspective.aspectRatio;
height = minSize * gltfCamera.perspective.aspectRatio;
} else {
width = this.renderer.boundingRect.width;
height = this.renderer.boundingRect.height;
}
const fov = gltfCamera.perspective.yfov * 180 / Math.PI;
const camera = new PerspectiveCamera({
label: gltfCamera.name ?? `glTF Perspective camera ${node.camera}`,
fov,
near: gltfCamera.perspective.znear ?? .01,
far: gltfCamera.perspective.zfar ?? 1e3,
width,
height,
pixelRatio: this.renderer.pixelRatio,
...gltfCamera.perspective.aspectRatio !== void 0 && { forceAspect: gltfCamera.perspective.aspectRatio }
});
camera.position.set(0);
camera.parent = child.node;
this.scenesManager.cameras.push(camera);
} else if (gltfCamera.type === "orthographic") {
const camera = new OrthographicCamera({
label: gltfCamera.name ?? `glTF Orthographic camera ${node.camera}`,
near: gltfCamera.orthographic.znear ?? .01,
far: gltfCamera.orthographic.zfar ?? 1e3,
left: -gltfCamera.orthographic.xmag,
right: gltfCamera.orthographic.xmag,
top: gltfCamera.orthographic.ymag,
bottom: -gltfCamera.orthographic.ymag
});
camera.position.set(0);
camera.parent = child.node;
this.scenesManager.cameras.push(camera);
}
}
if (this.gltf.animations) this.scenesManager.animations.forEach((targetsAnimation, i) => {
const animation = this.gltf.animations[i];
const channels = animation.channels.filter((channel) => channel.target.node === index);
animation.channels.filter((channel) => channel.target.path === "pointer" && channel.target.extensions && channel.target.extensions.KHR_animation_pointer && channel.target.extensions.KHR_animation_pointer.pointer && channel.target.extensions.KHR_animation_pointer.pointer.includes("weights")).forEach((pointerChannel) => {
const pointerWeightChannel = {
sampler: pointerChannel.sampler,
target: {
node: null,
path: "weights"
}
};
const splitedPointerPaths = pointerChannel.target.extensions.KHR_animation_pointer.pointer.split("/");
splitedPointerPaths.shift();
pointerWeightChannel.target.node = parseInt(splitedPointerPaths[1]);
channels.push(pointerWeightChannel);
});
if (channels && channels.length) {
targetsAnimation.addTarget(child.node);
channels.forEach((channel) => {
const animName = node.name ? `${node.name} animation` : `${channel.target.path} animation ${index}`;
const label = animation.name ? `${animation.name} ${animName}` : `Animation ${i} ${animName}`;
const input = (() => {
switch (channel.target.path) {
case "rotation": return {
type: "quaternion",
value: child.node.quaternion
};
case "translation": return {
type: "vec3",
value: child.node.position
};
case "scale": return {
type: "vec3",
value: child.node.scale
};
case "weights": return {
type: "array",
value: null
};
default: return {
type: null,
value: null
};
}
})();
const sampler = animation.samplers[channel.sampler];
const path = channel.target.path;
const { keyframes, values } = this.getAnimationKeyframesValues(sampler);
const keyframesAnimation = new KeyframesAnimation({
label,
inputIndex: sampler.input,
keyframes,
values,
path,
type: input.type,
inputValue: input.value,
interpolation: sampler.interpolation
});
targetsAnimation.addTargetAnimation(child.node, keyframesAnimation);
});
}
});
if (node.extensions && node.extensions.KHR_node_visibility) {
const visible = node.extensions.KHR_node_visibility.visible !== void 0 ? node.extensions.KHR_node_visibility.visible : true;
child.node.visible = visible;
}
}
/**
* Get a {@link TypedArray} from an accessor patched with sparse values if needed.
* @param accessor - {@link GLTF.IAccessor | Accessor} to get the array from.
* @returns - {@link TypedArray} holding the referent accessor values, patched with sparse values if needed.
* @private
*/
#getAccessorArray(accessor) {
const constructor = accessor.componentType ? GLTFScenesManager.getTypedArrayConstructorFromComponentType(accessor.componentType) : Float32Array;
const attrSize = GLTFScenesManager.getVertexAttributeParamsFromType(accessor.type).size;
const bufferView = this.gltf.bufferViews[accessor.bufferView];
const array = new constructor(this.gltf.arrayBuffers[bufferView.buffer], accessor.byteOffset + bufferView.byteOffset, accessor.count * attrSize);
if (accessor.sparse) {
const { indices, values } = this.#getSparseAccessorIndicesAndValues(accessor);
for (let i = 0; i < indices.length; i++) for (let j = 0; j < attrSize; j++) array[indices[i] * attrSize + j] = values[i * attrSize + j];
}
return array;
}
/**
* Get an accessor sparse indices values to use for replacement if any.
* @param accessor - {@link GLTF.IAccessor | Accessor} to check for sparse indices.
* @returns parameters - indices and values found as {@link TypedArray} if any.
* @private
*/
#getSparseAccessorIndicesAndValues(accessor) {
if (!accessor.sparse) return {
indices: null,
values: null
};
const accessorConstructor = GLTFScenesManager.getTypedArrayConstructorFromComponentType(accessor.componentType);
const attrSize = GLTFScenesManager.getVertexAttributeParamsFromType(accessor.type).size;
const sparseValuesBufferView = this.gltf.bufferViews[accessor.sparse.values.bufferView];
const sparseValues = new accessorConstructor(this.gltf.arrayBuffers[sparseValuesBufferView.buffer], accessor.byteOffset + sparseValuesBufferView.byteOffset, accessor.sparse.count * attrSize);
const sparseIndicesConstructor = GLTFScenesManager.getTypedArrayConstructorFromComponentType(accessor.sparse.indices.componentType);
const sparseIndicesBufferView = this.gltf.bufferViews[accessor.sparse.indices.bufferView];
return {
indices: new sparseIndicesConstructor(this.gltf.arrayBuffers[sparseIndicesBufferView.buffer], accessor.byteOffset + sparseIndicesBufferView.byteOffset, accessor.sparse.count),
values: sparseValues
};
}
/**
* Get a clean attribute name based on a glTF attribute name.
* @param gltfAttributeName - glTF attribute name.
* @returns - Attribute name conform to our expectations.
*/
static getCleanAttributeName(gltfAttributeName) {
return gltfAttributeName === "TEXCOORD_0" ? "uv" : gltfAttributeName.replace("_", "").replace("TEXCOORD", "uv").toLowerCase();
}
/**
* Sort an array of {@link VertexBufferAttributeParams} by an array of attribute names.
* @param attributesNames - array of attribute names to use for sorting.
* @param attributes - {@link VertexBufferAttributeParams} array to sort.
*/
sortAttributesByNames(attributesNames, attributes) {
attributes.sort((a, b) => {
let aIndex = attributesNames.findIndex((attrName) => attrName === a.name);
aIndex = aIndex === -1 ? Infinity : aIndex;
let bIndex = attributesNames.findIndex((attrName) => attrName === b.name);
bIndex = bIndex === -1 ? Infinity : bIndex;
return aIndex - bIndex;
});
}
/**
* Parse a {@link GLTF.IMeshPrimitive | glTF primitive} and create typed arrays from the given {@link gltf} accessors, bufferViews and buffers.
* @param primitiveProperty- Primitive property to parse, can either be `attributes` or `targets`.
* @param attributes - An empty {@link VertexBufferAttributeParams} array to fill with parsed values.
* @returns - Interleaved attributes {@link TypedArray} if any.
* @private
*/
#parsePrimitiveProperty(primitiveProperty, attributes) {
let interleavedArray = null;
let interleavedBufferView = null;
let maxByteOffset = 0;
const primitiveAttributes = Object.entries(primitiveProperty);
primitiveAttributes.sort((a, b) => a[1] - b[1]);
const primitiveAttributesValues = Object.values(primitiveProperty);
primitiveAttributesValues.sort((a, b) => a - b);
for (const [attribName, accessorIndex] of primitiveAttributes) {
const name = GLTFScenesManager.getCleanAttributeName(attribName);
const accessor = this.gltf.accessors[accessorIndex];
const constructor = accessor.componentType ? GLTFScenesManager.getTypedArrayConstructorFromComponentType(accessor.componentType) : Float32Array;
let bufferViewIndex = accessor.bufferView;
if (bufferViewIndex === void 0) continue;
const bufferView = this.gltf.bufferViews[bufferViewIndex];
const byteStride = bufferView.byteStride;
const accessorByteOffset = accessor.byteOffset;
if (byteStride !== void 0 && accessorByteOffset !== void 0 && accessorByteOffset < byteStride) maxByteOffset = Math.max(accessorByteOffset, maxByteOffset);
else maxByteOffset = 0;
if (name === "position") interleavedBufferView = bufferView;
const { size } = GLTFScenesManager.getVertexAttributeParamsFromType(accessor.type);
let array = null;
if (maxByteOffset > 0) {
const parentArray = new constructor(this.gltf.arrayBuffers[bufferView.buffer], 0, bufferView.byteLength / constructor.BYTES_PER_ELEMENT);
array = new constructor(accessor.count * size);
const arrayStride = accessorByteOffset / constructor.BYTES_PER_ELEMENT;
for (let i = 0; i < accessor.count; i++) for (let j = 0; j < size; j++) array[i * size + j] = parentArray[arrayStride + size * i + size * i + j];
} else if (bufferView.byteStride && bufferView.byteStride > constructor.BYTES_PER_ELEMENT * size) {
const dataView = new DataView(this.gltf.arrayBuffers[bufferView.buffer], bufferView.byteOffset + accessor.byteOffset);
array = new constructor(accessor.count * size);
for (let i = 0; i < accessor.count; i++) {
const baseOffset = i * bufferView.byteStride;
for (let j = 0; j < size; j++) array[i * size + j] = dataView.getUint16(baseOffset + j * constructor.BYTES_PER_ELEMENT, true);
}
} else array = new constructor(this.gltf.arrayBuffers[bufferView.buffer], accessor.byteOffset + bufferView.byteOffset, accessor.count * size);
if (accessor.sparse) {
const { indices, values } = this.#getSparseAccessorIndicesAndValues(accessor);
for (let i = 0; i < indices.length; i++) for (let j = 0; j < size; j++) array[indices[i] * size + j] = values[i * size + j];
}
if (name.includes("weights")) for (let i = 0; i < accessor.count * size; i += size) {
const x = array[i];
const y = array[i + 1];
const z = array[i + 2];
const w = array[i + 3];
let len = Math.abs(x) + Math.abs(y) + Math.abs(z) + Math.abs(w);
if (len > 0) len = 1 / Math.sqrt(len);
else len = 1;
array[i] *= len;
array[i + 1] *= len;
array[i + 2] *= len;
array[i + 3] *= len;
}
let normalized = !!accessor.normalized;
const patchedAttributeParams = vertexBufferAttributeLayouts.find((vb) => size <= vb.size && vb.typedArrayConstructor === array.constructor && vb.normalized === normalized);
if (this.gltf.extensionsRequired?.includes("KHR_mesh_quantization") && array.constructor !== Float32Array && (name === "position" || name === "normal" || name === "tangent" || name.indexOf("uv") !== -1)) {
const stride = patchedAttributeParams.size;
if (stride !== size) {
const newArray = new array.constructor(accessor.count * stride);
for (let i = 0; i < newArray.length; i++) {
const si = i * size;
const di = i * stride;
for (let c = 0; c < size; c++) newArray[di + c] = array[si + c];
}
array = newArray;
}
}
const attribute = {
name,
...patchedAttributeParams,
array,
normalized
};
attributes.push(attribute);
}
if (maxByteOffset > 0) {
const accessorsBufferViews = primitiveAttributesValues.map((accessorIndex) => this.gltf.accessors[accessorIndex].bufferView);
if (!accessorsBufferViews.every((val) => val === accessorsBufferViews[0])) {
let totalStride = 0;
const arrayLength = Object.values(primitiveProperty).reduce((acc, accessorIndex) => {
const accessor = this.gltf.accessors[accessorIndex];
const attrSize = GLTFScenesManager.getVertexAttributeParamsFromType(accessor.type).size;
totalStride += attrSize * Float32Array.BYTES_PER_ELEMENT;
return acc + accessor.count * attrSize;
}, 0);
interleavedArray = new Float32Array(Math.ceil(arrayLength / 4) * 4);
let startWriteOffset = 0;
const cleanAttributeNames = Object.entries(primitiveProperty).map((prop) => GLTFScenesManager.getCleanAttributeName(prop[0]));
Object.values(primitiveProperty).forEach((accessorIndex, index) => {
const accessor = this.gltf.accessors[accessorIndex];
const bufferView = this.gltf.bufferViews[accessor.bufferView];
const attribute = attributes.find((attr) => attr.name === cleanAttributeNames[index]);
const attrSize = GLTFScenesManager.getVertexAttributeParamsFromType(accessor.type).size;
const { indices, values } = this.#getSparseAccessorIndicesAndValues(accessor);
for (let i = 0; i < accessor.count; i++) {
const bufferOffset = bufferView.byteOffset + accessor.byteOffset + i * bufferView.byteStride;
const subarray = new Float32Array(this.gltf.arrayBuffers[bufferView.buffer], bufferOffset, attrSize);
if (indices && values && indices.includes(i)) for (let j = 0; i < attrSize; j++) subarray[j] = values[i * attrSize + j];
const startOffset = startWriteOffset + i * totalStride / Float32Array.BYTES_PER_ELEMENT;
interleavedArray.subarray(startOffset, startOffset + attrSize).set(subarray);
attribute.array.subarray(i * attrSize, i * attrSize + attrSize).set(subarray);
}
startWriteOffset += attrSize;
});
this.sortAttributesByNames(cleanAttributeNames, attributes);
} else {
interleavedArray = new Float32Array(this.gltf.arrayBuffers[interleavedBufferView.buffer], interleavedBufferView.byteOffset, Math.ceil(interleavedBufferView.byteLength / 4) * 4 / Float32Array.