@pixiv/three-vrm
Version:
VRM file loader for three.js.
1,084 lines (1,064 loc) • 2.26 MB
JavaScript
/*!
* @pixiv/three-vrm v2.1.0
* VRM file loader for three.js.
*
* Copyright (c) 2019-2024 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';
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
/*!
* @pixiv/three-vrm-core v2.1.0
* The implementation of core features of VRM, for @pixiv/three-vrm
*
* Copyright (c) 2020-2024 pixiv Inc.
* @pixiv/three-vrm-core is distributed under MIT License
* https://github.com/pixiv/three-vrm/blob/release/LICENSE
*/
// animationMixer の監視対象は、Scene の中に入っている必要がある。
// そのため、表示オブジェクトではないけれど、Object3D を継承して Scene に投入できるようにする。
class VRMExpression extends THREE__namespace.Object3D {
/**
* A value represents how much it should override blink expressions.
* `0.0` == no override at all, `1.0` == completely block the expressions.
*/
get overrideBlinkAmount() {
if (this.overrideBlink === 'block') {
return 0.0 < this.weight ? 1.0 : 0.0;
}
else if (this.overrideBlink === 'blend') {
return this.weight;
}
else {
return 0.0;
}
}
/**
* A value represents how much it should override lookAt expressions.
* `0.0` == no override at all, `1.0` == completely block the expressions.
*/
get overrideLookAtAmount() {
if (this.overrideLookAt === 'block') {
return 0.0 < this.weight ? 1.0 : 0.0;
}
else if (this.overrideLookAt === 'blend') {
return this.weight;
}
else {
return 0.0;
}
}
/**
* A value represents how much it should override mouth expressions.
* `0.0` == no override at all, `1.0` == completely block the expressions.
*/
get overrideMouthAmount() {
if (this.overrideMouth === 'block') {
return 0.0 < this.weight ? 1.0 : 0.0;
}
else if (this.overrideMouth === 'blend') {
return this.weight;
}
else {
return 0.0;
}
}
constructor(expressionName) {
super();
/**
* The current weight of the expression.
*/
this.weight = 0.0;
/**
* Interpret values greater than 0.5 as 1.0, ortherwise 0.0.
*/
this.isBinary = false;
/**
* Specify how the expression overrides blink expressions.
*/
this.overrideBlink = 'none';
/**
* Specify how the expression overrides lookAt expressions.
*/
this.overrideLookAt = 'none';
/**
* Specify how the expression overrides mouth expressions.
*/
this.overrideMouth = 'none';
this._binds = [];
this.name = `VRMExpression_${expressionName}`;
this.expressionName = expressionName;
// traverse 時の救済手段として Object3D ではないことを明示しておく
this.type = 'VRMExpression';
// 表示目的のオブジェクトではないので、負荷軽減のために visible を false にしておく。
// これにより、このインスタンスに対する毎フレームの matrix 自動計算を省略できる。
this.visible = false;
}
addBind(bind) {
this._binds.push(bind);
}
/**
* Apply weight to every assigned blend shapes.
* Should be called every frame.
*/
applyWeight(options) {
var _a;
let actualWeight = this.isBinary ? (this.weight <= 0.5 ? 0.0 : 1.0) : this.weight;
actualWeight *= (_a = options === null || options === void 0 ? void 0 : options.multiplier) !== null && _a !== void 0 ? _a : 1.0;
this._binds.forEach((bind) => bind.applyWeight(actualWeight));
}
/**
* Clear previously assigned blend shapes.
*/
clearAppliedWeight() {
this._binds.forEach((bind) => bind.clearAppliedWeight());
}
}
/******************************************************************************
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$6(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());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
function extractPrimitivesInternal(gltf, nodeIndex, node) {
var _a, _b;
const json = gltf.parser.json;
/**
* 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 = (_a = json.nodes) === null || _a === void 0 ? void 0 : _a[nodeIndex];
if (schemaNode == null) {
console.warn(`extractPrimitivesInternal: Attempt to use nodes[${nodeIndex}] of glTF but the node doesn't exist`);
return null;
}
const meshIndex = schemaNode.mesh;
if (meshIndex == null) {
return null;
}
// How many primitives the mesh has?
const schemaMesh = (_b = json.meshes) === null || _b === void 0 ? void 0 : _b[meshIndex];
if (schemaMesh == null) {
console.warn(`extractPrimitivesInternal: Attempt to use meshes[${meshIndex}] of glTF but the mesh doesn't exist`);
return null;
}
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$6(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$6(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;
});
}
/**
* Get a material definition index of glTF from associated material.
* It's basically a comat code between Three.js r133 or above and previous versions.
* @param parser GLTFParser
* @param material A material of gltf
* @returns Material definition index of glTF
*/
function gltfGetAssociatedMaterialIndex(parser, material) {
var _a, _b;
const threeRevision = parseInt(THREE__namespace.REVISION, 10);
let index = null;
if (threeRevision >= 133) {
index = (_b = (_a = parser.associations.get(material)) === null || _a === void 0 ? void 0 : _a.materials) !== null && _b !== void 0 ? _b : null;
}
else {
const associations = parser.associations;
const reference = associations.get(material);
if ((reference === null || reference === void 0 ? void 0 : reference.type) === 'materials') {
index = reference.index;
}
}
return index;
}
/* eslint-disable @typescript-eslint/naming-convention */
const VRMExpressionPresetName = {
Aa: 'aa',
Ih: 'ih',
Ou: 'ou',
Ee: 'ee',
Oh: 'oh',
Blink: 'blink',
Happy: 'happy',
Angry: 'angry',
Sad: 'sad',
Relaxed: 'relaxed',
LookUp: 'lookUp',
Surprised: 'surprised',
LookDown: 'lookDown',
LookLeft: 'lookLeft',
LookRight: 'lookRight',
BlinkLeft: 'blinkLeft',
BlinkRight: 'blinkRight',
Neutral: 'neutral',
};
/**
* Clamp the input value within [0.0 - 1.0].
*
* @param value The input value
*/
function saturate(value) {
return Math.max(Math.min(value, 1.0), 0.0);
}
class VRMExpressionManager {
get expressions() {
return this._expressions.concat();
}
get expressionMap() {
return Object.assign({}, this._expressionMap);
}
/**
* A map from name to expression, but excluding custom expressions.
*/
get presetExpressionMap() {
const result = {};
const presetNameSet = new Set(Object.values(VRMExpressionPresetName));
Object.entries(this._expressionMap).forEach(([name, expression]) => {
if (presetNameSet.has(name)) {
result[name] = expression;
}
});
return result;
}
/**
* A map from name to expression, but excluding preset expressions.
*/
get customExpressionMap() {
const result = {};
const presetNameSet = new Set(Object.values(VRMExpressionPresetName));
Object.entries(this._expressionMap).forEach(([name, expression]) => {
if (!presetNameSet.has(name)) {
result[name] = expression;
}
});
return result;
}
/**
* Create a new {@link VRMExpressionManager}.
*/
constructor() {
/**
* A set of name or preset name of expressions that will be overridden by {@link VRMExpression.overrideBlink}.
*/
this.blinkExpressionNames = ['blink', 'blinkLeft', 'blinkRight'];
/**
* A set of name or preset name of expressions that will be overridden by {@link VRMExpression.overrideLookAt}.
*/
this.lookAtExpressionNames = ['lookLeft', 'lookRight', 'lookUp', 'lookDown'];
/**
* A set of name or preset name of expressions that will be overridden by {@link VRMExpression.overrideMouth}.
*/
this.mouthExpressionNames = ['aa', 'ee', 'ih', 'oh', 'ou'];
/**
* A set of {@link VRMExpression}.
* When you want to register expressions, use {@link registerExpression}
*/
this._expressions = [];
/**
* A map from name to expression.
*/
this._expressionMap = {};
// do nothing
}
/**
* Copy the given {@link VRMExpressionManager} into this one.
* @param source The {@link VRMExpressionManager} you want to copy
* @returns this
*/
copy(source) {
// first unregister all the expression it has
const expressions = this._expressions.concat();
expressions.forEach((expression) => {
this.unregisterExpression(expression);
});
// then register all the expression of the source
source._expressions.forEach((expression) => {
this.registerExpression(expression);
});
// copy remaining members
this.blinkExpressionNames = source.blinkExpressionNames.concat();
this.lookAtExpressionNames = source.lookAtExpressionNames.concat();
this.mouthExpressionNames = source.mouthExpressionNames.concat();
return this;
}
/**
* Returns a clone of this {@link VRMExpressionManager}.
* @returns Copied {@link VRMExpressionManager}
*/
clone() {
return new VRMExpressionManager().copy(this);
}
/**
* Return a registered expression.
* If it cannot find an expression, it will return `null` instead.
*
* @param name Name or preset name of the expression
*/
getExpression(name) {
var _a;
return (_a = this._expressionMap[name]) !== null && _a !== void 0 ? _a : null;
}
/**
* Register an expression.
*
* @param expression {@link VRMExpression} that describes the expression
*/
registerExpression(expression) {
this._expressions.push(expression);
this._expressionMap[expression.expressionName] = expression;
}
/**
* Unregister an expression.
*
* @param expression The expression you want to unregister
*/
unregisterExpression(expression) {
const index = this._expressions.indexOf(expression);
if (index === -1) {
console.warn('VRMExpressionManager: The specified expressions is not registered');
}
this._expressions.splice(index, 1);
delete this._expressionMap[expression.expressionName];
}
/**
* Get the current weight of the specified expression.
* If it doesn't have an expression of given name, it will return `null` instead.
*
* @param name Name of the expression
*/
getValue(name) {
var _a;
const expression = this.getExpression(name);
return (_a = expression === null || expression === void 0 ? void 0 : expression.weight) !== null && _a !== void 0 ? _a : null;
}
/**
* Set a weight to the specified expression.
*
* @param name Name of the expression
* @param weight Weight
*/
setValue(name, weight) {
const expression = this.getExpression(name);
if (expression) {
expression.weight = saturate(weight);
}
}
/**
* Get a track name of specified expression.
* This track name is needed to manipulate its expression via keyframe animations.
*
* @example Manipulate an expression using keyframe animation
* ```js
* const trackName = vrm.expressionManager.getExpressionTrackName( '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 expression
*/
getExpressionTrackName(name) {
const expression = this.getExpression(name);
return expression ? `${expression.name}.weight` : null;
}
/**
* Update every expressions.
*/
update() {
// see how much we should override certain expressions
const weightMultipliers = this._calculateWeightMultipliers();
// reset expression binds first
this._expressions.forEach((expression) => {
expression.clearAppliedWeight();
});
// then apply binds
this._expressions.forEach((expression) => {
let multiplier = 1.0;
const name = expression.expressionName;
if (this.blinkExpressionNames.indexOf(name) !== -1) {
multiplier *= weightMultipliers.blink;
}
if (this.lookAtExpressionNames.indexOf(name) !== -1) {
multiplier *= weightMultipliers.lookAt;
}
if (this.mouthExpressionNames.indexOf(name) !== -1) {
multiplier *= weightMultipliers.mouth;
}
expression.applyWeight({ multiplier });
});
}
/**
* Calculate sum of override amounts to see how much we should multiply weights of certain expressions.
*/
_calculateWeightMultipliers() {
let blink = 1.0;
let lookAt = 1.0;
let mouth = 1.0;
this._expressions.forEach((expression) => {
blink -= expression.overrideBlinkAmount;
lookAt -= expression.overrideLookAtAmount;
mouth -= expression.overrideMouthAmount;
});
blink = Math.max(0.0, blink);
lookAt = Math.max(0.0, lookAt);
mouth = Math.max(0.0, mouth);
return { blink, lookAt, mouth };
}
}
/* eslint-disable @typescript-eslint/naming-convention */
const VRMExpressionMaterialColorType = {
Color: 'color',
EmissionColor: 'emissionColor',
ShadeColor: 'shadeColor',
MatcapColor: 'matcapColor',
RimColor: 'rimColor',
OutlineColor: 'outlineColor',
};
const v0ExpressionMaterialColorMap = {
_Color: VRMExpressionMaterialColorType.Color,
_EmissionColor: VRMExpressionMaterialColorType.EmissionColor,
_ShadeColor: VRMExpressionMaterialColorType.ShadeColor,
_RimColor: VRMExpressionMaterialColorType.RimColor,
_OutlineColor: VRMExpressionMaterialColorType.OutlineColor,
};
const _color = new THREE__namespace.Color();
/**
* A bind of expression influences to a material color.
*/
class VRMExpressionMaterialColorBind {
constructor({ material, type, targetValue, targetAlpha, }) {
this.material = material;
this.type = type;
this.targetValue = targetValue;
this.targetAlpha = targetAlpha !== null && targetAlpha !== void 0 ? targetAlpha : 1.0;
// init bind state
const color = this._initColorBindState();
const alpha = this._initAlphaBindState();
this._state = { color, alpha };
}
applyWeight(weight) {
const { color, alpha } = this._state;
if (color != null) {
const { propertyName, deltaValue } = color;
const target = this.material[propertyName];
if (target != undefined) {
target.add(_color.copy(deltaValue).multiplyScalar(weight));
}
}
if (alpha != null) {
const { propertyName, deltaValue } = alpha;
const target = this.material[propertyName];
if (target != undefined) {
this.material[propertyName] += deltaValue * weight;
}
}
}
clearAppliedWeight() {
const { color, alpha } = this._state;
if (color != null) {
const { propertyName, initialValue } = color;
const target = this.material[propertyName];
if (target != undefined) {
target.copy(initialValue);
}
}
if (alpha != null) {
const { propertyName, initialValue } = alpha;
const target = this.material[propertyName];
if (target != undefined) {
this.material[propertyName] = initialValue;
}
}
}
_initColorBindState() {
var _a, _b, _c;
const { material, type, targetValue } = this;
const propertyNameMap = this._getPropertyNameMap();
const propertyName = (_b = (_a = propertyNameMap === null || propertyNameMap === void 0 ? void 0 : propertyNameMap[type]) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : null;
if (propertyName == null) {
console.warn(`Tried to add a material color bind to the material ${(_c = material.name) !== null && _c !== void 0 ? _c : '(no name)'}, the type ${type} but the material or the type is not supported.`);
return null;
}
const target = material[propertyName];
const initialValue = target.clone();
// 負の値を保持するためにColor.subを使わずに差分を計算する
const deltaValue = new THREE__namespace.Color(targetValue.r - initialValue.r, targetValue.g - initialValue.g, targetValue.b - initialValue.b);
return { propertyName, initialValue, deltaValue };
}
_initAlphaBindState() {
var _a, _b, _c;
const { material, type, targetAlpha } = this;
const propertyNameMap = this._getPropertyNameMap();
const propertyName = (_b = (_a = propertyNameMap === null || propertyNameMap === void 0 ? void 0 : propertyNameMap[type]) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : null;
if (propertyName == null && targetAlpha !== 1.0) {
console.warn(`Tried to add a material alpha bind to the material ${(_c = material.name) !== null && _c !== void 0 ? _c : '(no name)'}, the type ${type} but the material or the type does not support alpha.`);
return null;
}
if (propertyName == null) {
return null;
}
const initialValue = material[propertyName];
const deltaValue = targetAlpha - initialValue;
return { propertyName, initialValue, deltaValue };
}
_getPropertyNameMap() {
var _a, _b;
return ((_b = (_a = Object.entries(VRMExpressionMaterialColorBind._propertyNameMapMap).find(([distinguisher]) => {
return this.material[distinguisher] === true;
})) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : null);
}
}
/**
* Mapping of property names from VRMC/materialColorBinds.type to three.js/Material.
* The first element stands for color channels, the second element stands for the alpha channel.
* The second element can be null if the target property doesn't exist.
*/
// TODO: We might want to use the `satisfies` operator once we bump TS to 4.9 or higher
// See: https://github.com/pixiv/three-vrm/pull/1323#discussion_r1374020035
VRMExpressionMaterialColorBind._propertyNameMapMap = {
isMeshStandardMaterial: {
color: ['color', 'opacity'],
emissionColor: ['emissive', null],
},
isMeshBasicMaterial: {
color: ['color', 'opacity'],
},
isMToonMaterial: {
color: ['color', 'opacity'],
emissionColor: ['emissive', null],
outlineColor: ['outlineColorFactor', null],
matcapColor: ['matcapFactor', null],
rimColor: ['parametricRimColorFactor', null],
shadeColor: ['shadeColorFactor', null],
},
};
/**
* A bind of {@link VRMExpression} influences to morph targets.
*/
class VRMExpressionMorphTargetBind {
constructor({ primitives, index, weight, }) {
this.primitives = primitives;
this.index = index;
this.weight = weight;
}
applyWeight(weight) {
this.primitives.forEach((mesh) => {
var _a;
if (((_a = mesh.morphTargetInfluences) === null || _a === void 0 ? void 0 : _a[this.index]) != null) {
mesh.morphTargetInfluences[this.index] += this.weight * weight;
}
});
}
clearAppliedWeight() {
this.primitives.forEach((mesh) => {
var _a;
if (((_a = mesh.morphTargetInfluences) === null || _a === void 0 ? void 0 : _a[this.index]) != null) {
mesh.morphTargetInfluences[this.index] = 0.0;
}
});
}
}
const _v2 = new THREE__namespace.Vector2();
/**
* A bind of expression influences to texture transforms.
*/
class VRMExpressionTextureTransformBind {
constructor({ material, scale, offset, }) {
var _a, _b;
this.material = material;
this.scale = scale;
this.offset = offset;
const propertyNames = (_a = Object.entries(VRMExpressionTextureTransformBind._propertyNamesMap).find(([distinguisher]) => {
return material[distinguisher] === true;
})) === null || _a === void 0 ? void 0 : _a[1];
if (propertyNames == null) {
console.warn(`Tried to add a texture transform bind to the material ${(_b = material.name) !== null && _b !== void 0 ? _b : '(no name)'} but the material is not supported.`);
this._properties = [];
}
else {
this._properties = [];
propertyNames.forEach((propertyName) => {
var _a;
const texture = (_a = material[propertyName]) === null || _a === void 0 ? void 0 : _a.clone();
if (!texture) {
return null;
}
material[propertyName] = texture; // because the texture is cloned
const initialOffset = texture.offset.clone();
const initialScale = texture.repeat.clone();
const deltaOffset = offset.clone().sub(initialOffset);
const deltaScale = scale.clone().sub(initialScale);
this._properties.push({
name: propertyName,
initialOffset,
deltaOffset,
initialScale,
deltaScale,
});
});
}
}
applyWeight(weight) {
this._properties.forEach((property) => {
const target = this.material[property.name];
if (target === undefined) {
return;
} // TODO: we should kick this at `addMaterialValue`
target.offset.add(_v2.copy(property.deltaOffset).multiplyScalar(weight));
target.repeat.add(_v2.copy(property.deltaScale).multiplyScalar(weight));
});
}
clearAppliedWeight() {
this._properties.forEach((property) => {
const target = this.material[property.name];
if (target === undefined) {
return;
} // TODO: we should kick this at `addMaterialValue`
target.offset.copy(property.initialOffset);
target.repeat.copy(property.initialScale);
});
}
}
VRMExpressionTextureTransformBind._propertyNamesMap = {
isMeshStandardMaterial: [
'map',
'emissiveMap',
'bumpMap',
'normalMap',
'displacementMap',
'roughnessMap',
'metalnessMap',
'alphaMap',
],
isMeshBasicMaterial: ['map', 'specularMap', 'alphaMap'],
isMToonMaterial: [
'map',
'normalMap',
'emissiveMap',
'shadeMultiplyTexture',
'rimMultiplyTexture',
'outlineWidthMultiplyTexture',
'uvAnimationMaskTexture',
],
};
/**
* Possible spec versions it recognizes.
*/
const POSSIBLE_SPEC_VERSIONS$4 = new Set(['1.0', '1.0-beta']);
/**
* A plugin of GLTFLoader that imports a {@link VRMExpressionManager} from a VRM extension of a GLTF.
*/
class VRMExpressionLoaderPlugin {
get name() {
// We should use the extension name instead but we have multiple plugins for an extension...
return 'VRMExpressionLoaderPlugin';
}
constructor(parser) {
this.parser = parser;
}
afterRoot(gltf) {
return __awaiter$6(this, void 0, void 0, function* () {
gltf.userData.vrmExpressionManager = yield this._import(gltf);
});
}
/**
* Import a {@link VRMExpressionManager} from a VRM.
*
* @param gltf A parsed result of GLTF taken from GLTFLoader
*/
_import(gltf) {
return __awaiter$6(this, void 0, void 0, function* () {
const v1Result = yield this._v1Import(gltf);
if (v1Result) {
return v1Result;
}
const v0Result = yield this._v0Import(gltf);
if (v0Result) {
return v0Result;
}
return null;
});
}
_v1Import(gltf) {
var _a, _b;
return __awaiter$6(this, void 0, void 0, function* () {
const json = this.parser.json;
// early abort if it doesn't use vrm
const isVRMUsed = ((_a = json.extensionsUsed) === null || _a === void 0 ? void 0 : _a.indexOf('VRMC_vrm')) !== -1;
if (!isVRMUsed) {
return null;
}
const extension = (_b = json.extensions) === null || _b === void 0 ? void 0 : _b['VRMC_vrm'];
if (!extension) {
return null;
}
const specVersion = extension.specVersion;
if (!POSSIBLE_SPEC_VERSIONS$4.has(specVersion)) {
console.warn(`VRMExpressionLoaderPlugin: Unknown VRMC_vrm specVersion "${specVersion}"`);
return null;
}
const schemaExpressions = extension.expressions;
if (!schemaExpressions) {
return null;
}
// list expressions
const presetNameSet = new Set(Object.values(VRMExpressionPresetName));
const nameSchemaExpressionMap = new Map();
if (schemaExpressions.preset != null) {
Object.entries(schemaExpressions.preset).forEach(([name, schemaExpression]) => {
if (schemaExpression == null) {
return;
} // typescript
if (!presetNameSet.has(name)) {
console.warn(`VRMExpressionLoaderPlugin: Unknown preset name "${name}" detected. Ignoring the expression`);
return;
}
nameSchemaExpressionMap.set(name, schemaExpression);
});
}
if (schemaExpressions.custom != null) {
Object.entries(schemaExpressions.custom).forEach(([name, schemaExpression]) => {
if (presetNameSet.has(name)) {
console.warn(`VRMExpressionLoaderPlugin: Custom expression cannot have preset name "${name}". Ignoring the expression`);
return;
}
nameSchemaExpressionMap.set(name, schemaExpression);
});
}
// prepare manager
const manager = new VRMExpressionManager();
// load expressions
yield Promise.all(Array.from(nameSchemaExpressionMap.entries()).map(([name, schemaExpression]) => __awaiter$6(this, void 0, void 0, function* () {
var _c, _d, _e, _f, _g, _h, _j;
const expression = new VRMExpression(name);
gltf.scene.add(expression);
expression.isBinary = (_c = schemaExpression.isBinary) !== null && _c !== void 0 ? _c : false;
expression.overrideBlink = (_d = schemaExpression.overrideBlink) !== null && _d !== void 0 ? _d : 'none';
expression.overrideLookAt = (_e = schemaExpression.overrideLookAt) !== null && _e !== void 0 ? _e : 'none';
expression.overrideMouth = (_f = schemaExpression.overrideMouth) !== null && _f !== void 0 ? _f : 'none';
(_g = schemaExpression.morphTargetBinds) === null || _g === void 0 ? void 0 : _g.forEach((bind) => __awaiter$6(this, void 0, void 0, function* () {
var _k;
if (bind.node === undefined || bind.index === undefined) {
return;
}
const primitives = (yield gltfExtractPrimitivesFromNode(gltf, bind.node));
const morphTargetIndex = bind.index;
// check if the mesh has the target morph target
if (!primitives.every((primitive) => Array.isArray(primitive.morphTargetInfluences) &&
morphTargetIndex < primitive.morphTargetInfluences.length)) {
console.warn(`VRMExpressionLoaderPlugin: ${schemaExpression.name} attempts to index morph #${morphTargetIndex} but not found.`);
return;
}
expression.addBind(new VRMExpressionMorphTargetBind({
primitives,
index: morphTargetIndex,
weight: (_k = bind.weight) !== null && _k !== void 0 ? _k : 1.0,
}));
}));
if (schemaExpression.materialColorBinds || schemaExpression.textureTransformBinds) {
// list up every material in `gltf.scene`
const gltfMaterials = [];
gltf.scene.traverse((object) => {
const material = object.material;
if (material) {
gltfMaterials.push(material);
}
});
(_h = schemaExpression.materialColorBinds) === null || _h === void 0 ? void 0 : _h.forEach((bind) => __awaiter$6(this, void 0, void 0, function* () {
const materials = gltfMaterials.filter((material) => {
const materialIndex = gltfGetAssociatedMaterialIndex(this.parser, material);
return bind.material === materialIndex;
});
materials.forEach((material) => {
expression.addBind(new VRMExpressionMaterialColorBind({
material,
type: bind.type,
targetValue: new THREE__namespace.Color().fromArray(bind.targetValue),
targetAlpha: bind.targetValue[3],
}));
});
}));
(_j = schemaExpression.textureTransformBinds) === null || _j === void 0 ? void 0 : _j.forEach((bind) => __awaiter$6(this, void 0, void 0, function* () {
const materials = gltfMaterials.filter((material) => {
const materialIndex = gltfGetAssociatedMaterialIndex(this.parser, material);
return bind.material === materialIndex;
});
materials.forEach((material) => {
var _a, _b;
expression.addBind(new VRMExpressionTextureTransformBind({
material,
offset: new THREE__namespace.Vector2().fromArray((_a = bind.offset) !== null && _a !== void 0 ? _a : [0.0, 0.0]),
scale: new THREE__namespace.Vector2().fromArray((_b = bind.scale) !== null && _b !== void 0 ? _b : [1.0, 1.0]),
}));
});
}));
}
manager.registerExpression(expression);
})));
return manager;
});
}
_v0Import(gltf) {
var _a;
return __awaiter$6(this, void 0, void 0, function* () {
const json = this.parser.json;
// early abort if it doesn't use vrm
const vrmExt = (_a = json.extensions) === null || _a === void 0 ? void 0 : _a.VRM;
if (!vrmExt) {
return null;
}
const schemaBlendShape = vrmExt.blendShapeMaster;
if (!schemaBlendShape) {
return null;
}
const manager = new VRMExpressionManager();
const schemaBlendShapeGroups = schemaBlendShape.blendShapeGroups;
if (!schemaBlendShapeGroups) {
return manager;
}
const blendShapeNameSet = new Set();
yield Promise.all(schemaBlendShapeGroups.map((schemaGroup) => __awaiter$6(this, void 0, void 0, function* () {
var _b;
const v0PresetName = schemaGroup.presetName;
const v1PresetName = (v0PresetName != null && VRMExpressionLoaderPlugin.v0v1PresetNameMap[v0PresetName]) || null;
const name = v1PresetName !== null && v1PresetName !== void 0 ? v1PresetName : schemaGroup.name;
if (name == null) {
console.warn('VRMExpressionLoaderPlugin: One of custom expressions has no name. Ignoring the expression');
return;
}
// duplication check
if (blendShapeNameSet.has(name)) {
console.warn(`VRMExpressionLoaderPlugin: An expression preset ${v0PresetName} has duplicated entries. Ignoring the expression`);
return;
}
blendShapeNameSet.add(name);
const expression = new VRMExpression(name);
gltf.scene.add(expression);
expression.isBinary = (_b = schemaGroup.isBinary) !== null && _b !== void 0 ? _b : false;
// v0 doesn't have ignore properties
// Bind morphTarget
if (schemaGroup.binds) {
schemaGroup.binds.forEach((bind) => __awaiter$6(this, void 0, void 0, function* () {
var _c;
if (bind.mesh === undefined || bind.index === undefined) {
return;
}
const nodesUsingMesh = [];
(_c = json.nodes) === null || _c === void 0 ? void 0 : _c.forEach((node, i) => {
if (node.mesh === bind.mesh) {
nodesUsingMesh.push(i);
}
});
const morphTargetIndex = bind.index;
yield Promise.all(nodesUsingMesh.map((nodeIndex) => __awaiter$6(this, void 0, void 0, function* () {
var _d;
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(`VRMExpressionLoaderPlugin: ${schemaGroup.name} attempts to index ${morphTargetIndex}th morph but not found.`);
return;
}
expression.addBind(new VRMExpressionMorphTargetBind({
primitives,
index: morphTargetIndex,
weight: 0.01 * ((_d = bind.weight) !== null && _d !== void 0 ? _d : 100), // narrowing the range from [ 0.0 - 100.0 ] to [ 0.0 - 1.0 ]
}));
})));
}));
}
// Bind MaterialColor and TextureTransform
const materialValues = schemaGroup.materialValues;
if (materialValues && materialValues.length !== 0) {
materialValues.forEach((materialValue) => {
if (materialValue.materialName === undefined ||
materialValue.propertyName === undefined ||
materialValue.targetValue === undefined) {
return;
}
/**
* アバターのオブジェクトに設定されているマテリアルの内から
* materialValueで指定されているマテリアルを集める。
*
* 特定には名前を使用する。
* アウトライン描画用のマテリアルも同時に集める。
*/
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 ||
mtl.name === materialValue.materialName + ' (Outline)') &&
materials.indexOf(mtl) === -1));
}
else if (material.name === materialValue.materialName && materials.indexOf(material) === -1) {
materials.push(material);
}
}
});
const materialPropertyName = materialValue.propertyName;
materials.forEach((material) => {
// TextureTransformBind
if (materialPropertyName === '_MainTex_ST') {
const scale = new THREE__namespace.Vector2(materialValue.targetValue[0], materialValue.targetValue[1]);
const offset = new THREE__namespace.Vector2(materialValue.targetValue[2], materialValue.targetValue[3]);
offset.y = 1.0 - offset.y - scale.y;
expression.addBind(new VRMExpressionTextureTransformBind({
material,
scale,
offset,
}));
return;
}
// MaterialColorBind
const materialColorType = v0ExpressionMaterialColorMap[materialPropertyName];
if (materialColorType) {
expression.addBind(new VRMExpressionMaterialColorBind({
material,
type: materialColorType,
targetValue: new THREE__namespace.Color().fromArray(materialValue.targetValue),
targetAlpha: materialValue.targetValue[3],