playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
287 lines (286 loc) • 9.51 kB
JavaScript
import { GraphNode } from "../../scene/graph-node.js";
import { MeshInstance } from "../../scene/mesh-instance.js";
import { Model } from "../../scene/model.js";
import { MorphInstance } from "../../scene/morph-instance.js";
import { SkinInstance } from "../../scene/skin-instance.js";
import { SkinInstanceCache } from "../../scene/skin-instance-cache.js";
import { Entity } from "../entity.js";
import { Asset } from "../asset/asset.js";
import { VertexFormat } from "../../platform/graphics/vertex-format.js";
import { VertexBuffer } from "../../platform/graphics/vertex-buffer.js";
class GlbContainerResource {
constructor(data, asset, assets, defaultMaterial) {
const createAsset = function(type, resource, index) {
const subAsset = GlbContainerResource.createAsset(asset.name, type, resource, index);
assets.add(subAsset);
return subAsset;
};
const renders = [];
for (let i = 0; i < data.renders.length; ++i) {
renders.push(createAsset("render", data.renders[i], i));
}
const materials = [];
for (let i = 0; i < data.materials.length; ++i) {
materials.push(createAsset("material", data.materials[i], i));
}
const animations = [];
for (let i = 0; i < data.animations.length; ++i) {
animations.push(createAsset("animation", data.animations[i], i));
}
this.data = data;
this._model = null;
this._assetName = asset.name;
this._assets = assets;
this._defaultMaterial = defaultMaterial;
this.renders = renders;
this.materials = materials;
this.textures = data.textures;
this.animations = animations;
}
get model() {
if (!this._model) {
const model = GlbContainerResource.createModel(this.data, this._defaultMaterial);
const modelAsset = GlbContainerResource.createAsset(this._assetName, "model", model, 0);
this._assets.add(modelAsset);
this._model = modelAsset;
}
return this._model;
}
static createAsset(assetName, type, resource, index) {
const subAsset = new Asset(`${assetName}/${type}/${index}`, type, {
url: ""
});
subAsset.resource = resource;
subAsset.loaded = true;
return subAsset;
}
instantiateModelEntity(options) {
const entity = new Entity(void 0, this._assets._loader._app);
entity.addComponent("model", Object.assign({ type: "asset", asset: this.model }, options));
return entity;
}
instantiateRenderEntity(options) {
const defaultMaterial = this._defaultMaterial;
const skinnedMeshInstances = [];
const createMeshInstance = function(root, entity, mesh, materials, meshDefaultMaterials, skins, gltfNode, nodeInstancingMap) {
const materialIndex = meshDefaultMaterials[mesh.id];
const material = materialIndex === void 0 ? defaultMaterial : materials[materialIndex];
const meshInstance = new MeshInstance(mesh, material);
if (mesh.morph) {
meshInstance.morphInstance = new MorphInstance(mesh.morph);
}
if (gltfNode.hasOwnProperty("skin")) {
skinnedMeshInstances.push({
meshInstance,
rootBone: root,
entity
});
}
const instData = nodeInstancingMap.get(gltfNode);
if (instData) {
const matrices = instData.matrices;
const vbFormat = VertexFormat.getDefaultInstancingFormat(mesh.device);
const vb = new VertexBuffer(mesh.device, vbFormat, matrices.length / 16, {
data: matrices
});
meshInstance.setInstancing(vb);
meshInstance.instancingData._destroyVertexBuffer = true;
}
return meshInstance;
};
const cloneHierarchy = (root, node, glb) => {
const entity = new Entity(void 0, this._assets._loader._app);
node._cloneInternal(entity);
if (!root) root = entity;
let attachedMi = null;
let renderAsset = null;
for (let i = 0; i < glb.nodes.length; i++) {
const glbNode = glb.nodes[i];
if (glbNode === node) {
const gltfNode = glb.gltf.nodes[i];
if (gltfNode.hasOwnProperty("mesh")) {
const meshGroup = glb.renders[gltfNode.mesh].meshes;
renderAsset = this.renders[gltfNode.mesh];
for (let mi = 0; mi < meshGroup.length; mi++) {
const mesh = meshGroup[mi];
if (mesh) {
const cloneMi = createMeshInstance(root, entity, mesh, glb.materials, glb.meshDefaultMaterials, glb.skins, gltfNode, glb.nodeInstancingMap);
if (!attachedMi) {
attachedMi = [];
}
attachedMi.push(cloneMi);
}
}
}
if (glb.lights) {
const lightEntity = glb.lights.get(gltfNode);
if (lightEntity) {
entity.addChild(lightEntity.clone());
}
}
if (glb.cameras) {
const cameraEntity = glb.cameras.get(gltfNode);
if (cameraEntity) {
cameraEntity.camera.system.cloneComponent(cameraEntity, entity);
}
}
}
}
if (attachedMi) {
entity.addComponent("render", Object.assign({
type: "asset",
meshInstances: attachedMi
}, options));
entity.render.assignAsset(renderAsset);
}
const children = node.children;
for (let i = 0; i < children.length; i++) {
const childClone = cloneHierarchy(root, children[i], glb);
entity.addChild(childClone);
}
return entity;
};
const sceneClones = [];
for (const scene of this.data.scenes) {
sceneClones.push(cloneHierarchy(null, scene, this.data));
}
skinnedMeshInstances.forEach((data) => {
data.meshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(data.meshInstance.mesh.skin, data.rootBone, data.entity);
data.meshInstance.node.render.rootBone = data.rootBone;
});
return GlbContainerResource.createSceneHierarchy(sceneClones, Entity);
}
// get material variants
getMaterialVariants() {
return this.data.variants ? Object.keys(this.data.variants) : [];
}
// apply material variant to entity
applyMaterialVariant(entity, name) {
const variant = name ? this.data.variants[name] : null;
if (variant === void 0) {
return;
}
const renders = entity.findComponents("render");
for (let i = 0; i < renders.length; i++) {
const renderComponent = renders[i];
this._applyMaterialVariant(variant, renderComponent.meshInstances);
}
}
// apply material variant to mesh instances
applyMaterialVariantInstances(instances, name) {
const variant = name ? this.data.variants[name] : null;
if (variant === void 0) {
return;
}
this._applyMaterialVariant(variant, instances);
}
// internally apply variant to instances
_applyMaterialVariant(variant, instances) {
instances.forEach((instance) => {
if (variant === null) {
instance.material = this._defaultMaterial;
} else {
const meshVariants = this.data.meshVariants[instance.mesh.id];
if (meshVariants) {
instance.material = this.data.materials[meshVariants[variant]];
}
}
});
}
// helper function to create a single hierarchy from an array of nodes
static createSceneHierarchy(sceneNodes, nodeType) {
let root = null;
if (sceneNodes.length === 1) {
root = sceneNodes[0];
} else {
root = new nodeType("SceneGroup");
for (const scene of sceneNodes) {
root.addChild(scene);
}
}
return root;
}
// create a pc.Model from the parsed GLB data structures
static createModel(glb, defaultMaterial) {
const createMeshInstance = function(model2, mesh, skins, skinInstances2, materials, node, gltfNode) {
const materialIndex = glb.meshDefaultMaterials[mesh.id];
const material = materialIndex === void 0 ? defaultMaterial : materials[materialIndex];
const meshInstance = new MeshInstance(mesh, material, node);
if (mesh.morph) {
const morphInstance = new MorphInstance(mesh.morph);
meshInstance.morphInstance = morphInstance;
model2.morphInstances.push(morphInstance);
}
if (gltfNode.hasOwnProperty("skin")) {
const skinIndex = gltfNode.skin;
const skin = skins[skinIndex];
mesh.skin = skin;
const skinInstance = skinInstances2[skinIndex];
meshInstance.skinInstance = skinInstance;
model2.skinInstances.push(skinInstance);
}
model2.meshInstances.push(meshInstance);
};
const model = new Model();
const skinInstances = [];
for (const skin of glb.skins) {
const skinInstance = new SkinInstance(skin);
skinInstance.bones = skin.bones;
skinInstances.push(skinInstance);
}
model.graph = GlbContainerResource.createSceneHierarchy(glb.scenes, GraphNode);
for (let i = 0; i < glb.nodes.length; i++) {
const node = glb.nodes[i];
if (node.root === model.graph) {
const gltfNode = glb.gltf.nodes[i];
if (gltfNode.hasOwnProperty("mesh")) {
const meshGroup = glb.renders[gltfNode.mesh].meshes;
for (let mi = 0; mi < meshGroup.length; mi++) {
const mesh = meshGroup[mi];
if (mesh) {
createMeshInstance(model, mesh, glb.skins, skinInstances, glb.materials, node, gltfNode);
}
}
}
}
}
return model;
}
destroy() {
const registry = this._assets;
const destroyAsset = function(asset) {
registry.remove(asset);
asset.unload();
};
const destroyAssets = function(assets) {
assets.forEach((asset) => {
destroyAsset(asset);
});
};
if (this.animations) {
destroyAssets(this.animations);
this.animations = null;
}
if (this.textures) {
destroyAssets(this.textures);
this.textures = null;
}
if (this.materials) {
destroyAssets(this.materials);
this.materials = null;
}
if (this.renders) {
destroyAssets(this.renders);
this.renders = null;
}
if (this._model) {
destroyAsset(this._model);
this._model = null;
}
this.data = null;
this.assets = null;
}
}
export {
GlbContainerResource
};