webgl-gltf
Version:
GLTF 2.0 loader for WebGL
309 lines • 12.9 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { mat4, quat, vec3, vec4 } from 'gl-matrix';
import { createMat4FromArray, applyRotationFromQuat } from './mat';
const accessorSizes = {
'SCALAR': 1,
'VEC2': 2,
'VEC3': 3,
'VEC4': 4,
'MAT2': 4,
'MAT3': 9,
'MAT4': 16
};
export var BufferType;
(function (BufferType) {
BufferType[BufferType["Float"] = 5126] = "Float";
BufferType[BufferType["Short"] = 5123] = "Short";
})(BufferType || (BufferType = {}));
const getBuffer = (path, buffer) => __awaiter(void 0, void 0, void 0, function* () {
const dir = path.split('/').slice(0, -1).join('/');
const response = yield fetch(`${dir}/${buffer}`);
return yield response.arrayBuffer();
});
const getTexture = (gl, uri) => __awaiter(void 0, void 0, void 0, function* () {
return new Promise(resolve => {
const img = new Image();
img.onload = () => {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
const ext = gl.getExtension('EXT_texture_filter_anisotropic');
if (ext) {
const max = gl.getParameter(ext.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
gl.texParameterf(gl.TEXTURE_2D, ext.TEXTURE_MAX_ANISOTROPY_EXT, max);
}
gl.generateMipmap(gl.TEXTURE_2D);
resolve(texture);
};
img.src = uri;
img.crossOrigin = 'undefined';
});
});
const readBufferFromFile = (gltf, buffers, accessor) => {
const bufferView = gltf.bufferViews[accessor.bufferView];
const size = accessorSizes[accessor.type];
const componentType = accessor.componentType;
const type = accessor.type;
const data = componentType == BufferType.Float
? new Float32Array(buffers[bufferView.buffer], (accessor.byteOffset || 0) + (bufferView.byteOffset || 0), accessor.count * size)
: new Int16Array(buffers[bufferView.buffer], (accessor.byteOffset || 0) + (bufferView.byteOffset || 0), accessor.count * size);
return {
size,
data,
type,
componentType,
};
};
const getAccessor = (gltf, mesh, attributeName) => {
const attribute = mesh.primitives[0].attributes[attributeName];
return gltf.accessors[attribute];
};
const getBufferFromName = (gl, gltf, buffers, mesh, name) => {
if (mesh.primitives[0].attributes[name] === undefined) {
return null;
}
const accessor = getAccessor(gltf, mesh, name);
const bufferData = readBufferFromFile(gltf, buffers, accessor);
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, bufferData.data, gl.STATIC_DRAW);
return {
buffer,
size: bufferData.size,
type: bufferData.componentType,
};
};
const loadNodes = (index, node) => {
const transform = mat4.create();
if (node.translation !== undefined)
mat4.translate(transform, transform, vec3.fromValues(node.translation[0], node.translation[1], node.translation[1]));
if (node.rotation !== undefined)
applyRotationFromQuat(transform, node.rotation);
if (node.scale !== undefined)
mat4.scale(transform, transform, vec3.fromValues(node.scale[0], node.scale[1], node.scale[1]));
if (node.matrix !== undefined)
createMat4FromArray(node.matrix);
return {
id: index,
name: node.name,
children: node.children || [],
localBindTransform: transform,
animatedTransform: mat4.create(),
skin: node.skin,
mesh: node.mesh
};
};
const loadAnimation = (gltf, animation, buffers) => {
const channels = animation.channels.map(c => {
const sampler = animation.samplers[c.sampler];
const time = readBufferFromFile(gltf, buffers, gltf.accessors[sampler.input]);
const buffer = readBufferFromFile(gltf, buffers, gltf.accessors[sampler.output]);
return {
node: c.target.node,
type: c.target.path,
time,
buffer,
interpolation: sampler.interpolation ? sampler.interpolation : 'LINEAR',
};
});
const c = {};
channels.forEach((channel) => {
if (c[channel.node] === undefined) {
c[channel.node] = {
translation: [],
rotation: [],
scale: [],
};
}
for (let i = 0; i < channel.time.data.length; i++) {
const size = channel.interpolation === 'CUBICSPLINE' ? channel.buffer.size * 3 : channel.buffer.size;
const offset = channel.interpolation === 'CUBICSPLINE' ? channel.buffer.size : 0;
const transform = channel.type === 'rotation'
? quat.fromValues(channel.buffer.data[i * size + offset], channel.buffer.data[i * size + offset + 1], channel.buffer.data[i * size + offset + 2], channel.buffer.data[i * size + offset + 3])
: vec3.fromValues(channel.buffer.data[i * size + offset], channel.buffer.data[i * size + offset + 1], channel.buffer.data[i * size + offset + 2]);
c[channel.node][channel.type].push({
time: channel.time.data[i],
transform: transform,
type: channel.type,
});
}
});
return c;
};
const loadMesh = (gl, gltf, mesh, buffers) => {
let indices = null;
let elementCount = 0;
if (mesh.primitives[0].indices !== undefined) {
const indexAccessor = gltf.accessors[mesh.primitives[0].indices];
const indexBuffer = readBufferFromFile(gltf, buffers, indexAccessor);
indices = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indices);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexBuffer.data, gl.STATIC_DRAW);
elementCount = indexBuffer.data.length;
}
else {
const accessor = getAccessor(gltf, mesh, 'POSITION');
elementCount = accessor.count;
}
return {
indices,
elementCount,
positions: getBufferFromName(gl, gltf, buffers, mesh, 'POSITION'),
normals: getBufferFromName(gl, gltf, buffers, mesh, 'NORMAL'),
tangents: getBufferFromName(gl, gltf, buffers, mesh, 'TANGENT'),
texCoord: getBufferFromName(gl, gltf, buffers, mesh, 'TEXCOORD_0'),
joints: getBufferFromName(gl, gltf, buffers, mesh, 'JOINTS_0'),
weights: getBufferFromName(gl, gltf, buffers, mesh, 'WEIGHTS_0'),
material: mesh.primitives[0].material,
};
};
const loadMaterial = (gl, material, path, images) => __awaiter(void 0, void 0, void 0, function* () {
const dir = path.split('/').slice(0, -1).join('/');
let baseColorTexture = null;
let metallicRoughnessTexture = null;
let emissiveTexture = null;
let normalTexture = null;
let occlusionTexture = null;
let baseColorFactor = vec4.fromValues(1.0, 1.0, 1.0, 1.0);
let roughnessFactor = 0.0;
let metallicFactor = 1.0;
let emissiveFactor = vec3.fromValues(1.0, 1.0, 1.0);
const pbr = material.pbrMetallicRoughness;
if (pbr) {
if (pbr.baseColorTexture) {
const uri = images[pbr.baseColorTexture.index].uri;
baseColorTexture = yield getTexture(gl, `${dir}/${uri}`);
}
if (pbr.baseColorFactor) {
baseColorFactor = vec4.fromValues(pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3]);
}
if (pbr.metallicRoughnessTexture) {
const uri = images[pbr.metallicRoughnessTexture.index].uri;
metallicRoughnessTexture = yield getTexture(gl, `${dir}/${uri}`);
}
metallicFactor = pbr.metallicFactor !== undefined ? pbr.metallicFactor : 1.0;
roughnessFactor = pbr.roughnessFactor !== undefined ? pbr.roughnessFactor : 1.0;
}
if (material.emissiveTexture) {
const uri = images[material.emissiveTexture.index].uri;
emissiveTexture = yield getTexture(gl, `${dir}/${uri}`);
}
if (material.normalTexture) {
const uri = images[material.normalTexture.index].uri;
normalTexture = yield getTexture(gl, `${dir}/${uri}`);
}
if (material.occlusionTexture) {
const uri = images[material.occlusionTexture.index].uri;
occlusionTexture = yield getTexture(gl, `${dir}/${uri}`);
}
if (material.emissiveFactor) {
emissiveFactor = vec3.fromValues(material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2]);
}
return {
baseColorTexture,
baseColorFactor,
metallicRoughnessTexture,
metallicFactor,
roughnessFactor,
emissiveTexture,
emissiveFactor,
normalTexture,
occlusionTexture,
};
});
/**
* Loads a GLTF model and its assets
* @param gl Web GL context
* @param uri URI to model
*/
const loadModel = (gl, uri) => __awaiter(void 0, void 0, void 0, function* () {
var _a;
const response = yield fetch(uri);
const gltf = yield response.json();
if (gltf.accessors === undefined || gltf.accessors.length === 0) {
throw new Error('GLTF File is missing accessors');
}
const buffers = yield Promise.all(gltf.buffers.map((b) => __awaiter(void 0, void 0, void 0, function* () { return yield getBuffer(uri, b.uri); })));
const scene = gltf.scenes[gltf.scene || 0];
const meshes = gltf.meshes.map(m => loadMesh(gl, gltf, m, buffers));
const materials = gltf.materials ? yield Promise.all(gltf.materials.map((m) => __awaiter(void 0, void 0, void 0, function* () { return yield loadMaterial(gl, m, uri, gltf.images); }))) : [];
const rootNode = scene.nodes[0];
const nodes = gltf.nodes.map((n, i) => loadNodes(i, n));
const animations = {};
(_a = gltf.animations) === null || _a === void 0 ? void 0 : _a.forEach(anim => animations[anim.name] = loadAnimation(gltf, anim, buffers));
const skins = gltf.skins ? gltf.skins.map(x => {
const bindTransforms = readBufferFromFile(gltf, buffers, gltf.accessors[x.inverseBindMatrices]);
const inverseBindTransforms = x.joints.map((_, i) => createMat4FromArray(bindTransforms.data.slice(i * 16, i * 16 + 16)));
return {
joints: x.joints,
inverseBindTransforms,
};
}) : [];
const name = uri.split('/').slice(-1)[0];
return {
name,
meshes,
nodes,
rootNode,
animations,
skins,
materials,
};
});
/**
* Deletes GL buffers and textures
* @param gl Web GL context
* @param model Model to dispose
*/
const dispose = (gl, model) => {
model.meshes.forEach(m => {
gl.deleteBuffer(m.indices);
if (m.joints)
gl.deleteBuffer(m.joints.buffer);
if (m.normals)
gl.deleteBuffer(m.normals.buffer);
if (m.positions)
gl.deleteBuffer(m.positions.buffer);
if (m.tangents)
gl.deleteBuffer(m.tangents.buffer);
if (m.texCoord)
gl.deleteBuffer(m.texCoord.buffer);
if (m.weights)
gl.deleteBuffer(m.weights.buffer);
m.indices = null;
m.joints = null;
m.normals = null;
m.tangents = null;
m.texCoord = null;
m.weights = null;
});
model.materials.forEach(m => {
if (m.baseColorTexture)
gl.deleteTexture(m.baseColorTexture);
if (m.emissiveTexture)
gl.deleteTexture(m.emissiveTexture);
if (m.normalTexture)
gl.deleteTexture(m.normalTexture);
if (m.occlusionTexture)
gl.deleteTexture(m.occlusionTexture);
if (m.metallicRoughnessTexture)
gl.deleteTexture(m.metallicRoughnessTexture);
m.baseColorTexture = null;
m.emissiveTexture = null;
m.normalTexture = null;
m.occlusionTexture = null;
m.metallicRoughnessTexture = null;
});
};
export { loadModel, dispose, };
//# sourceMappingURL=gltf.js.map