diff --git a/src/library/characterManager.js b/src/library/characterManager.js index c655821f..28da57f5 100644 --- a/src/library/characterManager.js +++ b/src/library/characterManager.js @@ -3,10 +3,10 @@ import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader" import { AnimationManager } from "./animationManager" import { ScreenshotManager } from "./screenshotManager"; import { BlinkManager } from "./blinkManager"; -import { VRMLoaderPlugin, VRMUtils } from "@pixiv/three-vrm"; +import { VRMLoaderPlugin } from "@pixiv/three-vrm"; import { getAsArray, disposeVRM, renameVRMBones, addModelData } from "./utils"; import { downloadGLB, downloadVRMWithAvatar } from "../library/download-utils" -import { saveVRMCollidersToUserData } from "./load-utils"; +import { saveVRMCollidersToUserData, renameMorphTargets} from "./load-utils"; import { cullHiddenMeshes, setTextureToChildMeshes, addChildAtFirst } from "./utils"; import { LipSync } from "./lipsync"; import { LookAtManager } from "./lookatManager"; @@ -554,7 +554,21 @@ export class CharacterManager { console.error("Error loading blendshape trait "+traitGroupID, blendshapeGroupId, blendshapeTraitId); } } - + /** + * remove blendshape trait + * @param {string} traitGroupID + * @param {string} blendshapeGroupId + * @returns + */ + removeBlendShapeTrait(groupTraitID, blendShapeGroupId){ + const currentTrait = this.avatar[groupTraitID]; + if (currentTrait){ + this._loadBlendShapeTrait(groupTraitID,blendShapeGroupId,null); + } + else{ + console.warn(`No trait with name: ${ groupTraitID } was found.`) + } + } /** * Loads a specific trait based on group and trait IDs. * @@ -971,6 +985,7 @@ export class CharacterManager { toggleBinaryBlendShape = (model,blendshape,enable)=>{ model.traverse((child)=>{ if(child.isMesh || child.isSkinnedMesh){ + const mesh = child; if(!mesh.morphTargetDictionary || !mesh.morphTargetInfluences) return const blendShapeIndex = mesh.morphTargetDictionary[blendshape.id]; @@ -1089,6 +1104,14 @@ export class CharacterManager { saveVRMCollidersToUserData(m); renameVRMBones(vrm); + + renameMorphTargets(m); + + /** + * unregister the Blendshapes from the manifest -if any. + * This is to avoid BlendshapeTraits being affected by the vrm.ExpressionManager + */ + this._unregisterMorphTargetsFromManifest(vrm); if (this.manifestData.isLipsyncTrait(traitID)) this.lipSync = new LipSync(vrm); @@ -1112,12 +1135,6 @@ export class CharacterManager { vrm.scene.traverse((child) => { if (child.isSkinnedMesh) { - const newMorphTargets = {}; - const targetNames = getAsArray(child.geometry.userData?.targetNames); - for (let i =0; i < targetNames.length;i++){ - newMorphTargets[targetNames[i]] = child.morphTargetDictionary[i]; - } - child.morphTargetDictionary = newMorphTargets; for (let i =0; i < child.skeleton.bones.length;i++){ child.skeleton.bones[i].userData.vrm0RestPosition = { ... child.skeleton.bones[i].position } } @@ -1132,6 +1149,29 @@ export class CharacterManager { return vrm; } + + /** + * + * @param {import("@pixiv/three-vrm").VRM} vrm + * @returns + */ + _unregisterMorphTargetsFromManifest(vrm){ + const manifestBlendShapes = this.manifestData.getAllBlendShapeTraits() + const expressions = vrm.expressionManager?.expressions + if(manifestBlendShapes.length == 0) return + if(!expressions) return + const expressionToRemove = [] + for(const expression of expressions){ + if(manifestBlendShapes.map((b)=>b.id).includes(expression.expressionName)){ + expressionToRemove.push(expression) + } + } + + for(const expression of expressionToRemove){ + vrm.expressionManager.unregisterExpression(expression) + } + } + _modelBaseSetup(model, item, traitID, textures, colors){ const meshTargets = []; diff --git a/src/library/load-utils.js b/src/library/load-utils.js index f1ae7587..1a059130 100644 --- a/src/library/load-utils.js +++ b/src/library/load-utils.js @@ -34,7 +34,7 @@ export const renameMorphTargets = (gltf) => { const json = gltf.parser.json const meshesJson = json.meshes; const associations = gltf.parser.associations - + gltf.scene.traverse((child) => { if(child instanceof SkinnedMesh){ if(child.morphTargetDictionary){ @@ -47,7 +47,6 @@ export const renameMorphTargets = (gltf) => { const meshJson = meshesJson[meshIndex] const primitives = meshJson?.primitives[primitivesIndex] - if(primitives?.extras?.targetNames){ const targetNames = primitives.extras.targetNames; for (let i = 0; i < targetNames.length; i++){ @@ -58,6 +57,7 @@ export const renameMorphTargets = (gltf) => { } if(hasEditedMorphs){ + // remove all morph target keys that are numbers for(const key in child.morphTargetDictionary){ if(!isNaN(parseInt(key))){ delete child.morphTargetDictionary[key] diff --git a/src/pages/Appearance.jsx b/src/pages/Appearance.jsx index d0c1d673..4555a326 100644 --- a/src/pages/Appearance.jsx +++ b/src/pages/Appearance.jsx @@ -461,21 +461,26 @@ const BlendShapeTraitView = ({selectedTrait,onBack,selectedBlendShapeTrait,setSe const groups = characterManager.getBlendShapeGroupTraits(selectedTrait?.traitGroup.trait||"",selectedTrait?.id||""); /** - * + * + * @param {string} traitGroup + * @param {import('../library/CharacterManifestData').BlendShapeGroup} blendShapeGroupTrait + */ + const removeBlendShapeTrait = (traitGroup,blendShapeGroupTrait)=>{ + characterManager.removeBlendShapeTrait(traitGroup,blendShapeGroupTrait.trait); + const blendShapeTraitCopy = {...selectedBlendShapeTrait}; + delete blendShapeTraitCopy[blendShapeGroupTrait.trait] + setSelectedBlendshapeTrait(blendShapeTraitCopy); + } + /** * @param {import('../library/CharacterManifestData').BlendShapeTrait} newBlendShape */ const selectBlendShapeTrait = (newBlendShape)=>{ - if(newBlendShape.id==null){ - const parent = newBlendShape.parentGroup; - characterManager.loadBlendShapeTrait(selectedTrait?.traitGroup.trait||"",parent.trait||"",null); - return - } const parent = newBlendShape.parentGroup; characterManager.loadBlendShapeTrait(selectedTrait?.traitGroup.trait||"",parent.trait||"",newBlendShape?.id||''); moveCamera({ targetY: parent.cameraTarget.height, distance: parent.cameraTarget.distance}) - const prev = {...selectedBlendShapeTrait}; - prev[parent.trait||''] = newBlendShape.id; - setSelectedBlendshapeTrait(prev); + const blendShapeTraitCopy = {...selectedBlendShapeTrait}; + blendShapeTraitCopy[parent.trait||''] = newBlendShape.id; + setSelectedBlendshapeTrait(blendShapeTraitCopy); } return ( @@ -496,7 +501,7 @@ const BlendShapeTraitView = ({selectedTrait,onBack,selectedBlendShapeTrait,setSe src={cancel} active={!selectedBlendShapeTrait[group.trait]} blendshapeID="cancel" - select={()=>selectBlendShapeTrait(new BlendShapeTrait(group,{id:null}))} + select={()=>removeBlendShapeTrait(selectedTrait.traitGroup.trait,group)} /> {group.collection.map((blendShapeTrait)=>{ let active = blendShapeTrait.id === selectedBlendShapeTrait[group.trait]