xeokit-viewer
Version:
BIM viewer built on xeokit and Bootstrap
596 lines (533 loc) • 21.4 kB
JavaScript
import {math} from "../../viewer/scene/math/math.js";
import {utils} from "../../viewer/scene/utils.js";
import {core} from "../../viewer/scene/core.js";
import {buildEdgeIndices} from '../../viewer/scene/math/buildEdgeIndices.js';
/**
* @private
*/
class GLTFPerformanceLoader {
constructor(cfg) { // TODO: Loading options fallbacks on loader, eg. handleGLTFNode etc
cfg = cfg || {};
}
load(plugin, performanceModel, src, options, ok, error) {
options = options || {};
loadGLTF(plugin, performanceModel, src, options, function () {
core.scheduleTask(function () {
performanceModel.scene.fire("modelLoaded", performanceModel.id); // FIXME: Assumes listeners know order of these two events
performanceModel.fire("loaded", true, true);
});
if (ok) {
ok();
}
},
function (msg) {
plugin.error(msg);
if (error) {
error(msg);
}
performanceModel.fire("error", msg);
});
}
parse(plugin, performanceModel, gltf, options, ok, error) {
options = options || {};
parseGLTF(plugin, gltf, "", options, performanceModel, function () {
performanceModel.scene.fire("modelLoaded", performanceModel.id); // FIXME: Assumes listeners know order of these two events
performanceModel.fire("loaded", true, true);
if (ok) {
ok();
}
},
function (msg) {
performanceModel.error(msg);
performanceModel.fire("error", msg);
if (error) {
error(msg);
}
});
}
}
const INSTANCE_THRESHOLD = 1;
var loadGLTF = (function () {
return function (plugin, performanceModel, src, options, ok, error) {
var spinner = plugin.viewer.scene.canvas.spinner;
spinner.processes++;
plugin.dataSource.getGLTF(src, function (json) { // OK
spinner.processes--;
parseGLTF(plugin, json, src, options, performanceModel, ok, error);
},
error);
};
function getBasePath(src) {
var i = src.lastIndexOf("/");
return (i !== 0) ? src.substring(0, i + 1) : "";
}
})();
var parseGLTF = (function () {
const WEBGL_COMPONENT_TYPES = {
5120: Int8Array,
5121: Uint8Array,
5122: Int16Array,
5123: Uint16Array,
5125: Uint32Array,
5126: Float32Array
};
const WEBGL_TYPE_SIZES = {
'SCALAR': 1,
'VEC2': 2,
'VEC3': 3,
'VEC4': 4,
'MAT2': 4,
'MAT3': 9,
'MAT4': 16
};
return function (plugin, json, src, options, performanceModel, ok) {
var ctx = {
src: src,
loadBuffer: options.loadBuffer,
prioritizeGLTFNode: options.prioritizeGLTFNode,
handleGLTFNode: options.handleGLTFNode,
json: json,
scene: performanceModel.scene,
plugin: plugin,
performanceModel: performanceModel,
numObjects: 0,
nodes: []
};
var spinner = plugin.viewer.scene.canvas.spinner;
spinner.processes++;
loadBuffers(ctx, function () {
loadBufferViews(ctx);
freeBuffers(ctx); // Don't need buffers once we've created views of them
loadMaterials(ctx);
spinner.processes--;
loadDefaultScene(ctx, ok);
});
};
function loadBuffers(ctx, ok) {
var buffers = ctx.json.buffers;
if (buffers) {
var numToLoad = buffers.length;
for (var i = 0, len = buffers.length; i < len; i++) {
loadBuffer(ctx, buffers[i], function () {
if (--numToLoad === 0) {
ok();
}
}, function (msg) {
ctx.plugin.error(msg);
if (--numToLoad === 0) {
ok();
}
});
}
} else {
ok();
}
}
function loadBuffer(ctx, bufferInfo, ok, err) {
var uri = bufferInfo.uri;
if (uri) {
ctx.plugin.dataSource.getArrayBuffer(ctx.src, uri, function (data) {
bufferInfo._buffer = data;
ok();
},
err);
} else {
err('gltf/handleBuffer missing uri in ' + JSON.stringify(bufferInfo));
}
}
function loadBufferViews(ctx) {
var bufferViewsInfo = ctx.json.bufferViews;
if (bufferViewsInfo) {
for (var i = 0, len = bufferViewsInfo.length; i < len; i++) {
loadBufferView(ctx, bufferViewsInfo[i]);
}
}
}
function loadBufferView(ctx, bufferViewInfo) {
var buffer = ctx.json.buffers[bufferViewInfo.buffer];
bufferViewInfo._typedArray = null;
var byteLength = bufferViewInfo.byteLength || 0;
var byteOffset = bufferViewInfo.byteOffset || 0;
bufferViewInfo._buffer = buffer._buffer.slice(byteOffset, byteOffset + byteLength);
}
function freeBuffers(ctx) {
var buffers = ctx.json.buffers;
if (buffers) {
for (var i = 0, len = buffers.length; i < len; i++) {
buffers[i]._buffer = null;
}
}
}
function loadMaterials(ctx) {
var materialsInfo = ctx.json.materials;
if (materialsInfo) {
var materialInfo;
var material;
for (var i = 0, len = materialsInfo.length; i < len; i++) {
materialInfo = materialsInfo[i];
material = loadMaterialColorize(ctx, materialInfo);
materialInfo._rgbaColor = material;
}
}
}
function loadMaterialColorize(ctx, materialInfo) { // Substitute RGBA for material, to use fast flat shading instead
var json = ctx.json;
var colorize = new Float32Array([1, 1, 1, 1]);
var extensions = materialInfo.extensions;
if (extensions) {
var specularPBR = extensions["KHR_materials_pbrSpecularGlossiness"];
if (specularPBR) {
var diffuseFactor = specularPBR.diffuseFactor;
if (diffuseFactor !== null && diffuseFactor !== undefined) {
colorize.set(diffuseFactor);
}
}
var common = extensions["KHR_materials_common"];
if (common) {
var technique = common.technique;
var values = common.values || {};
var blinn = technique === "BLINN";
var phong = technique === "PHONG";
var lambert = technique === "LAMBERT";
var diffuse = values.diffuse;
if (diffuse && (blinn || phong || lambert)) {
if (!utils.isString(diffuse)) {
colorize.set(diffuse);
}
}
var transparency = values.transparency;
if (transparency !== null && transparency !== undefined) {
colorize[3] = transparency;
}
var transparent = values.transparent;
if (transparent !== null && transparent !== undefined) {
colorize[3] = transparent;
}
}
}
var metallicPBR = materialInfo.pbrMetallicRoughness;
if (metallicPBR) {
var baseColorFactor = metallicPBR.baseColorFactor;
if (baseColorFactor) {
colorize.set(baseColorFactor);
}
}
return colorize;
}
function loadDefaultScene(ctx, ok) {
var json = ctx.json;
var scene = json.scene || 0;
var defaultSceneInfo = json.scenes[scene];
if (!defaultSceneInfo) {
error(ctx, "glTF has no default scene");
return;
}
preprocessScene(ctx, defaultSceneInfo);
loadScene(ctx, defaultSceneInfo, ok);
}
function preprocessScene(ctx, sceneInfo) {
var nodes = sceneInfo.nodes;
if (!nodes) {
return;
}
var json = ctx.json;
var glTFNode;
for (var i = 0, len = nodes.length; i < len; i++) {
glTFNode = json.nodes[nodes[i]];
if (!glTFNode) {
error(ctx, "Node not found: " + i);
continue;
}
countMeshUsage(ctx, i, glTFNode);
}
for (var i = 0, len = nodes.length; i < len; i++) {
glTFNode = json.nodes[nodes[i]];
if (glTFNode) {
preprocessNode(ctx, i, glTFNode, null);
}
}
ctx.nodes.sort(function (node1, node2) {
return node1.priority - node2.priority;
})
}
function preprocessNode(ctx, nodeIdx, glTFNode, matrix) {
var priority = 0;
if (ctx.prioritizeGLTFNode) {
priority = ctx.prioritizeGLTFNode(ctx.performanceModel.id, glTFNode);
if (priority === undefined || priority === null) {
return;
}
}
var json = ctx.json;
var localMatrix;
if (glTFNode.matrix) {
localMatrix = glTFNode.matrix;
if (matrix) {
matrix = math.mulMat4(matrix, localMatrix, math.mat4());
} else {
matrix = localMatrix;
}
}
if (glTFNode.translation) {
localMatrix = math.translationMat4v(glTFNode.translation);
if (matrix) {
matrix = math.mulMat4(matrix, localMatrix, math.mat4());
} else {
matrix = localMatrix;
}
}
if (glTFNode.rotation) {
localMatrix = math.quaternionToMat4(glTFNode.rotation);
if (matrix) {
matrix = math.mulMat4(matrix, localMatrix, math.mat4());
} else {
matrix = localMatrix;
}
}
if (glTFNode.scale) {
localMatrix = math.scalingMat4v(glTFNode.scale);
if (matrix) {
matrix = math.mulMat4(matrix, localMatrix, math.mat4());
} else {
matrix = localMatrix;
}
}
if (glTFNode.mesh !== undefined) {
const meshInfo = json.meshes[glTFNode.mesh];
if (meshInfo) {
glTFNode.worldMatrix = matrix ? matrix.slice() : math.identityMat4();
glTFNode.priority = priority;
ctx.nodes.push(glTFNode);
}
}
if (glTFNode.children) {
var children = glTFNode.children;
var childNodeInfo;
var childNodeIdx;
for (var i = 0, len = children.length; i < len; i++) {
childNodeIdx = children[i];
childNodeInfo = json.nodes[childNodeIdx];
if (!childNodeInfo) {
error(ctx, "Node not found: " + i);
continue;
}
preprocessNode(ctx, nodeIdx, childNodeInfo, matrix);
}
}
}
function loadScene(ctx, sceneInfo, ok) {
const nodes = sceneInfo.nodes;
if (!nodes) {
return;
}
const json = ctx.json;
var glTFNode;
for (var i = 0, len = nodes.length; i < len; i++) {
glTFNode = json.nodes[nodes[i]];
if (!glTFNode) {
error(ctx, "Node not found: " + i);
continue;
}
countMeshUsage(ctx, glTFNode);
}
var priority = null;
var tileId = null;
var tileOpen = false;
var nodei = 0;
var spinnerShowing = true;
ctx.plugin.viewer.scene.canvas.spinner.processes++;
function nextPriority() {
for (var i = nodei, len = ctx.nodes.length; i < len; i++) {
const glTFNode = ctx.nodes[i];
if (priority !== glTFNode.priority) {
if (tileId !== null) {
ctx.performanceModel.finalizeTile(tileId);
if (spinnerShowing) {
ctx.plugin.viewer.scene.canvas.spinner.processes--;
spinnerShowing = false;
}
}
nodei = i;
tileId = "" + glTFNode.priority;
ctx.performanceModel.createTile({
id: tileId
});
priority = glTFNode.priority;
tileOpen = true;
setTimeout(nextPriority, 100);
return;
}
loadNode(ctx, glTFNode, tileId);
}
if (tileOpen) {
ctx.performanceModel.finalizeTile(tileId);
}
if (spinnerShowing) {
ctx.plugin.viewer.scene.canvas.spinner.processes--;
spinnerShowing = false;
}
ok();
}
nextPriority();
}
function countMeshUsage(ctx, glTFNode) {
var json = ctx.json;
var mesh = glTFNode.mesh;
if (mesh !== undefined) {
var meshInfo = json.meshes[glTFNode.mesh];
if (meshInfo) {
meshInfo.instances = meshInfo.instances ? meshInfo.instances + 1 : 1;
}
}
if (glTFNode.children) {
var children = glTFNode.children;
var childNodeInfo;
var childNodeIdx;
for (var i = 0, len = children.length; i < len; i++) {
childNodeIdx = children[i];
childNodeInfo = json.nodes[childNodeIdx];
if (!childNodeInfo) {
error(ctx, "Node not found: " + i);
continue;
}
countMeshUsage(ctx, childNodeInfo);
}
}
}
function loadNode(ctx, glTFNode, tileId) {
var createEntity;
if (ctx.handleGLTFNode) {
var actions = {};
if (!ctx.handleGLTFNode(ctx.performanceModel.id, glTFNode, actions)) {
return;
}
if (actions.createEntity) {
createEntity = actions.createEntity;
}
}
var json = ctx.json;
var performanceModel = ctx.performanceModel;
if (glTFNode.mesh !== undefined) {
const meshInfo = json.meshes[glTFNode.mesh];
if (meshInfo) {
const numPrimitives = meshInfo.primitives.length;
if (numPrimitives > 0) {
const meshIds = [];
for (var i = 0; i < numPrimitives; i++) {
const meshCfg = {
id: performanceModel.id + "." + ctx.numObjects++,
tileId: tileId,
matrix: glTFNode.worldMatrix
};
const primitiveInfo = meshInfo.primitives[i];
const materialIndex = primitiveInfo.material;
var materialInfo;
if (materialIndex !== null && materialIndex !== undefined) {
materialInfo = json.materials[materialIndex];
}
if (materialInfo) {
meshCfg.color = materialInfo._rgbaColor;
meshCfg.opacity = materialInfo._rgbaColor[3];
} else {
meshCfg.color = new Float32Array([1.0, 1.0, 1.0]);
meshCfg.opacity = 1.0;
}
if (createEntity) {
if (createEntity.colorize) {
meshCfg.color = createEntity.colorize;
}
if (createEntity.opacity !== undefined && createEntity.opacity !== null) {
meshCfg.opacity = createEntity.opacity;
}
}
if (meshInfo.instances > INSTANCE_THRESHOLD) {
//------------------------------------------------------------------
// Instancing
//------------------------------------------------------------------
const geometryId = performanceModel.id + "." + glTFNode.mesh + "." + i;
if (!primitiveInfo.tilesGeometryIds) {
primitiveInfo.tilesGeometryIds = {};
}
var tileGeometryIds = primitiveInfo.tilesGeometryIds[tileId];
if (!tileGeometryIds) {
tileGeometryIds = primitiveInfo.tilesGeometryIds[tileId] = {};
}
if (tileGeometryIds[geometryId] === undefined) { // Ensures we only load each primitive mesh once
tileGeometryIds[geometryId] = geometryId;
const geometryCfg = {
id: geometryId,
tileId: tileId
};
loadPrimitiveGeometry(ctx, primitiveInfo, geometryCfg);
performanceModel.createGeometry(geometryCfg);
}
meshCfg.geometryId = geometryId;
performanceModel.createMesh(meshCfg);
meshIds.push(meshCfg.id);
} else {
//------------------------------------------------------------------
// Batching
//------------------------------------------------------------------
loadPrimitiveGeometry(ctx, primitiveInfo, meshCfg);
performanceModel.createMesh(meshCfg);
meshIds.push(meshCfg.id);
}
}
if (createEntity) {
performanceModel.createEntity(utils.apply(createEntity, {
tileId: tileId,
meshIds: meshIds
}));
} else {
performanceModel.createEntity({
tileId: tileId,
meshIds: meshIds
});
}
}
}
}
}
function loadPrimitiveGeometry(ctx, primitiveInfo, geometryCfg) {
var attributes = primitiveInfo.attributes;
if (!attributes) {
return;
}
geometryCfg.primitive = "triangles";
var indicesIndex = primitiveInfo.indices;
if (indicesIndex !== null && indicesIndex !== undefined) {
const accessorInfo = ctx.json.accessors[indicesIndex];
geometryCfg.indices = loadAccessorTypedArray(ctx, accessorInfo);
}
var positionsIndex = attributes.POSITION;
if (positionsIndex !== null && positionsIndex !== undefined) {
const accessorInfo = ctx.json.accessors[positionsIndex];
geometryCfg.positions = loadAccessorTypedArray(ctx, accessorInfo);
// scalePositionsArray(geometryCfg.positions);
}
var normalsIndex = attributes.NORMAL;
if (normalsIndex !== null && normalsIndex !== undefined) {
const accessorInfo = ctx.json.accessors[normalsIndex];
geometryCfg.normals = loadAccessorTypedArray(ctx, accessorInfo);
}
if (geometryCfg.indices) {
geometryCfg.edgeIndices = buildEdgeIndices(geometryCfg.positions, geometryCfg.indices, null, 10); // Save PerformanceModel from building edges
}
}
function loadAccessorTypedArray(ctx, accessorInfo) {
var bufferViewInfo = ctx.json.bufferViews[accessorInfo.bufferView];
var itemSize = WEBGL_TYPE_SIZES[accessorInfo.type];
var TypedArray = WEBGL_COMPONENT_TYPES[accessorInfo.componentType];
var elementBytes = TypedArray.BYTES_PER_ELEMENT; // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12.
var itemBytes = elementBytes * itemSize;
if (accessorInfo.byteStride && accessorInfo.byteStride !== itemBytes) { // The buffer is not interleaved if the stride is the item size in bytes.
error("interleaved buffer!"); // TODO
} else {
return new TypedArray(bufferViewInfo._buffer, accessorInfo.byteOffset || 0, accessorInfo.count * itemSize);
}
}
function error(ctx, msg) {
ctx.plugin.error(msg);
}
})();
export {GLTFPerformanceLoader}