@pixiv/three-vrm
Version:
VRM file loader for three.js.
1,138 lines (1,121 loc) • 561 kB
JavaScript
/*!
* @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
*/
import { Vector2, Vector3, Vector4, Color, Object3D, Quaternion, SkinnedMesh, Skeleton, Matrix4, Euler, GammaEncoding, RGBDEncoding, RGBM16Encoding, RGBM7Encoding, RGBEEncoding, sRGBEncoding, LinearEncoding, ShaderMaterial, TangentSpaceNormalMap, UniformsUtils, UniformsLib, REVISION, DoubleSide, BackSide, FrontSide, MeshBasicMaterial, Mesh, SphereBufferGeometry, OrthographicCamera, PlaneBufferGeometry, Scene, ArrowHelper, BoxHelper, SkeletonHelper } from 'three';
/*! *****************************************************************************
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 Vector2();
const _v3 = new Vector3();
const _v4 = new Vector4();
const _color = new Color();
// animationMixer の監視対象は、Scene の中に入っている必要がある。
// そのため、表示オブジェクトではないけれど、Object3D を継承して Scene に投入できるようにする。
class VRMBlendShapeGroup extends 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 Vector2().fromArray(args.targetValue);
deltaValue = targetValue.clone().sub(defaultValue);
}
else if (value.isVector3) {
type = VRMBlendShapeMaterialValueType.VECTOR3;
defaultValue = value.clone();
targetValue = new 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 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 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
// then we have to use `namespace` instead of export declarations for now.
// See: https://github.com/TypeStrong/typedoc/pull/801
// eslint-disable-next-line @typescript-eslint/no-namespace
var VRMSchema;
(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 = {}));
})(VRMSchema || (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 Vector3();
const _scale = new Vector3();
new 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 !== 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 Vector3(0.0, 0.0, -1.0));
const _quat = new 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 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 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 Skeleton(src.skeleton.bones, src.skeleton.boneInverses), new 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) {
return null;
}
const schemaFirstPerson = vrmExt.firstPerson;
if (!schemaFirstPerson) {
return null;
}
const firstPersonBoneIndex = schemaFirstPerson.firstPersonBone;
let firstPersonBone;
if (firstPersonBoneIndex === undefined || firstPersonBoneIndex === -1) {
firstPersonBone = humanoid.getBoneNode(VRMSchema.HumanoidBoneName.Head);
}
else {
firstPersonBone = yield gltf.parser.getDependency('node', firstPersonBoneIndex);
}
if (!firstPersonBone) {
console.warn('VRMFirstPersonImporter: Could not find firstPersonBone of the VRM');
return null;
}
const firstPersonBoneOffset = schemaFirstPerson.firstPersonBoneOffset
? new Vector3(schemaFirstPerson.firstPersonBoneOffset.x, schemaFirstPerson.firstPersonBoneOffset.y, -schemaFirstPerson.firstPersonBoneOffset.z)
: new Vector3(0.0, 0.06, 0.0); // fallback, taken from UniVRM implementation
const meshAnnotations = [];
const nodePrimitivesMap = yield gltfExtractPrimitivesFromNodes(gltf);
Array.from(nodePrimitivesMap.entries()).forEach(([nodeIndex, primitives]) => {
const schemaNode = gltf.parser.json.nodes[nodeIndex];
const flag = schemaFirstPerson.meshAnnotations
? schemaFirstPerson.meshAnnotations.find((a) => a.mesh === schemaNode.mesh)
: undefined;
meshAnnotations.push(new VRMRendererFirstPersonFlags(flag === null || flag === void 0 ? void 0 : flag.firstPersonFlag, primitives));
});
return new VRMFirstPerson(firstPersonBone, firstPersonBoneOffset, meshAnnotations);
});
}
}
/**
* A class represents a single `humanBone` of a VRM.
*/
class VRMHumanBone {
/**
* Create a new VRMHumanBone.
*
* @param node A [[GLTFNode]] that represents the new bone
* @param humanLimit A [[VRMHumanLimit]] object that represents properties of the new bone
*/
constructor(node, humanLimit) {
this.node = node;
this.humanLimit = humanLimit;
}
}
/**
* A compat function for `Quaternion.invert()` / `Quaternion.inverse()`.
* `Quaternion.invert()` is introduced in r123 and `Quaternion.inverse()` emits a warning.
* We are going to use this compat for a while.
* @param target A target quaternion
*/
function quatInvertCompat(target) {
if (target.invert) {
target.invert();
}
else {
target.inverse();
}
return target;
}
const _v3A = new Vector3();
const _quatA = new Quaternion();
/**
* A class represents humanoid of a VRM.
*/
class VRMHumanoid {
/**
* Create a new [[VRMHumanoid]].
* @param boneArray A [[VRMHumanBoneArray]] contains all the bones of the new humanoid
* @param humanDescription A [[VRMHumanDescription]] that represents properties of the new humanoid
*/
constructor(boneArray, humanDescription) {
/**
* A [[VRMPose]] that is its default state.
* Note that it's not compatible with `setPose` and `getPose`, since it contains non-relative values of each local transforms.
*/
this.restPose = {};
this.humanBones = this._createHumanBones(boneArray);
this.humanDescription = humanDescription;
this.restPose = this.getPose();
}
/**
* Return the current pose of this humanoid as a [[VRMPose]].
*
* Each transform is a local transform relative from rest pose (T-pose).
*/
getPose() {
const pose = {};
Object.keys(this.humanBones).forEach((vrmBoneName) => {
const node = th