UNPKG

@babylonjs/loaders

Version:

For usage documentation please visit https://doc.babylonjs.com/features/featuresDeepDive/importers/loadingFileTypes/.

1,296 lines (1,295 loc) 73.2 kB
import { EParameterType, ETextureFilterType, ECullingType, EBlendingFunction, EShaderType } from "./glTFLoaderInterfaces.js"; import { Quaternion, Vector3, Matrix } from "@babylonjs/core/Maths/math.vector.js"; import { Color3 } from "@babylonjs/core/Maths/math.color.js"; import { Tools } from "@babylonjs/core/Misc/tools.js"; import { Camera } from "@babylonjs/core/Cameras/camera.js"; import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera.js"; import { Animation } from "@babylonjs/core/Animations/animation.js"; import { Bone } from "@babylonjs/core/Bones/bone.js"; import { Skeleton } from "@babylonjs/core/Bones/skeleton.js"; import { Effect } from "@babylonjs/core/Materials/effect.js"; import { Material } from "@babylonjs/core/Materials/material.js"; import { MultiMaterial } from "@babylonjs/core/Materials/multiMaterial.js"; import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial.js"; import { ShaderMaterial } from "@babylonjs/core/Materials/shaderMaterial.js"; import { Texture } from "@babylonjs/core/Materials/Textures/texture.js"; import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData.js"; import { VertexBuffer } from "@babylonjs/core/Buffers/buffer.js"; import { Geometry } from "@babylonjs/core/Meshes/geometry.js"; import { SubMesh } from "@babylonjs/core/Meshes/subMesh.js"; import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh.js"; import { Mesh } from "@babylonjs/core/Meshes/mesh.js"; import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight.js"; import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight.js"; import { PointLight } from "@babylonjs/core/Lights/pointLight.js"; import { SpotLight } from "@babylonjs/core/Lights/spotLight.js"; import { GLTFUtils } from "./glTFLoaderUtils.js"; import { GLTFFileLoader } from "../glTFFileLoader.js"; import { Constants } from "@babylonjs/core/Engines/constants.js"; /** * Tokenizer. Used for shaders compatibility * Automatically map world, view, projection, worldViewProjection, attributes and so on */ // eslint-disable-next-line @typescript-eslint/naming-convention var ETokenType; (function (ETokenType) { ETokenType[ETokenType["IDENTIFIER"] = 1] = "IDENTIFIER"; ETokenType[ETokenType["UNKNOWN"] = 2] = "UNKNOWN"; ETokenType[ETokenType["END_OF_INPUT"] = 3] = "END_OF_INPUT"; })(ETokenType || (ETokenType = {})); class Tokenizer { constructor(toParse) { this._pos = 0; this.currentToken = ETokenType.UNKNOWN; this.currentIdentifier = ""; this.currentString = ""; this.isLetterOrDigitPattern = /^[a-zA-Z0-9]+$/; this._toParse = toParse; this._maxPos = toParse.length; } getNextToken() { if (this.isEnd()) { return ETokenType.END_OF_INPUT; } this.currentString = this.read(); this.currentToken = ETokenType.UNKNOWN; if (this.currentString === "_" || this.isLetterOrDigitPattern.test(this.currentString)) { this.currentToken = ETokenType.IDENTIFIER; this.currentIdentifier = this.currentString; while (!this.isEnd() && (this.isLetterOrDigitPattern.test((this.currentString = this.peek())) || this.currentString === "_")) { this.currentIdentifier += this.currentString; this.forward(); } } return this.currentToken; } peek() { return this._toParse[this._pos]; } read() { return this._toParse[this._pos++]; } forward() { this._pos++; } isEnd() { return this._pos >= this._maxPos; } } /** * Values */ // eslint-disable-next-line @typescript-eslint/naming-convention const glTFTransforms = ["MODEL", "VIEW", "PROJECTION", "MODELVIEW", "MODELVIEWPROJECTION", "JOINTMATRIX"]; const BabylonTransforms = ["world", "view", "projection", "worldView", "worldViewProjection", "mBones"]; // eslint-disable-next-line @typescript-eslint/naming-convention const glTFAnimationPaths = ["translation", "rotation", "scale"]; const BabylonAnimationPaths = ["position", "rotationQuaternion", "scaling"]; /** * Parse * @param parsedBuffers * @param gltfRuntime */ const ParseBuffers = (parsedBuffers, gltfRuntime) => { for (const buf in parsedBuffers) { const parsedBuffer = parsedBuffers[buf]; gltfRuntime.buffers[buf] = parsedBuffer; gltfRuntime.buffersCount++; } }; const ParseShaders = (parsedShaders, gltfRuntime) => { for (const sha in parsedShaders) { const parsedShader = parsedShaders[sha]; gltfRuntime.shaders[sha] = parsedShader; gltfRuntime.shaderscount++; } }; const ParseObject = (parsedObjects, runtimeProperty, gltfRuntime) => { for (const object in parsedObjects) { const parsedObject = parsedObjects[object]; gltfRuntime[runtimeProperty][object] = parsedObject; } }; /** * Utils * @param buffer */ const NormalizeUVs = (buffer) => { if (!buffer) { return; } for (let i = 0; i < buffer.length / 2; i++) { buffer[i * 2 + 1] = 1.0 - buffer[i * 2 + 1]; } }; const GetAttribute = (attributeParameter) => { if (attributeParameter.semantic === "NORMAL") { return "normal"; } else if (attributeParameter.semantic === "POSITION") { return "position"; } else if (attributeParameter.semantic === "JOINT") { return "matricesIndices"; } else if (attributeParameter.semantic === "WEIGHT") { return "matricesWeights"; } else if (attributeParameter.semantic === "COLOR") { return "color"; } else if (attributeParameter.semantic && attributeParameter.semantic.indexOf("TEXCOORD_") !== -1) { const channel = Number(attributeParameter.semantic.split("_")[1]); return "uv" + (channel === 0 ? "" : channel + 1); } return null; }; /** * Loads and creates animations * @param gltfRuntime */ const LoadAnimations = (gltfRuntime) => { for (const anim in gltfRuntime.animations) { const animation = gltfRuntime.animations[anim]; if (!animation.channels || !animation.samplers) { continue; } let lastAnimation = null; for (let i = 0; i < animation.channels.length; i++) { // Get parameters and load buffers const channel = animation.channels[i]; const sampler = animation.samplers[channel.sampler]; if (!sampler) { continue; } let inputData = null; let outputData = null; if (animation.parameters) { inputData = animation.parameters[sampler.input]; outputData = animation.parameters[sampler.output]; } else { inputData = sampler.input; outputData = sampler.output; } const bufferInput = GLTFUtils.GetBufferFromAccessor(gltfRuntime, gltfRuntime.accessors[inputData]); const bufferOutput = GLTFUtils.GetBufferFromAccessor(gltfRuntime, gltfRuntime.accessors[outputData]); const targetId = channel.target.id; let targetNode = gltfRuntime.scene.getNodeById(targetId); if (targetNode === null) { targetNode = gltfRuntime.scene.getNodeByName(targetId); } if (targetNode === null) { Tools.Warn("Creating animation named " + anim + ". But cannot find node named " + targetId + " to attach to"); continue; } const isBone = targetNode instanceof Bone; // Get target path (position, rotation or scaling) let targetPath = channel.target.path; const targetPathIndex = glTFAnimationPaths.indexOf(targetPath); if (targetPathIndex !== -1) { targetPath = BabylonAnimationPaths[targetPathIndex]; } // Determine animation type let animationType = Animation.ANIMATIONTYPE_MATRIX; if (!isBone) { if (targetPath === "rotationQuaternion") { animationType = Animation.ANIMATIONTYPE_QUATERNION; targetNode.rotationQuaternion = new Quaternion(); } else { animationType = Animation.ANIMATIONTYPE_VECTOR3; } } // Create animation and key frames let babylonAnimation = null; const keys = []; let arrayOffset = 0; let modifyKey = false; if (isBone && lastAnimation && lastAnimation.getKeys().length === bufferInput.length) { babylonAnimation = lastAnimation; modifyKey = true; } if (!modifyKey) { gltfRuntime.scene._blockEntityCollection = !!gltfRuntime.assetContainer; babylonAnimation = new Animation(anim, isBone ? "_matrix" : targetPath, 1, animationType, Animation.ANIMATIONLOOPMODE_CYCLE); gltfRuntime.scene._blockEntityCollection = false; } // For each frame for (let j = 0; j < bufferInput.length; j++) { let value = null; if (targetPath === "rotationQuaternion") { // VEC4 value = Quaternion.FromArray([bufferOutput[arrayOffset], bufferOutput[arrayOffset + 1], bufferOutput[arrayOffset + 2], bufferOutput[arrayOffset + 3]]); arrayOffset += 4; } else { // Position and scaling are VEC3 value = Vector3.FromArray([bufferOutput[arrayOffset], bufferOutput[arrayOffset + 1], bufferOutput[arrayOffset + 2]]); arrayOffset += 3; } if (isBone) { const bone = targetNode; let translation = Vector3.Zero(); let rotationQuaternion = new Quaternion(); let scaling = Vector3.Zero(); // Warning on decompose let mat = bone.getBaseMatrix(); if (modifyKey && lastAnimation) { mat = lastAnimation.getKeys()[j].value; } mat.decompose(scaling, rotationQuaternion, translation); if (targetPath === "position") { translation = value; } else if (targetPath === "rotationQuaternion") { rotationQuaternion = value; } else { scaling = value; } value = Matrix.Compose(scaling, rotationQuaternion, translation); } if (!modifyKey) { keys.push({ frame: bufferInput[j], value: value, }); } else if (lastAnimation) { lastAnimation.getKeys()[j].value = value; } } // Finish if (!modifyKey && babylonAnimation) { babylonAnimation.setKeys(keys); targetNode.animations.push(babylonAnimation); } lastAnimation = babylonAnimation; gltfRuntime.scene.stopAnimation(targetNode); gltfRuntime.scene.beginAnimation(targetNode, 0, bufferInput[bufferInput.length - 1], true, 1.0); } } }; /** * @returns the bones transformation matrix * @param node */ const ConfigureBoneTransformation = (node) => { let mat = null; if (node.translation || node.rotation || node.scale) { const scale = Vector3.FromArray(node.scale || [1, 1, 1]); const rotation = Quaternion.FromArray(node.rotation || [0, 0, 0, 1]); const position = Vector3.FromArray(node.translation || [0, 0, 0]); mat = Matrix.Compose(scale, rotation, position); } else { mat = Matrix.FromArray(node.matrix); } return mat; }; /** * Returns the parent bone * @param gltfRuntime * @param skins * @param jointName * @param newSkeleton * @returns the parent bone */ const GetParentBone = (gltfRuntime, skins, jointName, newSkeleton) => { // Try to find for (let i = 0; i < newSkeleton.bones.length; i++) { if (newSkeleton.bones[i].name === jointName) { return newSkeleton.bones[i]; } } // Not found, search in gltf nodes const nodes = gltfRuntime.nodes; for (const nde in nodes) { const node = nodes[nde]; if (!node.jointName) { continue; } const children = node.children; for (let i = 0; i < children.length; i++) { const child = gltfRuntime.nodes[children[i]]; if (!child.jointName) { continue; } if (child.jointName === jointName) { const mat = ConfigureBoneTransformation(node); const bone = new Bone(node.name || "", newSkeleton, GetParentBone(gltfRuntime, skins, node.jointName, newSkeleton), mat); bone.id = nde; return bone; } } } return null; }; /** * Returns the appropriate root node * @param nodesToRoot * @param id * @returns the root node */ const GetNodeToRoot = (nodesToRoot, id) => { for (let i = 0; i < nodesToRoot.length; i++) { const nodeToRoot = nodesToRoot[i]; for (let j = 0; j < nodeToRoot.node.children.length; j++) { const child = nodeToRoot.node.children[j]; if (child === id) { return nodeToRoot.bone; } } } return null; }; /** * Returns the node with the joint name * @param gltfRuntime * @param jointName * @returns the node with the joint name */ const GetJointNode = (gltfRuntime, jointName) => { const nodes = gltfRuntime.nodes; let node = nodes[jointName]; if (node) { return { node: node, id: jointName, }; } for (const nde in nodes) { node = nodes[nde]; if (node.jointName === jointName) { return { node: node, id: nde, }; } } return null; }; /** * Checks if a nodes is in joints * @param skins * @param id * @returns true if the node is in joints, else false */ const NodeIsInJoints = (skins, id) => { for (let i = 0; i < skins.jointNames.length; i++) { if (skins.jointNames[i] === id) { return true; } } return false; }; /** * Fills the nodes to root for bones and builds hierarchy * @param gltfRuntime * @param newSkeleton * @param skins * @param nodesToRoot */ const GetNodesToRoot = (gltfRuntime, newSkeleton, skins, nodesToRoot) => { // Creates nodes for root for (const nde in gltfRuntime.nodes) { const node = gltfRuntime.nodes[nde]; const id = nde; if (!node.jointName || NodeIsInJoints(skins, node.jointName)) { continue; } // Create node to root bone const mat = ConfigureBoneTransformation(node); const bone = new Bone(node.name || "", newSkeleton, null, mat); bone.id = id; nodesToRoot.push({ bone: bone, node: node, id: id }); } // Parenting for (let i = 0; i < nodesToRoot.length; i++) { const nodeToRoot = nodesToRoot[i]; const children = nodeToRoot.node.children; for (let j = 0; j < children.length; j++) { let child = null; for (let k = 0; k < nodesToRoot.length; k++) { if (nodesToRoot[k].id === children[j]) { child = nodesToRoot[k]; break; } } if (child) { child.bone._parent = nodeToRoot.bone; nodeToRoot.bone.children.push(child.bone); } } } }; /** * Imports a skeleton * @param gltfRuntime * @param skins * @param mesh * @param newSkeleton * @returns the bone name */ const ImportSkeleton = (gltfRuntime, skins, mesh, newSkeleton) => { if (!newSkeleton) { newSkeleton = new Skeleton(skins.name || "", "", gltfRuntime.scene); } if (!skins.babylonSkeleton) { return newSkeleton; } // Find the root bones const nodesToRoot = []; const nodesToRootToAdd = []; GetNodesToRoot(gltfRuntime, newSkeleton, skins, nodesToRoot); newSkeleton.bones = []; // Joints for (let i = 0; i < skins.jointNames.length; i++) { const jointNode = GetJointNode(gltfRuntime, skins.jointNames[i]); if (!jointNode) { continue; } const node = jointNode.node; if (!node) { Tools.Warn("Joint named " + skins.jointNames[i] + " does not exist"); continue; } const id = jointNode.id; // Optimize, if the bone already exists... const existingBone = gltfRuntime.scene.getBoneById(id); if (existingBone) { newSkeleton.bones.push(existingBone); continue; } // Search for parent bone let foundBone = false; let parentBone = null; for (let j = 0; j < i; j++) { const jointNode = GetJointNode(gltfRuntime, skins.jointNames[j]); if (!jointNode) { continue; } const joint = jointNode.node; if (!joint) { Tools.Warn("Joint named " + skins.jointNames[j] + " does not exist when looking for parent"); continue; } const children = joint.children; if (!children) { continue; } foundBone = false; for (let k = 0; k < children.length; k++) { if (children[k] === id) { parentBone = GetParentBone(gltfRuntime, skins, skins.jointNames[j], newSkeleton); foundBone = true; break; } } if (foundBone) { break; } } // Create bone const mat = ConfigureBoneTransformation(node); if (!parentBone && nodesToRoot.length > 0) { parentBone = GetNodeToRoot(nodesToRoot, id); if (parentBone) { if (nodesToRootToAdd.indexOf(parentBone) === -1) { nodesToRootToAdd.push(parentBone); } } } const bone = new Bone(node.jointName || "", newSkeleton, parentBone, mat); bone.id = id; } // Polish const bones = newSkeleton.bones; newSkeleton.bones = []; for (let i = 0; i < skins.jointNames.length; i++) { const jointNode = GetJointNode(gltfRuntime, skins.jointNames[i]); if (!jointNode) { continue; } for (let j = 0; j < bones.length; j++) { if (bones[j].id === jointNode.id) { newSkeleton.bones.push(bones[j]); break; } } } newSkeleton.prepare(); // Finish for (let i = 0; i < nodesToRootToAdd.length; i++) { newSkeleton.bones.push(nodesToRootToAdd[i]); } return newSkeleton; }; /** * Imports a mesh and its geometries * @param gltfRuntime * @param node * @param meshes * @param id * @param newMesh * @returns the new mesh */ const ImportMesh = (gltfRuntime, node, meshes, id, newMesh) => { if (!newMesh) { gltfRuntime.scene._blockEntityCollection = !!gltfRuntime.assetContainer; newMesh = new Mesh(node.name || "", gltfRuntime.scene); newMesh._parentContainer = gltfRuntime.assetContainer; gltfRuntime.scene._blockEntityCollection = false; newMesh.id = id; } if (!node.babylonNode) { return newMesh; } const subMaterials = []; let vertexData = null; const verticesStarts = []; const verticesCounts = []; const indexStarts = []; const indexCounts = []; for (let meshIndex = 0; meshIndex < meshes.length; meshIndex++) { const meshId = meshes[meshIndex]; const mesh = gltfRuntime.meshes[meshId]; if (!mesh) { continue; } // Positions, normals and UVs for (let i = 0; i < mesh.primitives.length; i++) { // Temporary vertex data const tempVertexData = new VertexData(); const primitive = mesh.primitives[i]; if (primitive.mode !== 4) { // continue; } const attributes = primitive.attributes; let accessor = null; let buffer = null; // Set positions, normal and uvs for (const semantic in attributes) { // Link accessor and buffer view accessor = gltfRuntime.accessors[attributes[semantic]]; buffer = GLTFUtils.GetBufferFromAccessor(gltfRuntime, accessor); if (semantic === "NORMAL") { tempVertexData.normals = new Float32Array(buffer.length); tempVertexData.normals.set(buffer); } else if (semantic === "POSITION") { if (GLTFFileLoader.HomogeneousCoordinates) { tempVertexData.positions = new Float32Array(buffer.length - buffer.length / 4); for (let j = 0; j < buffer.length; j += 4) { tempVertexData.positions[j] = buffer[j]; tempVertexData.positions[j + 1] = buffer[j + 1]; tempVertexData.positions[j + 2] = buffer[j + 2]; } } else { tempVertexData.positions = new Float32Array(buffer.length); tempVertexData.positions.set(buffer); } verticesCounts.push(tempVertexData.positions.length); } else if (semantic.indexOf("TEXCOORD_") !== -1) { const channel = Number(semantic.split("_")[1]); const uvKind = VertexBuffer.UVKind + (channel === 0 ? "" : channel + 1); const uvs = new Float32Array(buffer.length); uvs.set(buffer); NormalizeUVs(uvs); tempVertexData.set(uvs, uvKind); } else if (semantic === "JOINT") { tempVertexData.matricesIndices = new Float32Array(buffer.length); tempVertexData.matricesIndices.set(buffer); } else if (semantic === "WEIGHT") { tempVertexData.matricesWeights = new Float32Array(buffer.length); tempVertexData.matricesWeights.set(buffer); } else if (semantic === "COLOR") { tempVertexData.colors = new Float32Array(buffer.length); tempVertexData.colors.set(buffer); } } // Indices accessor = gltfRuntime.accessors[primitive.indices]; if (accessor) { buffer = GLTFUtils.GetBufferFromAccessor(gltfRuntime, accessor); tempVertexData.indices = new Int32Array(buffer.length); tempVertexData.indices.set(buffer); indexCounts.push(tempVertexData.indices.length); } else { // Set indices on the fly const indices = []; for (let j = 0; j < tempVertexData.positions.length / 3; j++) { indices.push(j); } tempVertexData.indices = new Int32Array(indices); indexCounts.push(tempVertexData.indices.length); } if (!vertexData) { vertexData = tempVertexData; } else { vertexData.merge(tempVertexData); } // Sub material const material = gltfRuntime.scene.getMaterialById(primitive.material); subMaterials.push(material === null ? GLTFUtils.GetDefaultMaterial(gltfRuntime.scene) : material); // Update vertices start and index start verticesStarts.push(verticesStarts.length === 0 ? 0 : verticesStarts[verticesStarts.length - 1] + verticesCounts[verticesCounts.length - 2]); indexStarts.push(indexStarts.length === 0 ? 0 : indexStarts[indexStarts.length - 1] + indexCounts[indexCounts.length - 2]); } } let material; gltfRuntime.scene._blockEntityCollection = !!gltfRuntime.assetContainer; if (subMaterials.length > 1) { material = new MultiMaterial("multimat" + id, gltfRuntime.scene); material.subMaterials = subMaterials; } else { material = new StandardMaterial("multimat" + id, gltfRuntime.scene); } if (subMaterials.length === 1) { material = subMaterials[0]; } material._parentContainer = gltfRuntime.assetContainer; if (!newMesh.material) { newMesh.material = material; } // Apply geometry new Geometry(id, gltfRuntime.scene, vertexData, false, newMesh); newMesh.computeWorldMatrix(true); gltfRuntime.scene._blockEntityCollection = false; // Apply submeshes newMesh.subMeshes = []; let index = 0; for (let meshIndex = 0; meshIndex < meshes.length; meshIndex++) { const meshId = meshes[meshIndex]; const mesh = gltfRuntime.meshes[meshId]; if (!mesh) { continue; } for (let i = 0; i < mesh.primitives.length; i++) { if (mesh.primitives[i].mode !== 4) { //continue; } SubMesh.AddToMesh(index, verticesStarts[index], verticesCounts[index], indexStarts[index], indexCounts[index], newMesh, newMesh, true); index++; } } // Finish return newMesh; }; /** * Configure node transformation from position, rotation and scaling * @param newNode * @param position * @param rotation * @param scaling */ const ConfigureNode = (newNode, position, rotation, scaling) => { if (newNode.position) { newNode.position = position; } if (newNode.rotationQuaternion || newNode.rotation) { newNode.rotationQuaternion = rotation; } if (newNode.scaling) { newNode.scaling = scaling; } }; /** * Configures node from transformation matrix * @param newNode * @param node */ const ConfigureNodeFromMatrix = (newNode, node) => { if (node.matrix) { const position = new Vector3(0, 0, 0); const rotation = new Quaternion(); const scaling = new Vector3(0, 0, 0); const mat = Matrix.FromArray(node.matrix); mat.decompose(scaling, rotation, position); ConfigureNode(newNode, position, rotation, scaling); } else if (node.translation && node.rotation && node.scale) { ConfigureNode(newNode, Vector3.FromArray(node.translation), Quaternion.FromArray(node.rotation), Vector3.FromArray(node.scale)); } newNode.computeWorldMatrix(true); }; /** * Imports a node * @param gltfRuntime * @param node * @param id * @returns the newly imported node */ const ImportNode = (gltfRuntime, node, id) => { let lastNode = null; if (gltfRuntime.importOnlyMeshes && (node.skin || node.meshes)) { if (gltfRuntime.importMeshesNames && gltfRuntime.importMeshesNames.length > 0 && gltfRuntime.importMeshesNames.indexOf(node.name || "") === -1) { return null; } } // Meshes if (node.skin) { if (node.meshes) { const skin = gltfRuntime.skins[node.skin]; const newMesh = ImportMesh(gltfRuntime, node, node.meshes, id, node.babylonNode); newMesh.skeleton = gltfRuntime.scene.getLastSkeletonById(node.skin); if (newMesh.skeleton === null) { newMesh.skeleton = ImportSkeleton(gltfRuntime, skin, newMesh, skin.babylonSkeleton); if (!skin.babylonSkeleton) { skin.babylonSkeleton = newMesh.skeleton; } } lastNode = newMesh; } } else if (node.meshes) { /** * Improve meshes property */ const newMesh = ImportMesh(gltfRuntime, node, node.mesh ? [node.mesh] : node.meshes, id, node.babylonNode); lastNode = newMesh; } // Lights else if (node.light && !node.babylonNode && !gltfRuntime.importOnlyMeshes) { const light = gltfRuntime.lights[node.light]; if (light) { if (light.type === "ambient") { const ambienLight = light[light.type]; const hemiLight = new HemisphericLight(node.light, Vector3.Zero(), gltfRuntime.scene); hemiLight.name = node.name || ""; if (ambienLight.color) { hemiLight.diffuse = Color3.FromArray(ambienLight.color); } lastNode = hemiLight; } else if (light.type === "directional") { const directionalLight = light[light.type]; const dirLight = new DirectionalLight(node.light, Vector3.Zero(), gltfRuntime.scene); dirLight.name = node.name || ""; if (directionalLight.color) { dirLight.diffuse = Color3.FromArray(directionalLight.color); } lastNode = dirLight; } else if (light.type === "point") { const pointLight = light[light.type]; const ptLight = new PointLight(node.light, Vector3.Zero(), gltfRuntime.scene); ptLight.name = node.name || ""; if (pointLight.color) { ptLight.diffuse = Color3.FromArray(pointLight.color); } lastNode = ptLight; } else if (light.type === "spot") { const spotLight = light[light.type]; const spLight = new SpotLight(node.light, Vector3.Zero(), Vector3.Zero(), 0, 0, gltfRuntime.scene); spLight.name = node.name || ""; if (spotLight.color) { spLight.diffuse = Color3.FromArray(spotLight.color); } if (spotLight.fallOfAngle) { spLight.angle = spotLight.fallOfAngle; } if (spotLight.fallOffExponent) { spLight.exponent = spotLight.fallOffExponent; } lastNode = spLight; } } } // Cameras else if (node.camera && !node.babylonNode && !gltfRuntime.importOnlyMeshes) { const camera = gltfRuntime.cameras[node.camera]; if (camera) { gltfRuntime.scene._blockEntityCollection = !!gltfRuntime.assetContainer; if (camera.type === "orthographic") { const orthoCamera = new FreeCamera(node.camera, Vector3.Zero(), gltfRuntime.scene, false); orthoCamera.name = node.name || ""; orthoCamera.mode = Camera.ORTHOGRAPHIC_CAMERA; orthoCamera.attachControl(); lastNode = orthoCamera; orthoCamera._parentContainer = gltfRuntime.assetContainer; } else if (camera.type === "perspective") { const perspectiveCamera = camera[camera.type]; const persCamera = new FreeCamera(node.camera, Vector3.Zero(), gltfRuntime.scene, false); persCamera.name = node.name || ""; persCamera.attachControl(); if (!perspectiveCamera.aspectRatio) { perspectiveCamera.aspectRatio = gltfRuntime.scene.getEngine().getRenderWidth() / gltfRuntime.scene.getEngine().getRenderHeight(); } if (perspectiveCamera.znear && perspectiveCamera.zfar) { persCamera.maxZ = perspectiveCamera.zfar; persCamera.minZ = perspectiveCamera.znear; } lastNode = persCamera; persCamera._parentContainer = gltfRuntime.assetContainer; } gltfRuntime.scene._blockEntityCollection = false; } } // Empty node if (!node.jointName) { if (node.babylonNode) { return node.babylonNode; } else if (lastNode === null) { gltfRuntime.scene._blockEntityCollection = !!gltfRuntime.assetContainer; const dummy = new Mesh(node.name || "", gltfRuntime.scene); dummy._parentContainer = gltfRuntime.assetContainer; gltfRuntime.scene._blockEntityCollection = false; node.babylonNode = dummy; lastNode = dummy; } } if (lastNode !== null) { if (node.matrix && lastNode instanceof Mesh) { ConfigureNodeFromMatrix(lastNode, node); } else { const translation = node.translation || [0, 0, 0]; const rotation = node.rotation || [0, 0, 0, 1]; const scale = node.scale || [1, 1, 1]; ConfigureNode(lastNode, Vector3.FromArray(translation), Quaternion.FromArray(rotation), Vector3.FromArray(scale)); } lastNode.updateCache(true); node.babylonNode = lastNode; } return lastNode; }; /** * Traverses nodes and creates them * @param gltfRuntime * @param id * @param parent * @param meshIncluded */ const TraverseNodes = (gltfRuntime, id, parent, meshIncluded = false) => { const node = gltfRuntime.nodes[id]; let newNode = null; if (gltfRuntime.importOnlyMeshes && !meshIncluded && gltfRuntime.importMeshesNames) { if (gltfRuntime.importMeshesNames.indexOf(node.name || "") !== -1 || gltfRuntime.importMeshesNames.length === 0) { meshIncluded = true; } else { meshIncluded = false; } } else { meshIncluded = true; } if (!node.jointName && meshIncluded) { newNode = ImportNode(gltfRuntime, node, id); if (newNode !== null) { newNode.id = id; newNode.parent = parent; } } if (node.children) { for (let i = 0; i < node.children.length; i++) { TraverseNodes(gltfRuntime, node.children[i], newNode, meshIncluded); } } }; /** * do stuff after buffers, shaders are loaded (e.g. hook up materials, load animations, etc.) * @param gltfRuntime */ const PostLoad = (gltfRuntime) => { // Nodes let currentScene = gltfRuntime.currentScene; if (currentScene) { for (let i = 0; i < currentScene.nodes.length; i++) { TraverseNodes(gltfRuntime, currentScene.nodes[i], null); } } else { for (const thing in gltfRuntime.scenes) { currentScene = gltfRuntime.scenes[thing]; for (let i = 0; i < currentScene.nodes.length; i++) { TraverseNodes(gltfRuntime, currentScene.nodes[i], null); } } } // Set animations LoadAnimations(gltfRuntime); for (let i = 0; i < gltfRuntime.scene.skeletons.length; i++) { const skeleton = gltfRuntime.scene.skeletons[i]; gltfRuntime.scene.beginAnimation(skeleton, 0, Number.MAX_VALUE, true, 1.0); } }; /** * onBind shaderrs callback to set uniforms and matrices * @param mesh * @param gltfRuntime * @param unTreatedUniforms * @param shaderMaterial * @param technique * @param material * @param onSuccess */ const OnBindShaderMaterial = (mesh, gltfRuntime, unTreatedUniforms, shaderMaterial, technique, material, onSuccess) => { const materialValues = material.values || technique.parameters; for (const unif in unTreatedUniforms) { const uniform = unTreatedUniforms[unif]; const type = uniform.type; if (type === EParameterType.FLOAT_MAT2 || type === EParameterType.FLOAT_MAT3 || type === EParameterType.FLOAT_MAT4) { if (uniform.semantic && !uniform.source && !uniform.node) { GLTFUtils.SetMatrix(gltfRuntime.scene, mesh, uniform, unif, shaderMaterial.getEffect()); } else if (uniform.semantic && (uniform.source || uniform.node)) { let source = gltfRuntime.scene.getNodeByName(uniform.source || uniform.node || ""); if (source === null) { source = gltfRuntime.scene.getNodeById(uniform.source || uniform.node || ""); } if (source === null) { continue; } GLTFUtils.SetMatrix(gltfRuntime.scene, source, uniform, unif, shaderMaterial.getEffect()); } } else { const value = materialValues[technique.uniforms[unif]]; if (!value) { continue; } if (type === EParameterType.SAMPLER_2D) { const texture = gltfRuntime.textures[material.values ? value : uniform.value].babylonTexture; if (texture === null || texture === undefined) { continue; } shaderMaterial.getEffect().setTexture(unif, texture); } else { GLTFUtils.SetUniform(shaderMaterial.getEffect(), unif, value, type); } } } onSuccess(shaderMaterial); }; /** * Prepare uniforms to send the only one time * Loads the appropriate textures * @param gltfRuntime * @param shaderMaterial * @param technique * @param material */ const PrepareShaderMaterialUniforms = (gltfRuntime, shaderMaterial, technique, material, unTreatedUniforms) => { const materialValues = material.values || technique.parameters; const techniqueUniforms = technique.uniforms; /** * Prepare values here (not matrices) */ for (const unif in unTreatedUniforms) { const uniform = unTreatedUniforms[unif]; const type = uniform.type; let value = materialValues[techniqueUniforms[unif]]; if (value === undefined) { // In case the value is the same for all materials value = uniform.value; } if (!value) { continue; } const onLoadTexture = (uniformName) => { return (texture) => { if (uniform.value && uniformName) { // Static uniform shaderMaterial.setTexture(uniformName, texture); delete unTreatedUniforms[uniformName]; } }; }; // Texture (sampler2D) if (type === EParameterType.SAMPLER_2D) { GLTFLoaderExtension.LoadTextureAsync(gltfRuntime, material.values ? value : uniform.value, onLoadTexture(unif), () => onLoadTexture(null)); } // Others else { if (uniform.value && GLTFUtils.SetUniform(shaderMaterial, unif, material.values ? value : uniform.value, type)) { // Static uniform delete unTreatedUniforms[unif]; } } } }; /** * Shader compilation failed * @param program * @param shaderMaterial * @param onError * @returns callback when shader is compiled */ const OnShaderCompileError = (program, shaderMaterial, onError) => { return (effect, error) => { shaderMaterial.dispose(true); onError("Cannot compile program named " + program.name + ". Error: " + error + ". Default material will be applied"); }; }; /** * Shader compilation success * @param gltfRuntime * @param shaderMaterial * @param technique * @param material * @param unTreatedUniforms * @param onSuccess * @returns callback when shader is compiled */ const OnShaderCompileSuccess = (gltfRuntime, shaderMaterial, technique, material, unTreatedUniforms, onSuccess) => { return (_) => { PrepareShaderMaterialUniforms(gltfRuntime, shaderMaterial, technique, material, unTreatedUniforms); shaderMaterial.onBind = (mesh) => { OnBindShaderMaterial(mesh, gltfRuntime, unTreatedUniforms, shaderMaterial, technique, material, onSuccess); }; }; }; /** * Returns the appropriate uniform if already handled by babylon * @param tokenizer * @param technique * @param unTreatedUniforms * @returns the name of the uniform handled by babylon */ const ParseShaderUniforms = (tokenizer, technique, unTreatedUniforms) => { for (const unif in technique.uniforms) { const uniform = technique.uniforms[unif]; const uniformParameter = technique.parameters[uniform]; if (tokenizer.currentIdentifier === unif) { if (uniformParameter.semantic && !uniformParameter.source && !uniformParameter.node) { const transformIndex = glTFTransforms.indexOf(uniformParameter.semantic); if (transformIndex !== -1) { delete unTreatedUniforms[unif]; return BabylonTransforms[transformIndex]; } } } } return tokenizer.currentIdentifier; }; /** * All shaders loaded. Create materials one by one * @param gltfRuntime */ const ImportMaterials = (gltfRuntime) => { // Create materials for (const mat in gltfRuntime.materials) { GLTFLoaderExtension.LoadMaterialAsync(gltfRuntime, mat, () => { }, () => { }); } }; /** * Implementation of the base glTF spec * @internal */ export class GLTFLoaderBase { static CreateRuntime(parsedData, scene, rootUrl) { const gltfRuntime = { extensions: {}, accessors: {}, buffers: {}, bufferViews: {}, meshes: {}, lights: {}, cameras: {}, nodes: {}, images: {}, textures: {}, shaders: {}, programs: {}, samplers: {}, techniques: {}, materials: {}, animations: {}, skins: {}, extensionsUsed: [], scenes: {}, buffersCount: 0, shaderscount: 0, scene: scene, rootUrl: rootUrl, loadedBufferCount: 0, loadedBufferViews: {}, loadedShaderCount: 0, importOnlyMeshes: false, dummyNodes: [], assetContainer: null, }; // Parse if (parsedData.extensions) { ParseObject(parsedData.extensions, "extensions", gltfRuntime); } if (parsedData.extensionsUsed) { ParseObject(parsedData.extensionsUsed, "extensionsUsed", gltfRuntime); } if (parsedData.buffers) { ParseBuffers(parsedData.buffers, gltfRuntime); } if (parsedData.bufferViews) { ParseObject(parsedData.bufferViews, "bufferViews", gltfRuntime); } if (parsedData.accessors) { ParseObject(parsedData.accessors, "accessors", gltfRuntime); } if (parsedData.meshes) { ParseObject(parsedData.meshes, "meshes", gltfRuntime); } if (parsedData.lights) { ParseObject(parsedData.lights, "lights", gltfRuntime); } if (parsedData.cameras) { ParseObject(parsedData.cameras, "cameras", gltfRuntime); } if (parsedData.nodes) { ParseObject(parsedData.nodes, "nodes", gltfRuntime); } if (parsedData.images) { ParseObject(parsedData.images, "images", gltfRuntime); } if (parsedData.textures) { ParseObject(parsedData.textures, "textures", gltfRuntime); } if (parsedData.shaders) { ParseShaders(parsedData.shaders, gltfRuntime); } if (parsedData.programs) { ParseObject(parsedData.programs, "programs", gltfRuntime); } if (parsedData.samplers) { ParseObject(parsedData.samplers, "samplers", gltfRuntime); } if (parsedData.techniques) { ParseObject(parsedData.techniques, "techniques", gltfRuntime); } if (parsedData.materials) { ParseObject(parsedData.materials, "materials", gltfRuntime); } if (parsedData.animations) { ParseObject(parsedData.animations, "animations", gltfRuntime); } if (parsedData.skins) { ParseObject(parsedData.skins, "skins", gltfRuntime); } if (parsedData.scenes) { gltfRuntime.scenes = parsedData.scenes; } if (parsedData.scene && parsedData.scenes) { gltfRuntime.currentScene = parsedData.scenes[parsedData.scene]; } return gltfRuntime; } // eslint-disable-next-line no-restricted-syntax static LoadBufferAsync(gltfRuntime, id, onSuccess, onError, onProgress) { const buffer = gltfRuntime.buffers[id]; if (Tools.IsBase64(buffer.uri)) { setTimeout(() => onSuccess(new Uint8Array(Tools.DecodeBase64(buffer.uri)))); } else { Tools.LoadFile(gltfRuntime.rootUrl + buffer.uri, (data) => onSuccess(new Uint8Array(data)), onProgress, undefined, true, (request) => { if (request) { onError(request.status + " " + request.statusText); } }); } } // eslint-disable-next-line no-restricted-syntax static LoadTextureBufferAsync(gltfRuntime, id, onSuccess, onError) { const texture = gltfRuntime.textures[id]; if (!texture || !texture.source) { onError(""); return; } if (texture.babylonTexture) { onSuccess(null); return; } const source = gltfRuntime.images[texture.source]; if (Tools.IsBase64(source.uri)) { setTimeout(() => onSuccess(new Uint8Array(Tools.DecodeBase64(source.uri)))); } else { Tools.LoadFile(gltfRuntime.rootUrl + source.uri, (data) => onSuccess(new Uint8Array(data)), undefined, undefined, true, (request) => { if (request) { onError(request.status + " " + request.statusText); } }); } } // eslint-disable-next-line no-restricted-syntax static CreateTextureAsync(gltfRuntime, id, buffer, onSuccess) { const texture = gltfRuntime.textures[id]; if (texture.babylonTexture) { onSuccess(texture.babylonTexture); return; } const sampler = gltfRuntime.samplers[texture.sampler]; const createMipMaps = sampler.minFilter === ETextureFilterType.NEAREST_MIPMAP_NEAREST || sampler.minFilter === ETextureFilterType.NEAREST_MIPMAP_LINEAR || sampler.minFilter === ETextureFilterType.LINEAR_MIPMAP_NEAREST || sampler.minFilter === ETextureFilterType.LINEAR_MIPMAP_LINEAR; const samplingMode = Texture.BILINEAR_SAMPLINGMODE; const blob = buffer == null ? new Blob() : new Blob([buffer]); const blobURL = URL.createObjectURL(blob); const revokeBlobURL = () => URL.revokeObjectURL(blobURL); const newTexture = new Texture(blobURL, gltfRuntime.scene, !createMipMaps, true, samplingMode, revokeBlobURL, revokeBlobURL); if (sampler.wrapS !== undefined) { newTexture.wrapU = GLTFUtils.GetWrapMode(sampler.wrapS); } if (sampler.wrapT !== undefined) { newTexture.wrapV = GLTFUtils.GetWrapMode(sampler.wrapT); } newTexture.name = id; texture.babylonTexture = newTexture; onSuccess(newTexture); } // eslint-disable-next-line no-restricted-syntax static LoadShaderStringAsync(gltfRuntime, id, onSuccess, onError) { const shader = gltfRuntime.shaders[id]; if (Tools.IsBase64(shader.uri)) { const shaderString = atob(shader.uri.split(",")[1]); if (onSuccess) { onSuccess(shaderString); } } else { Tools.LoadFile(gltfRuntime.rootUrl + shader.uri, onSuccess, undefined, undefined, false, (request) => { if (request && onError) { onError(request.status + " " + request.statusText); } }); } } // eslint-disable-next-line no-restricted-syntax static LoadMaterialAsync(gltfRuntime, id, onSuccess, onError) { const material = gltfRuntime.materials[id]; if (!material.technique) { if (onError) { onError("No technique found."); } return; } const technique = gltfRuntime.techniques[material.technique]; if (!technique) { gltfRuntime.scene._blockEntityCollection = !!gltfRuntime.assetContainer; const defaultMaterial = new StandardMaterial(id, gltfRuntime.scene); defaultMaterial._parentContainer = gltfRuntime.assetContainer; gltfRuntime.scene._blockEntityCollection = false; defaultMaterial.diffuseColor = new Color3(0.5, 0.5, 0.5); defaultMaterial.sideOrientation = Material.CounterClockWiseSideOrientation; onSuccess(defaultMaterial); return; } const program = gltfRuntime.programs[technique.program]; const states = technique.states; const vertexShader = Effect.ShadersStore[program.vertexShader + "VertexShader"]; const pixelShader = Effect.ShadersStore[program.fragmentShader + "PixelShader"]; let newVertexShader = "";