lazy-gltf-loader
Version:
一个能让 GLTF 局部懒加载的 Loader
269 lines (221 loc) • 7.39 kB
JavaScript
import { Matrix4, Skeleton, Group } from 'three';
import {
GLTFLoader,
GLTFParser,
assignExtrasToUserData,
addUnknownExtensionsToUserData,
} from './GLTFLoader.js';
export class LazyGLTFLoader extends GLTFLoader {
parse(data, path, onLoad, onError, Parser, cfg) {
super.parse(data, path, onLoad, onError, LazyGLTFParser, cfg);
}
}
export class LazyGLTFParser extends GLTFParser {
constructor(json, options) {
super(json, options);
this.lazyCfg = options.parserCfg || {};
this.lazyCfg.include = this.lazyCfg.include || [];
this.lazyCfg.exclude = this.lazyCfg.exclude || [];
}
// overwrite
parse(onLoad, onError) {
const parser = this;
const json = this.json;
const extensions = this.extensions;
// Clear the loader cache
this.cache.removeAll();
this.lazyLoadFns = new Map();
this.lazyGLTFAnimations = [];
// Mark the special nodes/meshes in json for efficient parse
this._invokeAll(function (ext) {
return ext._markDefs && ext._markDefs();
});
Promise.all(
this._invokeAll(function (ext) {
return ext.beforeRoot && ext.beforeRoot();
}),
)
.then(function () {
return Promise.all([
parser.getDependencies('scene'),
parser.getDependencies('camera'),
]);
})
.then(function (dependencies) {
const result = {
scene: dependencies[0][json.scene || 0],
scenes: dependencies[0],
cameras: dependencies[1],
animations: parser.lazyGLTFAnimations,
asset: json.asset,
parser: parser,
userData: {},
};
addUnknownExtensionsToUserData(extensions, result, json);
assignExtrasToUserData(result, json);
Promise.all(
parser._invokeAll(function (ext) {
return ext.afterRoot && ext.afterRoot(result);
}),
).then(function () {
onLoad(result);
});
})
.catch(onError);
}
loadScene(sceneIndex) {
const json = this.json;
const extensions = this.extensions;
const sceneDef = this.json.scenes[sceneIndex];
const parser = this;
// Loader returns Group, not Scene.
// See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172
const scene = new Group();
if (sceneDef.name) scene.name = parser.createUniqueName(sceneDef.name);
assignExtrasToUserData(scene, sceneDef);
if (sceneDef.extensions)
addUnknownExtensionsToUserData(extensions, scene, sceneDef);
const pending = [];
if (sceneDef.nodes)
buildNodeChild(pending, sceneDef.nodes, scene, json, parser);
return Promise.all(pending).then(function () {
return scene;
});
}
// api
getLazyNodeParent(name) {
const action = this.lazyLoadFns.get(name);
return action ? action.parent : null;
}
lazyNode(name, autoMount = true) {
const action = this.lazyLoadFns.get(name);
return action
? action.load().then(node => {
autoMount && action.parent.add(node);
return node;
})
: Promise.reject('no exist');
}
lazyNodes(names, autoMount = true) {
return Promise.all(names.map(name => this.lazyNode(name, false))).then(
nodes => {
autoMount &&
nodes.forEach((node, i) => {
const action = this.lazyLoadFns.get(names[i]);
action.parent.add(node);
});
return nodes;
},
);
}
lazyAnimation(name) {
const animations = this.json.animations;
const loaded = this.lazyGLTFAnimations.filter(i => i.name === name);
if (loaded.length) return Promise.resolve(loaded[0]);
for (let i = 0, il = animations.length; i < il; i++) {
if (name === animations[i].name) {
return this.loadAnimation(i).then(animation => {
this.lazyGLTFAnimations.push(animation);
return animation;
});
}
}
}
lazyAnimations(names) {
return Promise.all(names.map(name => this.lazyAnimation(name)));
}
dispose() {
this.lazyLoadFns.clear();
this.lazyGLTFAnimations.length = 0;
}
}
function buildNodeHierachy(nodeId, parentObject, json, parser, lazy) {
const nodeDef = json.nodes[nodeId];
return parser
.getDependency('node', nodeId)
.then(function (node) {
if (nodeDef.skin === undefined) return node;
// build skeleton here as well
let skinEntry;
return parser
.getDependency('skin', nodeDef.skin)
.then(function (skin) {
skinEntry = skin;
const pendingJoints = [];
for (let i = 0, il = skinEntry.joints.length; i < il; i++) {
pendingJoints.push(
parser.getDependency('node', skinEntry.joints[i]),
);
}
return Promise.all(pendingJoints);
})
.then(function (jointNodes) {
node.traverse(function (mesh) {
if (!mesh.isMesh) return;
const bones = [];
const boneInverses = [];
for (let j = 0, jl = jointNodes.length; j < jl; j++) {
const jointNode = jointNodes[j];
if (jointNode) {
bones.push(jointNode);
const mat = new Matrix4();
if (skinEntry.inverseBindMatrices !== undefined) {
mat.fromArray(skinEntry.inverseBindMatrices.array, j * 16);
}
boneInverses.push(mat);
} else {
console.warn(
'THREE.GLTFLoader: Joint "%s" could not be found.',
skinEntry.joints[j],
);
}
}
mesh.bind(new Skeleton(bones, boneInverses), mesh.matrixWorld);
});
return node;
});
})
.then(function (node) {
// build node hierachy
!lazy && parentObject.add(node);
const pending = [];
if (nodeDef.children)
buildNodeChild(pending, nodeDef.children, node, json, parser);
return Promise.all(pending).then(() => node);
});
}
function buildNodeChild(pending, children, parent, json, parser) {
const { include, exclude } = parser.lazyCfg;
const [includeMatched, excludeMatched] = children.reduce((acc, curr) => {
acc[0] = acc[0] || include.includes(json.nodes[curr].name);
acc[1] = acc[1] || exclude.includes(json.nodes[curr].name);
return acc;
}, []);
for (let i = 0, il = children.length; i < il; i++) {
const childNodeId = children[i];
const childDef = json.nodes[childNodeId];
const included = include.includes(childDef.name);
const excluded = exclude.includes(childDef.name);
if (
(!includeMatched && !excludeMatched) ||
(includeMatched && included) ||
(excludeMatched && !excluded)
) {
const promise = buildNodeHierachy(childNodeId, parent, json, parser);
pending.push(promise);
if (includeMatched || excludeMatched)
parser.lazyLoadFns.set(childDef.name, { parent, load: () => promise });
} else {
const action = {
parent,
load() {
action.promise =
action.promise ||
buildNodeHierachy(childNodeId, parent, json, parser, true);
return action.promise;
},
};
parser.lazyLoadFns.set(childDef.name, action);
}
}
}