playcanvas
Version:
PlayCanvas WebGL game engine
756 lines (753 loc) • 26 kB
JavaScript
import { CoreExporter } from './core-exporter.js';
import { math } from '../../core/math/math.js';
import { Vec2 } from '../../core/math/vec2.js';
import { Vec3 } from '../../core/math/vec3.js';
import { Quat } from '../../core/math/quat.js';
import { Color } from '../../core/math/color.js';
import { BoundingBox } from '../../core/shape/bounding-box.js';
import { CULLFACE_NONE, SEMANTIC_POSITION, SEMANTIC_TEXCOORD7, SEMANTIC_TEXCOORD6, SEMANTIC_TEXCOORD5, SEMANTIC_TEXCOORD4, SEMANTIC_TEXCOORD3, SEMANTIC_TEXCOORD2, SEMANTIC_TEXCOORD1, SEMANTIC_TEXCOORD0, SEMANTIC_BLENDWEIGHT, SEMANTIC_BLENDINDICES, SEMANTIC_COLOR, SEMANTIC_TANGENT, SEMANTIC_NORMAL, TYPE_FLOAT32, TYPE_UINT32, TYPE_INT32, TYPE_UINT16, TYPE_INT16, TYPE_UINT8, TYPE_INT8, INDEXFORMAT_UINT32, INDEXFORMAT_UINT16, INDEXFORMAT_UINT8, ADDRESS_REPEAT, ADDRESS_MIRRORED_REPEAT, ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR_MIPMAP_LINEAR, FILTER_NEAREST_MIPMAP_LINEAR, FILTER_LINEAR_MIPMAP_NEAREST, FILTER_NEAREST_MIPMAP_NEAREST, FILTER_LINEAR, FILTER_NEAREST } from '../../platform/graphics/constants.js';
import { IndexBuffer } from '../../platform/graphics/index-buffer.js';
import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js';
import { StandardMaterial } from '../../scene/materials/standard-material.js';
import { PROJECTION_ORTHOGRAPHIC, BLEND_NORMAL, BLEND_NONE } from '../../scene/constants.js';
const ARRAY_BUFFER = 34962;
const ELEMENT_ARRAY_BUFFER = 34963;
const getIndexComponentType = (indexFormat)=>{
switch(indexFormat){
case INDEXFORMAT_UINT8:
return 5121;
case INDEXFORMAT_UINT16:
return 5123;
case INDEXFORMAT_UINT32:
return 5125;
}
return 0;
};
const getComponentType = (dataType)=>{
switch(dataType){
case TYPE_INT8:
return 5120;
case TYPE_UINT8:
return 5121;
case TYPE_INT16:
return 5122;
case TYPE_UINT16:
return 5123;
case TYPE_INT32:
return 5124;
case TYPE_UINT32:
return 5125;
case TYPE_FLOAT32:
return 5126;
}
return 0;
};
const getAccessorType = (componentCount)=>{
switch(componentCount){
case 1:
return 'SCALAR';
case 2:
return 'VEC2';
case 3:
return 'VEC3';
case 4:
return 'VEC4';
}
return 0;
};
const getSemantic = (engineSemantic)=>{
switch(engineSemantic){
case SEMANTIC_POSITION:
return 'POSITION';
case SEMANTIC_NORMAL:
return 'NORMAL';
case SEMANTIC_TANGENT:
return 'TANGENT';
case SEMANTIC_COLOR:
return 'COLOR_0';
case SEMANTIC_BLENDINDICES:
return 'JOINTS_0';
case SEMANTIC_BLENDWEIGHT:
return 'WEIGHTS_0';
case SEMANTIC_TEXCOORD0:
return 'TEXCOORD_0';
case SEMANTIC_TEXCOORD1:
return 'TEXCOORD_1';
case SEMANTIC_TEXCOORD2:
return 'TEXCOORD_2';
case SEMANTIC_TEXCOORD3:
return 'TEXCOORD_3';
case SEMANTIC_TEXCOORD4:
return 'TEXCOORD_4';
case SEMANTIC_TEXCOORD5:
return 'TEXCOORD_5';
case SEMANTIC_TEXCOORD6:
return 'TEXCOORD_6';
case SEMANTIC_TEXCOORD7:
return 'TEXCOORD_7';
}
return '';
};
const getFilter = function(filter) {
switch(filter){
case FILTER_NEAREST:
return 9728;
case FILTER_LINEAR:
return 9729;
case FILTER_NEAREST_MIPMAP_NEAREST:
return 9984;
case FILTER_LINEAR_MIPMAP_NEAREST:
return 9985;
case FILTER_NEAREST_MIPMAP_LINEAR:
return 9986;
case FILTER_LINEAR_MIPMAP_LINEAR:
return 9987;
}
return 0;
};
const getWrap = function(wrap) {
switch(wrap){
case ADDRESS_CLAMP_TO_EDGE:
return 33071;
case ADDRESS_MIRRORED_REPEAT:
return 33648;
case ADDRESS_REPEAT:
return 10497;
}
return 0;
};
function isCanvasTransparent(canvas) {
const context = canvas.getContext('2d');
const pixelData = context.getImageData(0, 0, canvas.width, canvas.height).data;
for(let i = 3; i < pixelData.length; i += 4){
if (pixelData[i] < 255) {
return true;
}
}
return false;
}
const textureSemantics = [
'diffuseMap',
'colorMap',
'normalMap',
'metalnessMap',
'emissiveMap'
];
class GltfExporter extends CoreExporter {
collectResources(root) {
const resources = {
buffers: [],
cameras: [],
entities: [],
materials: [],
skins: [],
textures: [],
entityMeshInstances: [],
bufferViewMap: new Map(),
compressableTexture: new Set()
};
const { materials, buffers, entityMeshInstances, textures } = resources;
root.forEach((entity)=>{
resources.entities.push(entity);
});
const collectMeshInstances = (meshInstances)=>{
meshInstances.forEach((meshInstance)=>{
const material = meshInstance.material;
if (materials.indexOf(material) < 0) {
resources.materials.push(material);
textureSemantics.forEach((semantic)=>{
const texture = material[semantic];
if (texture && textures.indexOf(texture) < 0) {
if (semantic !== 'normalMap') {
resources.compressableTexture.add(texture);
}
textures.push(texture);
}
});
}
const node = meshInstance.node;
let nodeMeshInstances = entityMeshInstances.find((e)=>e.node === node);
if (!nodeMeshInstances) {
nodeMeshInstances = {
node: node,
meshInstances: []
};
entityMeshInstances.push(nodeMeshInstances);
}
nodeMeshInstances.meshInstances.push(meshInstance);
const mesh = meshInstance.mesh;
const vertexBuffer = mesh.vertexBuffer;
if (buffers.indexOf(vertexBuffer) < 0) {
buffers.unshift(vertexBuffer);
}
const indexBuffer = mesh.indexBuffer[0];
if (buffers.indexOf(indexBuffer) < 0) {
buffers.push(indexBuffer);
}
if (mesh.skin && resources.skins.indexOf(mesh.skin) < 0) {
resources.skins.push(mesh.skin);
}
});
};
resources.entities.forEach((entity)=>{
if (entity.camera) {
resources.cameras.push(entity.camera);
}
if (entity.render && entity.render.enabled) {
collectMeshInstances(entity.render.meshInstances);
}
if (entity.model && entity.model.enabled && entity.model.meshInstances) {
collectMeshInstances(entity.model.meshInstances);
}
});
return resources;
}
writeBufferViews(resources, json) {
json.bufferViews = [];
for (const buffer of resources.buffers){
GltfExporter.writeBufferView(resources, json, buffer);
}
}
static writeBufferView(resources, json, buffer) {
json.buffers = json.buffers ?? [];
json.buffers[0] = json.buffers[0] ?? {
byteLength: 0
};
const bufferInfo = json.buffers[0];
bufferInfo.byteLength = math.roundUp(bufferInfo.byteLength, 4);
const offset = bufferInfo.byteLength;
const addBufferView = (target, byteLength, byteOffset, byteStride)=>{
const bufferView = {
buffer: 0,
byteLength: byteLength,
byteOffset: byteOffset
};
if (target === ARRAY_BUFFER || target === ELEMENT_ARRAY_BUFFER) {
bufferView.target = target;
}
if (byteStride !== undefined) {
bufferView.byteStride = byteStride;
}
return json.bufferViews.push(bufferView) - 1;
};
let arrayBuffer;
if (buffer instanceof VertexBuffer) {
arrayBuffer = buffer.lock();
const format = buffer.getFormat();
if (format.interleaved) {
const bufferViewIndex = addBufferView(ARRAY_BUFFER, arrayBuffer.byteLength, offset, format.size);
resources.bufferViewMap.set(buffer, [
bufferViewIndex
]);
} else {
const bufferViewIndices = [];
for (const element of format.elements){
const bufferViewIndex = addBufferView(ARRAY_BUFFER, element.size * format.vertexCount, offset + element.offset, element.size);
bufferViewIndices.push(bufferViewIndex);
}
resources.bufferViewMap.set(buffer, bufferViewIndices);
}
} else if (buffer instanceof IndexBuffer) {
arrayBuffer = buffer.lock();
const bufferViewIndex = addBufferView(ELEMENT_ARRAY_BUFFER, arrayBuffer.byteLength, offset);
resources.bufferViewMap.set(buffer, [
bufferViewIndex
]);
} else {
arrayBuffer = buffer;
const bufferViewIndex = addBufferView(undefined, arrayBuffer.byteLength, offset);
resources.bufferViewMap.set(buffer, [
bufferViewIndex
]);
}
bufferInfo.byteLength += arrayBuffer.byteLength;
}
writeCameras(resources, json) {
if (resources.cameras.length > 0) {
json.cameras = resources.cameras.map((cam)=>{
const projection = cam.projection;
const nearClip = cam.nearClip;
const farClip = cam.farClip;
const camera = {};
if (projection === PROJECTION_ORTHOGRAPHIC) {
camera.type = 'orthographic';
camera.orthographic = {
xmag: 1,
ymag: 1,
znear: nearClip,
zfar: farClip
};
} else {
const fov = cam.fov;
camera.type = 'perspective';
camera.perspective = {
yfov: fov * Math.PI / 180,
znear: nearClip,
zfar: farClip
};
}
return camera;
});
}
}
attachTexture(resources, material, destination, name, textureSemantic, json) {
const texture = material[textureSemantic];
if (texture) {
const textureIndex = resources.textures.indexOf(texture);
if (textureIndex < 0) console.warn(`Texture ${texture.name} wasn't collected.`);
destination[name] = {
index: textureIndex
};
const scale = material[`${textureSemantic}Tiling`];
const offset = material[`${textureSemantic}Offset`];
const rotation = material[`${textureSemantic}Rotation`];
if (scale && !scale.equals(Vec2.ONE) || offset && !offset.equals(Vec2.ZERO) || rotation !== 0) {
destination[name].extensions = {
KHR_texture_transform: {}
};
json.extensionsUsed = json.extensionsUsed ?? [];
if (json.extensionsUsed.indexOf('KHR_texture_transform') < 0) {
json.extensionsUsed.push('KHR_texture_transform');
}
json.extensionsRequired = json.extensionsRequired ?? [];
if (json.extensionsRequired.indexOf('KHR_texture_transform') < 0) {
json.extensionsRequired.push('KHR_texture_transform');
}
if (scale && !scale.equals(Vec2.ONE)) {
destination[name].extensions.KHR_texture_transform.scale = [
scale.x,
scale.y
];
}
if (offset && !offset.equals(Vec2.ZERO)) {
destination[name].extensions.KHR_texture_transform.offset = [
offset.x,
offset.y - 1 + scale.y
];
}
if (rotation !== 0) {
destination[name].extensions.KHR_texture_transform.rotation = rotation * math.DEG_TO_RAD;
}
}
}
}
writeStandardMaterial(resources, mat, output, json) {
const { diffuse, emissive, opacity, metalness, gloss, glossInvert } = mat;
const pbr = output.pbrMetallicRoughness;
if (!diffuse.equals(Color.WHITE) || opacity !== 1) {
pbr.baseColorFactor = [
diffuse.r,
diffuse.g,
diffuse.b,
opacity
];
}
if (metalness !== 1) {
pbr.metallicFactor = metalness;
}
const roughness = glossInvert ? gloss : 1 - gloss;
if (roughness !== 1) {
pbr.roughnessFactor = roughness;
}
this.attachTexture(resources, mat, pbr, 'baseColorTexture', 'diffuseMap', json);
this.attachTexture(resources, mat, pbr, 'metallicRoughnessTexture', 'metalnessMap', json);
if (!emissive.equals(Color.BLACK)) {
output.emissiveFactor = [
emissive.r,
emissive.g,
emissive.b
];
}
}
writeMaterials(resources, json) {
if (resources.materials.length > 0) {
json.materials = resources.materials.map((mat)=>{
const { name, blendType, cull, alphaTest } = mat;
const material = {
pbrMetallicRoughness: {}
};
if (name && name.length > 0) {
material.name = name;
}
if (mat instanceof StandardMaterial) {
this.writeStandardMaterial(resources, mat, material, json);
}
if (blendType === BLEND_NORMAL) {
material.alphaMode = 'BLEND';
} else if (blendType === BLEND_NONE) {
if (alphaTest !== 0) {
material.alphaMode = 'MASK';
material.alphaCutoff = alphaTest;
}
}
if (cull === CULLFACE_NONE) {
material.doubleSided = true;
}
this.attachTexture(resources, mat, material, 'normalTexture', 'normalMap', json);
this.attachTexture(resources, mat, material, 'occlusionTexture', 'aoMap', json);
this.attachTexture(resources, mat, material, 'emissiveTexture', 'emissiveMap', json);
return material;
});
}
}
writeNodes(resources, json) {
if (resources.entities.length > 0) {
json.nodes = resources.entities.map((entity)=>{
const name = entity.name;
const t = entity.getLocalPosition();
const r = entity.getLocalRotation();
const s = entity.getLocalScale();
const node = {};
if (name && name.length > 0) {
node.name = name;
}
if (!t.equals(Vec3.ZERO)) {
node.translation = [
t.x,
t.y,
t.z
];
}
if (!r.equals(Quat.IDENTITY)) {
node.rotation = [
r.x,
r.y,
r.z,
r.w
];
}
if (!s.equals(Vec3.ONE)) {
node.scale = [
s.x,
s.y,
s.z
];
}
if (entity.camera && entity.camera.enabled) {
node.camera = resources.cameras.indexOf(entity.camera);
}
const entityMeshInstance = resources.entityMeshInstances.find((e)=>e.node === entity);
if (entityMeshInstance) {
node.mesh = resources.entityMeshInstances.indexOf(entityMeshInstance);
const meshInstance = entityMeshInstance.meshInstances[0];
if (meshInstance && meshInstance.mesh.skin) {
node.skin = resources.skins.indexOf(meshInstance.mesh.skin);
}
}
if (entity.children.length > 0) {
node.children = [];
entity.children.forEach((child)=>{
node.children.push(resources.entities.indexOf(child));
});
}
return node;
});
}
}
writeMeshes(resources, json, options) {
if (resources.entityMeshInstances.length > 0) {
json.accessors = [];
json.meshes = [];
resources.entityMeshInstances.forEach((entityMeshInstances)=>{
const mesh = {
primitives: []
};
const meshInstances = entityMeshInstances.meshInstances;
meshInstances.forEach((meshInstance)=>{
const primitive = GltfExporter.createPrimitive(resources, json, meshInstance.mesh, options);
primitive.material = resources.materials.indexOf(meshInstance.material);
mesh.primitives.push(primitive);
});
json.meshes.push(mesh);
});
}
}
static createPrimitive(resources, json, mesh, options = {}) {
const primitive = {
attributes: {}
};
const { vertexBuffer } = mesh;
const { format } = vertexBuffer;
const { interleaved, elements } = format;
const numVertices = vertexBuffer.getNumVertices();
elements.forEach((element, elementIndex)=>{
const semantic = getSemantic(element.name);
if (options.stripUnusedAttributes) {
let isUsed = true;
if (semantic.startsWith('TEXCOORD_')) {
const texCoordIndex = parseInt(semantic.split('_')[1], 10);
isUsed = resources.materials.some((material)=>{
return textureSemantics.some((texSemantic)=>{
const texture = material[texSemantic];
return texture && (texCoordIndex === 0 || material[`${texSemantic}Tiling`]?.uv === texCoordIndex);
});
});
}
if (semantic === 'COLOR_0') {
isUsed = resources.materials.some((material)=>material.vertexColors);
}
if (semantic === 'TANGENT') {
isUsed = resources.materials.some((material)=>material.normalMap);
}
if (semantic === 'JOINTS_0' || semantic === 'WEIGHTS_0') {
isUsed = resources.entityMeshInstances.some((emi)=>emi.meshInstances.some((mi)=>mi.mesh.skin));
}
if (!isUsed) {
return;
}
}
let bufferView = resources.bufferViewMap.get(vertexBuffer);
if (!bufferView) {
GltfExporter.writeBufferView(resources, json, vertexBuffer);
resources.buffers.push(vertexBuffer);
bufferView = resources.bufferViewMap.get(vertexBuffer);
}
const viewIndex = bufferView[interleaved ? 0 : elementIndex];
const accessor = {
bufferView: viewIndex,
byteOffset: interleaved ? element.offset : 0,
componentType: getComponentType(element.dataType),
type: getAccessorType(element.numComponents),
count: numVertices
};
const idx = json.accessors.push(accessor) - 1;
primitive.attributes[semantic] = idx;
if (element.name === SEMANTIC_POSITION) {
const positions = [];
mesh.getPositions(positions);
const min = new Vec3();
const max = new Vec3();
BoundingBox.computeMinMax(positions, min, max);
accessor.min = [
min.x,
min.y,
min.z
];
accessor.max = [
max.x,
max.y,
max.z
];
}
});
const indexBuffer = mesh.indexBuffer[0];
if (indexBuffer) {
let bufferView = resources.bufferViewMap.get(indexBuffer);
if (!bufferView) {
GltfExporter.writeBufferView(resources, json, indexBuffer);
resources.buffers.push(indexBuffer);
bufferView = resources.bufferViewMap.get(indexBuffer);
}
const viewIndex = bufferView[0];
const accessor = {
bufferView: viewIndex,
componentType: getIndexComponentType(indexBuffer.getFormat()),
count: indexBuffer.getNumIndices(),
type: 'SCALAR'
};
const idx = json.accessors.push(accessor) - 1;
primitive.indices = idx;
}
return primitive;
}
writeSkins(resources, json) {
if (resources.skins.length > 0) {
json.skins = resources.skins.map((skin)=>{
const matrices = new Float32Array(skin.inverseBindPose.length * 16);
for(let i = 0; i < skin.inverseBindPose.length; i++){
const ibm = skin.inverseBindPose[i];
matrices.set(ibm.data, i * 16);
}
const matrixBuffer = matrices.buffer;
GltfExporter.writeBufferView(resources, json, matrixBuffer);
resources.buffers.push(matrixBuffer);
const bufferView = resources.bufferViewMap.get(matrixBuffer);
const accessor = {
bufferView: bufferView[0],
componentType: getComponentType(TYPE_FLOAT32),
count: skin.inverseBindPose.length,
type: 'MAT4'
};
const accessorIndex = json.accessors.push(accessor) - 1;
const joints = skin.boneNames.map((boneName)=>{
const node = resources.entities.find((entity)=>entity.name === boneName);
return resources.entities.indexOf(node);
});
return {
inverseBindMatrices: accessorIndex,
joints: joints
};
});
}
}
convertTextures(srcTextures, options) {
const textureOptions = {
maxTextureSize: options.maxTextureSize
};
const promises = [];
srcTextures.forEach((srcTexture)=>{
const promise = this.textureToCanvas(srcTexture, textureOptions);
promise.then((canvas)=>{
return new Promise((resolve)=>resolve(canvas));
});
promises.push(promise);
});
return promises;
}
writeTextures(resources, textureCanvases, json, options) {
const textures = resources.textures;
const promises = [];
for(let i = 0; i < textureCanvases.length; i++){
const texture = textures[i];
const canvas = textureCanvases[i];
const isRGBA = isCanvasTransparent(canvas) || !resources.compressableTexture.has(texture);
const mimeType = isRGBA ? 'image/png' : 'image/jpeg';
promises.push(this.getBlob(canvas, mimeType).then((blob)=>{
const reader = new FileReader();
reader.readAsArrayBuffer(blob);
return new Promise((resolve)=>{
reader.onloadend = ()=>{
resolve(reader);
};
});
}).then((reader)=>{
const buffer = this.getPaddedArrayBuffer(reader.result);
GltfExporter.writeBufferView(resources, json, buffer);
resources.buffers.push(buffer);
const bufferView = resources.bufferViewMap.get(buffer);
json.images[i] = {
mimeType: mimeType,
bufferView: bufferView[0]
};
json.samplers[i] = {
minFilter: getFilter(texture.minFilter),
magFilter: getFilter(texture.magFilter),
wrapS: getWrap(texture.addressU),
wrapT: getWrap(texture.addressV)
};
json.textures[i] = {
sampler: i,
source: i
};
}));
}
return Promise.all(promises);
}
getBlob(canvas, mimeType) {
if (canvas.toBlob !== undefined) {
return new Promise((resolve)=>{
canvas.toBlob(resolve, mimeType);
});
}
let quality = 1.0;
if (mimeType === 'image/jpeg') {
quality = 0.92;
}
return canvas.convertToBlob({
type: mimeType,
quality: quality
});
}
getPaddedArrayBuffer(arrayBuffer, paddingByte = 0) {
const paddedLength = math.roundUp(arrayBuffer.byteLength, 4);
if (paddedLength !== arrayBuffer.byteLength) {
const array = new Uint8Array(paddedLength);
array.set(new Uint8Array(arrayBuffer));
if (paddingByte !== 0) {
for(let i = arrayBuffer.byteLength; i < paddedLength; i++){
array[i] = paddingByte;
}
}
return array.buffer;
}
return arrayBuffer;
}
buildJson(resources, options) {
const promises = this.convertTextures(resources.textures, options);
return Promise.all(promises).then(async (textureCanvases)=>{
const json = {
asset: {
version: '2.0',
generator: 'PlayCanvas GltfExporter'
},
scenes: [
{
nodes: [
0
]
}
],
images: [],
samplers: [],
textures: [],
scene: 0
};
this.writeBufferViews(resources, json);
this.writeCameras(resources, json);
this.writeMeshes(resources, json, options);
this.writeMaterials(resources, json);
this.writeNodes(resources, json, options);
this.writeSkins(resources, json);
await this.writeTextures(resources, textureCanvases, json, options);
if (!json.images.length) delete json.images;
if (!json.samplers.length) delete json.samplers;
if (!json.textures.length) delete json.textures;
return json;
});
}
build(entity, options = {}) {
const resources = this.collectResources(entity);
return this.buildJson(resources, options).then((json)=>{
const encoder = new TextEncoder();
const jsonData = encoder.encode(JSON.stringify(json));
const headerLength = 12;
const jsonHeaderLength = 8;
const jsonDataLength = jsonData.length;
const jsonPaddingLength = 4 - (jsonDataLength & 3) & 3;
const binaryHeaderLength = 8;
const binaryDataLength = json.buffers.reduce((total, buffer)=>math.roundUp(total + buffer.byteLength, 4), 0);
let totalLength = headerLength + jsonHeaderLength + jsonDataLength + jsonPaddingLength;
if (binaryDataLength > 0) {
totalLength += binaryHeaderLength + binaryDataLength;
}
const glbBuffer = new ArrayBuffer(totalLength);
const glbView = new DataView(glbBuffer);
glbView.setUint32(0, 0x46546C67, true);
glbView.setUint32(4, 2, true);
glbView.setUint32(8, totalLength, true);
glbView.setUint32(12, jsonDataLength + jsonPaddingLength, true);
glbView.setUint32(16, 0x4E4F534A, true);
let offset = headerLength + jsonHeaderLength;
new Uint8Array(glbBuffer, offset, jsonDataLength).set(jsonData);
offset += jsonDataLength;
for(let i = 0; i < jsonPaddingLength; i++){
glbView.setUint8(offset + i, 0x20);
}
offset += jsonPaddingLength;
if (binaryDataLength > 0) {
glbView.setUint32(offset, binaryDataLength, true);
glbView.setUint32(offset + 4, 0x004E4942, true);
offset += binaryHeaderLength;
resources.buffers.forEach((buffer)=>{
let src;
const bufferViewId = resources.bufferViewMap.get(buffer)[0];
const bufferOffset = json.bufferViews[bufferViewId].byteOffset;
if (buffer instanceof ArrayBuffer) {
src = new Uint8Array(buffer);
} else {
const srcBuffer = buffer.lock();
if (srcBuffer instanceof ArrayBuffer) {
src = new Uint8Array(srcBuffer);
} else {
src = new Uint8Array(srcBuffer.buffer, srcBuffer.byteOffset, srcBuffer.byteLength);
}
}
const dst = new Uint8Array(glbBuffer, offset + bufferOffset, src.byteLength);
dst.set(src);
});
}
return Promise.resolve(glbBuffer);
});
}
}
export { GltfExporter };