UNPKG

elation-engine

Version:
1,041 lines (1,027 loc) 198 kB
/*! * @pixiv/three-vrm v0.6.4 * VRM file loader for three.js. * * Copyright (c) 2019-2021 pixiv Inc. * @pixiv/three-vrm is distributed under MIT License * https://github.com/pixiv/three-vrm/blob/release/LICENSE */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) : typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.THREE_VRM = {}, global.THREE)); }(this, (function (exports, THREE) { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __awaiter(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()); }); } // See: https://threejs.org/docs/#manual/en/introduction/How-to-dispose-of-objects function disposeMaterial(material) { Object.keys(material).forEach((propertyName) => { const value = material[propertyName]; if (value === null || value === void 0 ? void 0 : value.isTexture) { const texture = value; texture.dispose(); } }); material.dispose(); } function dispose(object3D) { const geometry = object3D.geometry; if (geometry) { geometry.dispose(); } const material = object3D.material; if (material) { if (Array.isArray(material)) { material.forEach((material) => disposeMaterial(material)); } else if (material) { disposeMaterial(material); } } } function deepDispose(object3D) { object3D.traverse(dispose); } var VRMBlendShapeMaterialValueType; (function (VRMBlendShapeMaterialValueType) { VRMBlendShapeMaterialValueType[VRMBlendShapeMaterialValueType["NUMBER"] = 0] = "NUMBER"; VRMBlendShapeMaterialValueType[VRMBlendShapeMaterialValueType["VECTOR2"] = 1] = "VECTOR2"; VRMBlendShapeMaterialValueType[VRMBlendShapeMaterialValueType["VECTOR3"] = 2] = "VECTOR3"; VRMBlendShapeMaterialValueType[VRMBlendShapeMaterialValueType["VECTOR4"] = 3] = "VECTOR4"; VRMBlendShapeMaterialValueType[VRMBlendShapeMaterialValueType["COLOR"] = 4] = "COLOR"; })(VRMBlendShapeMaterialValueType || (VRMBlendShapeMaterialValueType = {})); const _v2 = new THREE.Vector2(); const _v3 = new THREE.Vector3(); const _v4 = new THREE.Vector4(); const _color = new THREE.Color(); // animationMixer の監視対象は、Scene の中に入っている必要がある。 // そのため、表示オブジェクトではないけれど、Object3D を継承して Scene に投入できるようにする。 class VRMBlendShapeGroup extends THREE.Object3D { constructor(expressionName) { super(); this.weight = 0.0; this.isBinary = false; this._binds = []; this._materialValues = []; this.name = `BlendShapeController_${expressionName}`; // traverse 時の救済手段として Object3D ではないことを明示しておく this.type = 'BlendShapeController'; // 表示目的のオブジェクトではないので、負荷軽減のために visible を false にしておく。 // これにより、このインスタンスに対する毎フレームの matrix 自動計算を省略できる。 this.visible = false; } addBind(args) { // original weight is 0-100 but we want to deal with this value within 0-1 const weight = args.weight / 100; this._binds.push({ meshes: args.meshes, morphTargetIndex: args.morphTargetIndex, weight, }); } addMaterialValue(args) { const material = args.material; const propertyName = args.propertyName; let value = material[propertyName]; if (!value) { // property has not been found return; } value = args.defaultValue || value; let type; let defaultValue; let targetValue; let deltaValue; if (value.isVector2) { type = VRMBlendShapeMaterialValueType.VECTOR2; defaultValue = value.clone(); targetValue = new THREE.Vector2().fromArray(args.targetValue); deltaValue = targetValue.clone().sub(defaultValue); } else if (value.isVector3) { type = VRMBlendShapeMaterialValueType.VECTOR3; defaultValue = value.clone(); targetValue = new THREE.Vector3().fromArray(args.targetValue); deltaValue = targetValue.clone().sub(defaultValue); } else if (value.isVector4) { type = VRMBlendShapeMaterialValueType.VECTOR4; defaultValue = value.clone(); // vectorProperty and targetValue index is different from each other // exported vrm by UniVRM file is // // vectorProperty // offset = targetValue[0], targetValue[1] // tiling = targetValue[2], targetValue[3] // // targetValue // offset = targetValue[2], targetValue[3] // tiling = targetValue[0], targetValue[1] targetValue = new THREE.Vector4().fromArray([ args.targetValue[2], args.targetValue[3], args.targetValue[0], args.targetValue[1], ]); deltaValue = targetValue.clone().sub(defaultValue); } else if (value.isColor) { type = VRMBlendShapeMaterialValueType.COLOR; defaultValue = value.clone(); targetValue = new THREE.Color().fromArray(args.targetValue); deltaValue = targetValue.clone().sub(defaultValue); } else { type = VRMBlendShapeMaterialValueType.NUMBER; defaultValue = value; targetValue = args.targetValue[0]; deltaValue = targetValue - defaultValue; } this._materialValues.push({ material, propertyName, defaultValue, targetValue, deltaValue, type, }); } /** * Apply weight to every assigned blend shapes. * Should be called via {@link BlendShapeMaster#update}. */ applyWeight() { const w = this.isBinary ? (this.weight < 0.5 ? 0.0 : 1.0) : this.weight; this._binds.forEach((bind) => { bind.meshes.forEach((mesh) => { if (!mesh.morphTargetInfluences) { return; } // TODO: we should kick this at `addBind` mesh.morphTargetInfluences[bind.morphTargetIndex] += w * bind.weight; }); }); this._materialValues.forEach((materialValue) => { const prop = materialValue.material[materialValue.propertyName]; if (prop === undefined) { return; } // TODO: we should kick this at `addMaterialValue` if (materialValue.type === VRMBlendShapeMaterialValueType.NUMBER) { const deltaValue = materialValue.deltaValue; materialValue.material[materialValue.propertyName] += deltaValue * w; } else if (materialValue.type === VRMBlendShapeMaterialValueType.VECTOR2) { const deltaValue = materialValue.deltaValue; materialValue.material[materialValue.propertyName].add(_v2.copy(deltaValue).multiplyScalar(w)); } else if (materialValue.type === VRMBlendShapeMaterialValueType.VECTOR3) { const deltaValue = materialValue.deltaValue; materialValue.material[materialValue.propertyName].add(_v3.copy(deltaValue).multiplyScalar(w)); } else if (materialValue.type === VRMBlendShapeMaterialValueType.VECTOR4) { const deltaValue = materialValue.deltaValue; materialValue.material[materialValue.propertyName].add(_v4.copy(deltaValue).multiplyScalar(w)); } else if (materialValue.type === VRMBlendShapeMaterialValueType.COLOR) { const deltaValue = materialValue.deltaValue; materialValue.material[materialValue.propertyName].add(_color.copy(deltaValue).multiplyScalar(w)); } if (typeof materialValue.material.shouldApplyUniforms === 'boolean') { materialValue.material.shouldApplyUniforms = true; } }); } /** * Clear previously assigned blend shapes. */ clearAppliedWeight() { this._binds.forEach((bind) => { bind.meshes.forEach((mesh) => { if (!mesh.morphTargetInfluences) { return; } // TODO: we should kick this at `addBind` mesh.morphTargetInfluences[bind.morphTargetIndex] = 0.0; }); }); this._materialValues.forEach((materialValue) => { const prop = materialValue.material[materialValue.propertyName]; if (prop === undefined) { return; } // TODO: we should kick this at `addMaterialValue` if (materialValue.type === VRMBlendShapeMaterialValueType.NUMBER) { const defaultValue = materialValue.defaultValue; materialValue.material[materialValue.propertyName] = defaultValue; } else if (materialValue.type === VRMBlendShapeMaterialValueType.VECTOR2) { const defaultValue = materialValue.defaultValue; materialValue.material[materialValue.propertyName].copy(defaultValue); } else if (materialValue.type === VRMBlendShapeMaterialValueType.VECTOR3) { const defaultValue = materialValue.defaultValue; materialValue.material[materialValue.propertyName].copy(defaultValue); } else if (materialValue.type === VRMBlendShapeMaterialValueType.VECTOR4) { const defaultValue = materialValue.defaultValue; materialValue.material[materialValue.propertyName].copy(defaultValue); } else if (materialValue.type === VRMBlendShapeMaterialValueType.COLOR) { const defaultValue = materialValue.defaultValue; materialValue.material[materialValue.propertyName].copy(defaultValue); } if (typeof materialValue.material.shouldApplyUniforms === 'boolean') { materialValue.material.shouldApplyUniforms = true; } }); } } // Typedoc does not support export declarations yet (function (VRMSchema) { (function (BlendShapePresetName) { BlendShapePresetName["A"] = "a"; BlendShapePresetName["Angry"] = "angry"; BlendShapePresetName["Blink"] = "blink"; BlendShapePresetName["BlinkL"] = "blink_l"; BlendShapePresetName["BlinkR"] = "blink_r"; BlendShapePresetName["E"] = "e"; BlendShapePresetName["Fun"] = "fun"; BlendShapePresetName["I"] = "i"; BlendShapePresetName["Joy"] = "joy"; BlendShapePresetName["Lookdown"] = "lookdown"; BlendShapePresetName["Lookleft"] = "lookleft"; BlendShapePresetName["Lookright"] = "lookright"; BlendShapePresetName["Lookup"] = "lookup"; BlendShapePresetName["Neutral"] = "neutral"; BlendShapePresetName["O"] = "o"; BlendShapePresetName["Sorrow"] = "sorrow"; BlendShapePresetName["U"] = "u"; BlendShapePresetName["Unknown"] = "unknown"; })(VRMSchema.BlendShapePresetName || (VRMSchema.BlendShapePresetName = {})); (function (FirstPersonLookAtTypeName) { FirstPersonLookAtTypeName["BlendShape"] = "BlendShape"; FirstPersonLookAtTypeName["Bone"] = "Bone"; })(VRMSchema.FirstPersonLookAtTypeName || (VRMSchema.FirstPersonLookAtTypeName = {})); (function (HumanoidBoneName) { HumanoidBoneName["Chest"] = "chest"; HumanoidBoneName["Head"] = "head"; HumanoidBoneName["Hips"] = "hips"; HumanoidBoneName["Jaw"] = "jaw"; HumanoidBoneName["LeftEye"] = "leftEye"; HumanoidBoneName["LeftFoot"] = "leftFoot"; HumanoidBoneName["LeftHand"] = "leftHand"; HumanoidBoneName["LeftIndexDistal"] = "leftIndexDistal"; HumanoidBoneName["LeftIndexIntermediate"] = "leftIndexIntermediate"; HumanoidBoneName["LeftIndexProximal"] = "leftIndexProximal"; HumanoidBoneName["LeftLittleDistal"] = "leftLittleDistal"; HumanoidBoneName["LeftLittleIntermediate"] = "leftLittleIntermediate"; HumanoidBoneName["LeftLittleProximal"] = "leftLittleProximal"; HumanoidBoneName["LeftLowerArm"] = "leftLowerArm"; HumanoidBoneName["LeftLowerLeg"] = "leftLowerLeg"; HumanoidBoneName["LeftMiddleDistal"] = "leftMiddleDistal"; HumanoidBoneName["LeftMiddleIntermediate"] = "leftMiddleIntermediate"; HumanoidBoneName["LeftMiddleProximal"] = "leftMiddleProximal"; HumanoidBoneName["LeftRingDistal"] = "leftRingDistal"; HumanoidBoneName["LeftRingIntermediate"] = "leftRingIntermediate"; HumanoidBoneName["LeftRingProximal"] = "leftRingProximal"; HumanoidBoneName["LeftShoulder"] = "leftShoulder"; HumanoidBoneName["LeftThumbDistal"] = "leftThumbDistal"; HumanoidBoneName["LeftThumbIntermediate"] = "leftThumbIntermediate"; HumanoidBoneName["LeftThumbProximal"] = "leftThumbProximal"; HumanoidBoneName["LeftToes"] = "leftToes"; HumanoidBoneName["LeftUpperArm"] = "leftUpperArm"; HumanoidBoneName["LeftUpperLeg"] = "leftUpperLeg"; HumanoidBoneName["Neck"] = "neck"; HumanoidBoneName["RightEye"] = "rightEye"; HumanoidBoneName["RightFoot"] = "rightFoot"; HumanoidBoneName["RightHand"] = "rightHand"; HumanoidBoneName["RightIndexDistal"] = "rightIndexDistal"; HumanoidBoneName["RightIndexIntermediate"] = "rightIndexIntermediate"; HumanoidBoneName["RightIndexProximal"] = "rightIndexProximal"; HumanoidBoneName["RightLittleDistal"] = "rightLittleDistal"; HumanoidBoneName["RightLittleIntermediate"] = "rightLittleIntermediate"; HumanoidBoneName["RightLittleProximal"] = "rightLittleProximal"; HumanoidBoneName["RightLowerArm"] = "rightLowerArm"; HumanoidBoneName["RightLowerLeg"] = "rightLowerLeg"; HumanoidBoneName["RightMiddleDistal"] = "rightMiddleDistal"; HumanoidBoneName["RightMiddleIntermediate"] = "rightMiddleIntermediate"; HumanoidBoneName["RightMiddleProximal"] = "rightMiddleProximal"; HumanoidBoneName["RightRingDistal"] = "rightRingDistal"; HumanoidBoneName["RightRingIntermediate"] = "rightRingIntermediate"; HumanoidBoneName["RightRingProximal"] = "rightRingProximal"; HumanoidBoneName["RightShoulder"] = "rightShoulder"; HumanoidBoneName["RightThumbDistal"] = "rightThumbDistal"; HumanoidBoneName["RightThumbIntermediate"] = "rightThumbIntermediate"; HumanoidBoneName["RightThumbProximal"] = "rightThumbProximal"; HumanoidBoneName["RightToes"] = "rightToes"; HumanoidBoneName["RightUpperArm"] = "rightUpperArm"; HumanoidBoneName["RightUpperLeg"] = "rightUpperLeg"; HumanoidBoneName["Spine"] = "spine"; HumanoidBoneName["UpperChest"] = "upperChest"; })(VRMSchema.HumanoidBoneName || (VRMSchema.HumanoidBoneName = {})); (function (MetaAllowedUserName) { MetaAllowedUserName["Everyone"] = "Everyone"; MetaAllowedUserName["ExplicitlyLicensedPerson"] = "ExplicitlyLicensedPerson"; MetaAllowedUserName["OnlyAuthor"] = "OnlyAuthor"; })(VRMSchema.MetaAllowedUserName || (VRMSchema.MetaAllowedUserName = {})); (function (MetaUssageName) { MetaUssageName["Allow"] = "Allow"; MetaUssageName["Disallow"] = "Disallow"; })(VRMSchema.MetaUssageName || (VRMSchema.MetaUssageName = {})); (function (MetaLicenseName) { MetaLicenseName["Cc0"] = "CC0"; MetaLicenseName["CcBy"] = "CC_BY"; MetaLicenseName["CcByNc"] = "CC_BY_NC"; MetaLicenseName["CcByNcNd"] = "CC_BY_NC_ND"; MetaLicenseName["CcByNcSa"] = "CC_BY_NC_SA"; MetaLicenseName["CcByNd"] = "CC_BY_ND"; MetaLicenseName["CcBySa"] = "CC_BY_SA"; MetaLicenseName["Other"] = "Other"; MetaLicenseName["RedistributionProhibited"] = "Redistribution_Prohibited"; })(VRMSchema.MetaLicenseName || (VRMSchema.MetaLicenseName = {})); })(exports.VRMSchema || (exports.VRMSchema = {})); function extractPrimitivesInternal(gltf, nodeIndex, node) { /** * Let's list up every possible patterns that parsed gltf nodes with a mesh can have,,, * * "*" indicates that those meshes should be listed up using this function * * ### A node with a (mesh, a signle primitive) * * - `THREE.Mesh`: The only primitive of the mesh * * * ### A node with a (mesh, multiple primitives) * * - `THREE.Group`: The root of the mesh * - `THREE.Mesh`: A primitive of the mesh * * - `THREE.Mesh`: A primitive of the mesh (2) * * * ### A node with a (mesh, multiple primitives) AND (a child with a mesh, a single primitive) * * - `THREE.Group`: The root of the mesh * - `THREE.Mesh`: A primitive of the mesh * * - `THREE.Mesh`: A primitive of the mesh (2) * * - `THREE.Mesh`: A primitive of a MESH OF THE CHILD * * ### A node with a (mesh, multiple primitives) AND (a child with a mesh, multiple primitives) * * - `THREE.Group`: The root of the mesh * - `THREE.Mesh`: A primitive of the mesh * * - `THREE.Mesh`: A primitive of the mesh (2) * * - `THREE.Group`: The root of a MESH OF THE CHILD * - `THREE.Mesh`: A primitive of the mesh of the child * - `THREE.Mesh`: A primitive of the mesh of the child (2) * * ### A node with a (mesh, multiple primitives) BUT the node is a bone * * - `THREE.Bone`: The root of the node, as a bone * - `THREE.Group`: The root of the mesh * - `THREE.Mesh`: A primitive of the mesh * * - `THREE.Mesh`: A primitive of the mesh (2) * * * ### A node with a (mesh, multiple primitives) AND (a child with a mesh, multiple primitives) BUT the node is a bone * * - `THREE.Bone`: The root of the node, as a bone * - `THREE.Group`: The root of the mesh * - `THREE.Mesh`: A primitive of the mesh * * - `THREE.Mesh`: A primitive of the mesh (2) * * - `THREE.Group`: The root of a MESH OF THE CHILD * - `THREE.Mesh`: A primitive of the mesh of the child * - `THREE.Mesh`: A primitive of the mesh of the child (2) * * ...I will take a strategy that traverses the root of the node and take first (primitiveCount) meshes. */ // Make sure that the node has a mesh const schemaNode = gltf.parser.json.nodes[nodeIndex]; const meshIndex = schemaNode.mesh; if (meshIndex == null) { return null; } // How many primitives the mesh has? const schemaMesh = gltf.parser.json.meshes[meshIndex]; const primitiveCount = schemaMesh.primitives.length; // Traverse the node and take first (primitiveCount) meshes const primitives = []; node.traverse((object) => { if (primitives.length < primitiveCount) { if (object.isMesh) { primitives.push(object); } } }); return primitives; } /** * Extract primitives ( `THREE.Mesh[]` ) of a node from a loaded GLTF. * The main purpose of this function is to distinguish primitives and children from a node that has both meshes and children. * * It utilizes the behavior that GLTFLoader adds mesh primitives to the node object ( `THREE.Group` ) first then adds its children. * * @param gltf A GLTF object taken from GLTFLoader * @param nodeIndex The index of the node */ function gltfExtractPrimitivesFromNode(gltf, nodeIndex) { return __awaiter(this, void 0, void 0, function* () { const node = yield gltf.parser.getDependency('node', nodeIndex); return extractPrimitivesInternal(gltf, nodeIndex, node); }); } /** * Extract primitives ( `THREE.Mesh[]` ) of nodes from a loaded GLTF. * See {@link gltfExtractPrimitivesFromNode} for more details. * * It returns a map from node index to extraction result. * If a node does not have a mesh, the entry for the node will not be put in the returning map. * * @param gltf A GLTF object taken from GLTFLoader */ function gltfExtractPrimitivesFromNodes(gltf) { return __awaiter(this, void 0, void 0, function* () { const nodes = yield gltf.parser.getDependencies('node'); const map = new Map(); nodes.forEach((node, index) => { const result = extractPrimitivesInternal(gltf, index, node); if (result != null) { map.set(index, result); } }); return map; }); } function renameMaterialProperty(name) { if (name[0] !== '_') { console.warn(`renameMaterialProperty: Given property name "${name}" might be invalid`); return name; } name = name.substring(1); if (!/[A-Z]/.test(name[0])) { console.warn(`renameMaterialProperty: Given property name "${name}" might be invalid`); return name; } return name[0].toLowerCase() + name.substring(1); } /** * Clamp an input number within [ `0.0` - `1.0` ]. * * @param value The input value */ function saturate(value) { return Math.max(Math.min(value, 1.0), 0.0); } const _position = new THREE.Vector3(); const _scale = new THREE.Vector3(); new THREE.Quaternion(); /** * Extract world rotation of an object from its world space matrix, in cheaper way. * * @param object The object * @param out Target vector */ function getWorldQuaternionLite(object, out) { object.matrixWorld.decompose(_position, out, _scale); return out; } class VRMBlendShapeProxy { /** * Create a new VRMBlendShape. */ constructor() { /** * List of registered blend shape. */ this._blendShapeGroups = {}; /** * A map from [[VRMSchema.BlendShapePresetName]] to its actual blend shape name. */ this._blendShapePresetMap = {}; /** * A list of name of unknown blend shapes. */ this._unknownGroupNames = []; // do nothing } /** * List of name of registered blend shape group. */ get expressions() { return Object.keys(this._blendShapeGroups); } /** * A map from [[VRMSchema.BlendShapePresetName]] to its actual blend shape name. */ get blendShapePresetMap() { return this._blendShapePresetMap; } /** * A list of name of unknown blend shapes. */ get unknownGroupNames() { return this._unknownGroupNames; } /** * Return registered blend shape group. * * @param name Name of the blend shape group */ getBlendShapeGroup(name) { const presetName = this._blendShapePresetMap[name]; const controller = presetName ? this._blendShapeGroups[presetName] : this._blendShapeGroups[name]; if (!controller) { console.warn(`no blend shape found by ${name}`); return undefined; } return controller; } /** * Register a blend shape group. * * @param name Name of the blend shape gorup * @param controller VRMBlendShapeController that describes the blend shape group */ registerBlendShapeGroup(name, presetName, controller) { this._blendShapeGroups[name] = controller; if (presetName) { this._blendShapePresetMap[presetName] = name; } else { this._unknownGroupNames.push(name); } } /** * Get current weight of specified blend shape group. * * @param name Name of the blend shape group */ getValue(name) { var _a; const controller = this.getBlendShapeGroup(name); return (_a = controller === null || controller === void 0 ? void 0 : controller.weight) !== null && _a !== void 0 ? _a : null; } /** * Set a weight to specified blend shape group. * * @param name Name of the blend shape group * @param weight Weight */ setValue(name, weight) { const controller = this.getBlendShapeGroup(name); if (controller) { controller.weight = saturate(weight); } } /** * Get a track name of specified blend shape group. * This track name is needed to manipulate its blend shape group via keyframe animations. * * @example Manipulate a blend shape group using keyframe animation * ```js * const trackName = vrm.blendShapeProxy.getBlendShapeTrackName( THREE.VRMSchema.BlendShapePresetName.Blink ); * const track = new THREE.NumberKeyframeTrack( * name, * [ 0.0, 0.5, 1.0 ], // times * [ 0.0, 1.0, 0.0 ] // values * ); * * const clip = new THREE.AnimationClip( * 'blink', // name * 1.0, // duration * [ track ] // tracks * ); * * const mixer = new THREE.AnimationMixer( vrm.scene ); * const action = mixer.clipAction( clip ); * action.play(); * ``` * * @param name Name of the blend shape group */ getBlendShapeTrackName(name) { const controller = this.getBlendShapeGroup(name); return controller ? `${controller.name}.weight` : null; } /** * Update every blend shape groups. */ update() { Object.keys(this._blendShapeGroups).forEach((name) => { const controller = this._blendShapeGroups[name]; controller.clearAppliedWeight(); }); Object.keys(this._blendShapeGroups).forEach((name) => { const controller = this._blendShapeGroups[name]; controller.applyWeight(); }); } } /** * An importer that imports a [[VRMBlendShape]] from a VRM extension of a GLTF. */ class VRMBlendShapeImporter { /** * Import a [[VRMBlendShape]] from a VRM. * * @param gltf A parsed result of GLTF taken from GLTFLoader */ import(gltf) { var _a; return __awaiter(this, void 0, void 0, function* () { const vrmExt = (_a = gltf.parser.json.extensions) === null || _a === void 0 ? void 0 : _a.VRM; if (!vrmExt) { return null; } const schemaBlendShape = vrmExt.blendShapeMaster; if (!schemaBlendShape) { return null; } const blendShape = new VRMBlendShapeProxy(); const blendShapeGroups = schemaBlendShape.blendShapeGroups; if (!blendShapeGroups) { return blendShape; } const blendShapePresetMap = {}; yield Promise.all(blendShapeGroups.map((schemaGroup) => __awaiter(this, void 0, void 0, function* () { const name = schemaGroup.name; if (name === undefined) { console.warn('VRMBlendShapeImporter: One of blendShapeGroups has no name'); return; } let presetName; if (schemaGroup.presetName && schemaGroup.presetName !== exports.VRMSchema.BlendShapePresetName.Unknown && !blendShapePresetMap[schemaGroup.presetName]) { presetName = schemaGroup.presetName; blendShapePresetMap[schemaGroup.presetName] = name; } const group = new VRMBlendShapeGroup(name); gltf.scene.add(group); group.isBinary = schemaGroup.isBinary || false; if (schemaGroup.binds) { schemaGroup.binds.forEach((bind) => __awaiter(this, void 0, void 0, function* () { if (bind.mesh === undefined || bind.index === undefined) { return; } const nodesUsingMesh = []; gltf.parser.json.nodes.forEach((node, i) => { if (node.mesh === bind.mesh) { nodesUsingMesh.push(i); } }); const morphTargetIndex = bind.index; yield Promise.all(nodesUsingMesh.map((nodeIndex) => __awaiter(this, void 0, void 0, function* () { var _b; const primitives = (yield gltfExtractPrimitivesFromNode(gltf, nodeIndex)); // check if the mesh has the target morph target if (!primitives.every((primitive) => Array.isArray(primitive.morphTargetInfluences) && morphTargetIndex < primitive.morphTargetInfluences.length)) { console.warn(`VRMBlendShapeImporter: ${schemaGroup.name} attempts to index ${morphTargetIndex}th morph but not found.`); return; } group.addBind({ meshes: primitives, morphTargetIndex, weight: (_b = bind.weight) !== null && _b !== void 0 ? _b : 100, }); }))); })); } const materialValues = schemaGroup.materialValues; if (materialValues) { materialValues.forEach((materialValue) => { if (materialValue.materialName === undefined || materialValue.propertyName === undefined || materialValue.targetValue === undefined) { return; } const materials = []; gltf.scene.traverse((object) => { if (object.material) { const material = object.material; if (Array.isArray(material)) { materials.push(...material.filter((mtl) => mtl.name === materialValue.materialName && materials.indexOf(mtl) === -1)); } else if (material.name === materialValue.materialName && materials.indexOf(material) === -1) { materials.push(material); } } }); materials.forEach((material) => { group.addMaterialValue({ material, propertyName: renameMaterialProperty(materialValue.propertyName), targetValue: materialValue.targetValue, }); }); }); } blendShape.registerBlendShapeGroup(name, presetName, group); }))); return blendShape; }); } } const VECTOR3_FRONT = Object.freeze(new THREE.Vector3(0.0, 0.0, -1.0)); const _quat = new THREE.Quaternion(); var FirstPersonFlag; (function (FirstPersonFlag) { FirstPersonFlag[FirstPersonFlag["Auto"] = 0] = "Auto"; FirstPersonFlag[FirstPersonFlag["Both"] = 1] = "Both"; FirstPersonFlag[FirstPersonFlag["ThirdPersonOnly"] = 2] = "ThirdPersonOnly"; FirstPersonFlag[FirstPersonFlag["FirstPersonOnly"] = 3] = "FirstPersonOnly"; })(FirstPersonFlag || (FirstPersonFlag = {})); /** * This class represents a single [`meshAnnotation`](https://github.com/vrm-c/UniVRM/blob/master/specification/0.0/schema/vrm.firstperson.meshannotation.schema.json) entry. * Each mesh will be assigned to specified layer when you call [[VRMFirstPerson.setup]]. */ class VRMRendererFirstPersonFlags { /** * Create a new mesh annotation. * * @param firstPersonFlag A [[FirstPersonFlag]] of the annotation entry * @param node A node of the annotation entry. */ constructor(firstPersonFlag, primitives) { this.firstPersonFlag = VRMRendererFirstPersonFlags._parseFirstPersonFlag(firstPersonFlag); this.primitives = primitives; } static _parseFirstPersonFlag(firstPersonFlag) { switch (firstPersonFlag) { case 'Both': return FirstPersonFlag.Both; case 'ThirdPersonOnly': return FirstPersonFlag.ThirdPersonOnly; case 'FirstPersonOnly': return FirstPersonFlag.FirstPersonOnly; default: return FirstPersonFlag.Auto; } } } class VRMFirstPerson { /** * Create a new VRMFirstPerson object. * * @param firstPersonBone A first person bone * @param firstPersonBoneOffset An offset from the specified first person bone * @param meshAnnotations A renderer settings. See the description of [[RendererFirstPersonFlags]] for more info */ constructor(firstPersonBone, firstPersonBoneOffset, meshAnnotations) { this._meshAnnotations = []; this._firstPersonOnlyLayer = VRMFirstPerson._DEFAULT_FIRSTPERSON_ONLY_LAYER; this._thirdPersonOnlyLayer = VRMFirstPerson._DEFAULT_THIRDPERSON_ONLY_LAYER; this._initialized = false; this._firstPersonBone = firstPersonBone; this._firstPersonBoneOffset = firstPersonBoneOffset; this._meshAnnotations = meshAnnotations; } get firstPersonBone() { return this._firstPersonBone; } get meshAnnotations() { return this._meshAnnotations; } getFirstPersonWorldDirection(target) { return target.copy(VECTOR3_FRONT).applyQuaternion(getWorldQuaternionLite(this._firstPersonBone, _quat)); } /** * A camera layer represents `FirstPersonOnly` layer. * Note that **you must call [[setup]] first before you use the layer feature** or it does not work properly. * * The value is [[DEFAULT_FIRSTPERSON_ONLY_LAYER]] by default but you can change the layer by specifying via [[setup]] if you prefer. * * @see https://vrm.dev/en/univrm/api/univrm_use_firstperson/ * @see https://threejs.org/docs/#api/en/core/Layers */ get firstPersonOnlyLayer() { return this._firstPersonOnlyLayer; } /** * A camera layer represents `ThirdPersonOnly` layer. * Note that **you must call [[setup]] first before you use the layer feature** or it does not work properly. * * The value is [[DEFAULT_THIRDPERSON_ONLY_LAYER]] by default but you can change the layer by specifying via [[setup]] if you prefer. * * @see https://vrm.dev/en/univrm/api/univrm_use_firstperson/ * @see https://threejs.org/docs/#api/en/core/Layers */ get thirdPersonOnlyLayer() { return this._thirdPersonOnlyLayer; } getFirstPersonBoneOffset(target) { return target.copy(this._firstPersonBoneOffset); } /** * Get current world position of the first person. * The position takes [[FirstPersonBone]] and [[FirstPersonOffset]] into account. * * @param v3 target * @returns Current world position of the first person */ getFirstPersonWorldPosition(v3) { // UniVRM#VRMFirstPersonEditor // var worldOffset = head.localToWorldMatrix.MultiplyPoint(component.FirstPersonOffset); const offset = this._firstPersonBoneOffset; const v4 = new THREE.Vector4(offset.x, offset.y, offset.z, 1.0); v4.applyMatrix4(this._firstPersonBone.matrixWorld); return v3.set(v4.x, v4.y, v4.z); } /** * In this method, it assigns layers for every meshes based on mesh annotations. * You must call this method first before you use the layer feature. * * This is an equivalent of [VRMFirstPerson.Setup](https://github.com/vrm-c/UniVRM/blob/master/Assets/VRM/UniVRM/Scripts/FirstPerson/VRMFirstPerson.cs) of the UniVRM. * * The `cameraLayer` parameter specifies which layer will be assigned for `FirstPersonOnly` / `ThirdPersonOnly`. * In UniVRM, we specified those by naming each desired layer as `FIRSTPERSON_ONLY_LAYER` / `THIRDPERSON_ONLY_LAYER` * but we are going to specify these layers at here since we are unable to name layers in Three.js. * * @param cameraLayer Specify which layer will be for `FirstPersonOnly` / `ThirdPersonOnly`. */ setup({ firstPersonOnlyLayer = VRMFirstPerson._DEFAULT_FIRSTPERSON_ONLY_LAYER, thirdPersonOnlyLayer = VRMFirstPerson._DEFAULT_THIRDPERSON_ONLY_LAYER, } = {}) { if (this._initialized) { return; } this._initialized = true; this._firstPersonOnlyLayer = firstPersonOnlyLayer; this._thirdPersonOnlyLayer = thirdPersonOnlyLayer; this._meshAnnotations.forEach((item) => { if (item.firstPersonFlag === FirstPersonFlag.FirstPersonOnly) { item.primitives.forEach((primitive) => { primitive.layers.set(this._firstPersonOnlyLayer); }); } else if (item.firstPersonFlag === FirstPersonFlag.ThirdPersonOnly) { item.primitives.forEach((primitive) => { primitive.layers.set(this._thirdPersonOnlyLayer); }); } else if (item.firstPersonFlag === FirstPersonFlag.Auto) { this._createHeadlessModel(item.primitives); } }); } _excludeTriangles(triangles, bws, skinIndex, exclude) { let count = 0; if (bws != null && bws.length > 0) { for (let i = 0; i < triangles.length; i += 3) { const a = triangles[i]; const b = triangles[i + 1]; const c = triangles[i + 2]; const bw0 = bws[a]; const skin0 = skinIndex[a]; if (bw0[0] > 0 && exclude.includes(skin0[0])) continue; if (bw0[1] > 0 && exclude.includes(skin0[1])) continue; if (bw0[2] > 0 && exclude.includes(skin0[2])) continue; if (bw0[3] > 0 && exclude.includes(skin0[3])) continue; const bw1 = bws[b]; const skin1 = skinIndex[b]; if (bw1[0] > 0 && exclude.includes(skin1[0])) continue; if (bw1[1] > 0 && exclude.includes(skin1[1])) continue; if (bw1[2] > 0 && exclude.includes(skin1[2])) continue; if (bw1[3] > 0 && exclude.includes(skin1[3])) continue; const bw2 = bws[c]; const skin2 = skinIndex[c]; if (bw2[0] > 0 && exclude.includes(skin2[0])) continue; if (bw2[1] > 0 && exclude.includes(skin2[1])) continue; if (bw2[2] > 0 && exclude.includes(skin2[2])) continue; if (bw2[3] > 0 && exclude.includes(skin2[3])) continue; triangles[count++] = a; triangles[count++] = b; triangles[count++] = c; } } return count; } _createErasedMesh(src, erasingBonesIndex) { const dst = new THREE.SkinnedMesh(src.geometry.clone(), src.material); dst.name = `${src.name}(erase)`; dst.frustumCulled = src.frustumCulled; dst.layers.set(this._firstPersonOnlyLayer); const geometry = dst.geometry; const skinIndexAttr = geometry.getAttribute('skinIndex').array; const skinIndex = []; for (let i = 0; i < skinIndexAttr.length; i += 4) { skinIndex.push([skinIndexAttr[i], skinIndexAttr[i + 1], skinIndexAttr[i + 2], skinIndexAttr[i + 3]]); } const skinWeightAttr = geometry.getAttribute('skinWeight').array; const skinWeight = []; for (let i = 0; i < skinWeightAttr.length; i += 4) { skinWeight.push([skinWeightAttr[i], skinWeightAttr[i + 1], skinWeightAttr[i + 2], skinWeightAttr[i + 3]]); } const index = geometry.getIndex(); if (!index) { throw new Error("The geometry doesn't have an index buffer"); } const oldTriangles = Array.from(index.array); const count = this._excludeTriangles(oldTriangles, skinWeight, skinIndex, erasingBonesIndex); const newTriangle = []; for (let i = 0; i < count; i++) { newTriangle[i] = oldTriangles[i]; } geometry.setIndex(newTriangle); // mtoon material includes onBeforeRender. this is unsupported at SkinnedMesh#clone if (src.onBeforeRender) { dst.onBeforeRender = src.onBeforeRender; } dst.bind(new THREE.Skeleton(src.skeleton.bones, src.skeleton.boneInverses), new THREE.Matrix4()); return dst; } _createHeadlessModelForSkinnedMesh(parent, mesh) { const eraseBoneIndexes = []; mesh.skeleton.bones.forEach((bone, index) => { if (this._isEraseTarget(bone)) eraseBoneIndexes.push(index); }); // Unlike UniVRM we don't copy mesh if no invisible bone was found if (!eraseBoneIndexes.length) { mesh.layers.enable(this._thirdPersonOnlyLayer); mesh.layers.enable(this._firstPersonOnlyLayer); return; } mesh.layers.set(this._thirdPersonOnlyLayer); const newMesh = this._createErasedMesh(mesh, eraseBoneIndexes); parent.add(newMesh); } _createHeadlessModel(primitives) { primitives.forEach((primitive) => { if (primitive.type === 'SkinnedMesh') { const skinnedMesh = primitive; this._createHeadlessModelForSkinnedMesh(skinnedMesh.parent, skinnedMesh); } else { if (this._isEraseTarget(primitive)) { primitive.layers.set(this._thirdPersonOnlyLayer); } } }); } /** * It just checks whether the node or its parent is the first person bone or not. * @param bone The target bone */ _isEraseTarget(bone) { if (bone === this._firstPersonBone) { return true; } else if (!bone.parent) { return false; } else { return this._isEraseTarget(bone.parent); } } } /** * A default camera layer for `FirstPersonOnly` layer. * * @see [[getFirstPersonOnlyLayer]] */ VRMFirstPerson._DEFAULT_FIRSTPERSON_ONLY_LAYER = 9; /** * A default camera layer for `ThirdPersonOnly` layer. * * @see [[getThirdPersonOnlyLayer]] */ VRMFirstPerson._DEFAULT_THIRDPERSON_ONLY_LAYER = 10; /** * An importer that imports a [[VRMFirstPerson]] from a VRM extension of a GLTF. */ class VRMFirstPersonImporter { /** * Import a [[VRMFirstPerson]] from a VRM. * * @param gltf A parsed result of GLTF taken from GLTFLoader * @param humanoid A [[VRMHumanoid]] instance that represents the VRM */ import(gltf, humanoid) { var _a; return __awaiter(this, void 0, void 0, function* () { const vrmExt = (_a = gltf.parser.json.extensions) === null || _a === void 0 ? void 0 : _a.VRM; if (!vrmExt)