From fb4dd8d80c2f59337d8f54f5963b560a13c98f54 Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Mon, 14 Oct 2024 15:09:53 +1300 Subject: [PATCH 1/8] fix VRM0 spring bones --- src/library/VRMExporterv0.js | 92 +++++++++++++++++++---------- src/library/characterManager.js | 98 ++++++++++++++++++++++++++++--- src/library/download-utils.js | 100 +++++++++++++++++++++++++++++--- src/library/load-utils.js | 15 +++++ src/library/sceneInitializer.js | 8 ++- 5 files changed, 264 insertions(+), 49 deletions(-) diff --git a/src/library/VRMExporterv0.js b/src/library/VRMExporterv0.js index edf4d493..15ad8b97 100644 --- a/src/library/VRMExporterv0.js +++ b/src/library/VRMExporterv0.js @@ -114,7 +114,26 @@ function getVRM0BoneName(name) { return name; } export default class VRMExporterv0 { - async parse(vrm, avatar, screenshot, rootSpringBones, isktx2, scale, onDone) { + /** + * + * @param {*} vrm + * @param {*} avatar + * @param {Object} screenshot + * @param {{ + * bones:{ + * name:string, + * bone:Object3D, + * }[] + * settings:VRMSpringBoneJointSettings, + * center:any, + * colliderGroups:VRMSpringBoneColliderGroup[], + * name:string + * }[] } springBoneGroups + * @param {boolean} isktx2 + * @param {number} scale + * @param {*} onDone + */ + async parse(vrm, avatar, screenshot, springBoneGroups, isktx2, scale, onDone) { const vrmMeta = convertMetaToVRM0(vrm.meta); const humanoid = convertHumanoidToVRM0(vrm.humanoid); const materials = vrm.materials; @@ -546,17 +565,21 @@ export default class VRMExporterv0 { }); //const outputVrmMeta = ToOutputVRMMeta(vrmMeta, icon, outputImages); const outputVrmMeta = vrmMeta; - const rootSpringBonesIndexes = []; - rootSpringBones.forEach(rootSpringBone => { + + const rootGroupSpringBonesIndexes = {} + + springBoneGroups.forEach(group => { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; - if (node.name === rootSpringBone.name) { - rootSpringBonesIndexes.push(i); + if(!rootGroupSpringBonesIndexes[group.name]){ + rootGroupSpringBonesIndexes[group.name] = [] + } + if (node.name === group.bones.name) { + rootGroupSpringBonesIndexes[group.name].push(i); break; } } }) - // should be fetched from rootSpringBonesIndexes instead const colliderGroups = []; @@ -596,16 +619,31 @@ export default class VRMExporterv0 { } const boneGroups = []; - rootSpringBones.forEach(springBone => { - //const boneIndices = findBoneIndices(springBone.name); - const boneIndex = findBoneIndex(springBone.name) - if (boneIndex === -1) { - console.warn("Spring bone " + springBone.name + " was removed during cleanup process. Skipping."); - return; // Skip to the next iteration + springBoneGroups.forEach(boneGroup => { + const settings = boneGroup.settings + const group = { + bones: [], + center: -1, + colliderGroups: [], + dragForce: settings.dragForce, + gravityDir: { x: settings.gravityDir.x, y: settings.gravityDir.y, z: settings.gravityDir.z }, + gravityPower: settings.gravityPower, + hitRadius: settings.hitRadius, + stiffiness: settings.stiffness // for some reason specs mark as stiffiness, but loads it as stiffness + } + for(const bone of boneGroup.bones){ + //const boneIndices = findBoneIndices(springBone.name); + const boneIndex = findBoneIndex(bone.name) + if (boneIndex === -1) { + console.warn("Spring bone " + bone.name + " was removed during cleanup process. Skipping."); + return; // Skip to the next iteration + } + // FIX!! + group.bones.push(boneIndex) } // get the collider group indices const colliderIndices = []; - springBone.colliderGroups.forEach(colliderGroup => { + boneGroup.colliderGroups.forEach(colliderGroup => { const springCollider = colliderGroup.colliders[0]; // sometimes there are no colliders defined in collidersGroup if (springCollider != null) { @@ -616,33 +654,23 @@ export default class VRMExporterv0 { colliderIndices.push(ind); } else { - if (debug) console.warn("No collider group for bone name: ", springParent.name + " was found"); + console.warn("No collider group for bone name: ", springParent.name + " was found"); } } else { - if(debug) console.log("No colliders definition were present in vrm file file for: ", springBone.name + " spring bones") + console.warn("No colliders definition were present in vrm file file for: ", boneGroup.name + " spring bones") } }); + group.colliderGroups.push(...colliderIndices); + group.center = findBoneIndex(boneGroup.center?.name) - - let centerIndex = findBoneIndex(springBone.center?.name); - if (centerIndex == -1) console.warn("no center bone for spring bone " + springBone.name); - // springBone: bone:boneObject, center:boneObject, string:name, array:colliderGroup, settings:object, - const settings = springBone.settings; - - // FIX!! + if (group.center == -1) { + // it's ok if there is no center bone; it's optional + console.debug("no center bone for spring bone " + boneGroup.name); + } boneGroups.push( - { - bones: [boneIndex], - center: centerIndex, - colliderGroups: colliderIndices, - dragForce: settings.dragForce, - gravityDir: { x: settings.gravityDir.x, y: settings.gravityDir.y, z: settings.gravityDir.z }, - gravityPower: settings.gravityPower, - hitRadius: settings.hitRadius, - stiffiness: settings.stiffness // for some reason specs mark as stiffiness, but loads it as stiffness - } + group ); }); diff --git a/src/library/characterManager.js b/src/library/characterManager.js index 6352629f..f96d2e0a 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, VRMSpringBoneCollider } from "@pixiv/three-vrm"; import { getAsArray, disposeVRM, renameVRMBones, addModelData } from "./utils"; import { downloadGLB, downloadVRMWithAvatar } from "../library/download-utils" -import { saveVRMCollidersToUserData } from "./load-utils"; +import { getNodesWithColliders, saveVRMCollidersToUserData } from "./load-utils"; import { cullHiddenMeshes, setTextureToChildMeshes, addChildAtFirst } from "./utils"; import { LipSync } from "./lipsync"; import { LookAtManager } from "./lookatManager"; @@ -70,10 +70,17 @@ export class CharacterManager { } - update(){ + update(deltaTime){ if (this.lookAtManager != null){ this.lookAtManager.update(); } + if(this.avatar){ + for (const prop in this.avatar){ + if (this.avatar[prop]?.vrm != null){ + this.avatar[prop].vrm.springBoneManager?.update(deltaTime); + } + } + } } addLookAtMouse(screenPrecentage, canvasID, camera, enable = true){ @@ -982,8 +989,12 @@ export class CharacterManager { addModelData(vrm, {isVRM0:vrm.meta?.metaVersion === '0'}) - if (this.manifestData.isColliderRequired(traitID)) + if (this.manifestData.isColliderRequired(traitID)){ saveVRMCollidersToUserData(m); + } + + // apply colliders to the spring manager + this._applySpringBoneColliders(vrm); renameVRMBones(vrm); @@ -1029,6 +1040,82 @@ export class CharacterManager { return vrm; } + + /** + * Naive Method that will apply all colliders to all spring bones; + * @param {import('@pixiv/three-vrm').VRM} vrm + */ + _applySpringBoneColliders(vrm){ + + /** + * method to add collider groups to the joints of the new VRM + * @param {import('@pixiv/three-vrm').VRMSpringBoneColliderGroup[]} colliderGroups + */ + function addToJoints(colliderGroups){ + vrm.springBoneManager.joints.forEach((joint)=>{ + for(const group of colliderGroups){ + const isSameName = joint.colliderGroups.find((cg) => cg.name === group.name) + if(isSameName){ + return; + } + if (joint.colliderGroups.indexOf(group) === -1){ + joint.colliderGroups.push(group) + } + } + }) + + } + + // Iterate through the avatar record; + Object.entries(this.avatar).map(([key, entry]) => { + + // Step 1: Check if other trait have colliders; if they do, just copy them over to new trait + const colliderGroups = [] + // first check if the collider group already exists in vrm.springBoneManager + if(entry.vrm.springBoneManager?.colliderGroups.length){ + colliderGroups.push(...entry.vrm.springBoneManager.colliderGroups) + } + + if(colliderGroups.length){ + addToJoints(colliderGroups) + // done + return + } + + // Step 2: If no colliders found, check for saved colliders in the userData + // This is useful for VRMs that have colliders but no spring bones + + // get nodes with colliders + const nodes = getNodesWithColliders(entry.vrm); + if(nodes.length == 0) return + + // For each node with colliders info + nodes.forEach((node) => { + if(!vrm.springBoneManager){ + return + } + + const colliderGroup = { + colliders: [], + name: node.name + } + // Only direct children + for(const child of node.children){ + if (child instanceof VRMSpringBoneCollider){ + if(colliderGroup.colliders.indexOf(child) === -1){ + colliderGroup.colliders.push(child); + } + } + } + + if(colliderGroup.colliders.length){ + addToJoints([colliderGroup]) + } + }) + + }) + } + _modelBaseSetup(model, item, traitID, textures, colors){ const meshTargets = []; @@ -1268,9 +1355,6 @@ class TraitLoadingManager{ const gltfLoader = new GLTFLoader(loadingManager); gltfLoader.crossOrigin = 'anonymous'; gltfLoader.register((parser) => { - // return new VRMLoaderPlugin(parser, {autoUpdateHumanBones: true, helperRoot:vrmHelperRoot}) - // const springBoneLoader = new VRMSpringBoneLoaderPlugin(parser); - // return new VRMLoaderPlugin(parser, {autoUpdateHumanBones: true, springBonePlugin:springBoneLoader}) return new VRMLoaderPlugin(parser, {autoUpdateHumanBones: true}) }) diff --git a/src/library/download-utils.js b/src/library/download-utils.js index 7a1cce11..e8e03af9 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -196,6 +196,82 @@ function parseGLB (glbModel){ }) } +/** + * + * @param {Record>} avatar + * @returns {GroupSpringBones[]} + */ +function getGroupSpringBones (avatar) { + + /** + * @typedef {Object} GroupSpringBones + * @property {THREE.Bone[]} bones + * @property {VRMSpringBoneJointSettings} settings + * @property {VRMSpringBoneColliderGroup[]} colliderGroups + * @property {string} name + * @property {any} center + */ + + /** + * @type {Object[]} + */ + const finalSpringBones= []; + + // add non repeating spring bones + for(const trait in avatar){ + if (avatar[trait]?.vrm?.springBoneManager!= null){ + const joints = avatar[trait].vrm.springBoneManager.joints; + for (const item of joints) { + const doesNameExist = finalSpringBones.some(boneData => boneData.name === item.bone.name); + if (!doesNameExist) { + finalSpringBones.push({ + name:item.bone.name, + settings:item.settings, + bone:item.bone, + colliderGroups:item.colliderGroups, + center:item.center + }); + } + + } + } + } + + //get only the root bone of the last array + /** + * @type {GroupSpringBones[]} + */ + const groupSpringBones = []; + + // create a group for each root bone + finalSpringBones.forEach(springBone => { + const parent = finalSpringBones.find(bone => bone.name == springBone.bone.parent?.name) + if(parent == null){ + // current spring bone is a root bone + groupSpringBones.push({ + bones:[springBone], + settings:springBone.settings, + center:springBone.center, + colliderGroups:springBone.colliderGroups, + name:springBone.bone.name + }); + return; + } + }) + + finalSpringBones.map((springBone) => { + const group = groupSpringBones.find(group => group.bones.find(bone => bone.name == springBone.bone.parent?.name) != null); + if(group != null){ + group.bones.push({ + name:springBone.name, + bone:springBone.bone + }); + } + }) + + return groupSpringBones; +} + function getRootBones (avatar) { const finalSpringBones = []; //const springBonesData = []; @@ -320,16 +396,24 @@ function parseVRM (glbModel, avatar, options){ } reverseBonesXZ(); - const headBone = skinnedMesh.skeleton.bones.filter(bone => bone.name === 'head')[0]; - - const rootSpringBones = getRootBones(avatar); + const isOutputVRM0 = options.outputVRM0 ?? options.isVrm0 ?? false; + // @TODO: change springBone selection logic for VRM1 + const rootSpringBones = isOutputVRM0?getGroupSpringBones(avatar):getRootBones(avatar); // XXX collider bones should be taken from springBone.colliderBones - const colliderBones = []; - - exporter.parse(vrmData, glbModel, screenshot, rootSpringBones, options.ktxCompression, scale, (vrm) => { - resolve(vrm) - }) + // const colliderBones = []; + + if(options.outputVRM0){ + // VRM 0.0 + exporter.parse(vrmData, glbModel, screenshot, rootSpringBones, options.ktxCompression, scale, (vrm) => { + resolve(vrm) + }) + }else{ + // VRM 1.0 has a different amount of parameters + exporter.parse(vrmData, glbModel, screenshot, (vrm) => { + resolve(vrm) + }) + } }) } diff --git a/src/library/load-utils.js b/src/library/load-utils.js index 0c8f3f78..4f4c8387 100644 --- a/src/library/load-utils.js +++ b/src/library/load-utils.js @@ -122,6 +122,21 @@ const saveVRM1Colliders = (gltf) => { } } +/** + * Get the Bones with colliders in userData; these would be from the traits in "colliderTraits" in the manifest + * @param {VRM} vrm + * @returns {Array} an array of objects with the shape of the colliders + */ +export const getNodesWithColliders = (vrm)=>{ + const nodes = []; + vrm.scene.traverse((child)=>{ + if (child.userData?.VRMcolliders && child.userData.VRMcolliders.length > 0){ + nodes.push(child); + } + }) + return nodes; +} + // code from gltf loader, follows the same process of renaming const uniqueNames = (originalName, namesUsed) => { const sanitizedName = PropertyBinding.sanitizeNodeName(originalName || ''); diff --git a/src/library/sceneInitializer.js b/src/library/sceneInitializer.js index aaad3caf..c34f4832 100644 --- a/src/library/sceneInitializer.js +++ b/src/library/sceneInitializer.js @@ -60,14 +60,17 @@ export function sceneInitializer(canvasId) { renderer.setPixelRatio(window.devicePixelRatio); renderer.outputEncoding = THREE.sRGBEncoding; + const clock = new THREE.Clock(); const animate = () => { requestAnimationFrame(animate); + const delta = clock.getDelta(); controls.target.clamp(minPan, maxPan); controls?.update(); - characterManager.update(); + characterManager.update(delta); renderer.render(scene, camera); }; + animate(); const handleMouseClick = (event) => { @@ -98,6 +101,7 @@ export function sceneInitializer(canvasId) { camera, controls, characterManager, - sceneElements + sceneElements, + clock }; } From adbfc385bfaef1ad50d7d11774ec97406d73e802 Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Tue, 15 Oct 2024 08:41:56 +1300 Subject: [PATCH 2/8] Allow pausing the spingbones --- src/library/characterManager.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/library/characterManager.js b/src/library/characterManager.js index f96d2e0a..8a660d67 100644 --- a/src/library/characterManager.js +++ b/src/library/characterManager.js @@ -70,6 +70,18 @@ export class CharacterManager { } + /** + * toggle whether the spring bone animations are paused or not; this is useful when taking screenshots or calculating bone offsets + * @param x true to pause, false to unpause + */ + togglePauseSpringBoneAnimation(x){ + for(const [_,trait] of Object.entries(this.avatar)){ + if(trait.vrm.springBoneManager){ + trait.vrm.springBoneManager.paused =x + } + } + } + update(deltaTime){ if (this.lookAtManager != null){ this.lookAtManager.update(); @@ -77,6 +89,9 @@ export class CharacterManager { if(this.avatar){ for (const prop in this.avatar){ if (this.avatar[prop]?.vrm != null){ + if(this.avatar[prop].vrm.springBoneManager?.paused){ + return + } this.avatar[prop].vrm.springBoneManager?.update(deltaTime); } } From 8fc39416ca2f9a85c1cf0cf839611a4ced8c1ac7 Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Thu, 17 Oct 2024 13:16:35 +1300 Subject: [PATCH 3/8] fix removing trait --- src/library/characterManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/characterManager.js b/src/library/characterManager.js index 8a660d67..3ebbafd0 100644 --- a/src/library/characterManager.js +++ b/src/library/characterManager.js @@ -1313,7 +1313,7 @@ export class CharacterManager { // just dispose for now this._disposeTrait(this.avatar[traitGroupID].vrm) - this.avatar[traitGroupID] = {} + delete this.avatar[traitGroupID] // XXX restore effects without setTimeout } return; From 7ec19cb7467dddc131eb9bee29b6b29e1718a261 Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Wed, 23 Oct 2024 06:38:20 +1300 Subject: [PATCH 4/8] fix check vrm0 exporter --- src/library/download-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/download-utils.js b/src/library/download-utils.js index e8e03af9..adeb76c9 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -369,7 +369,7 @@ function parseVRM (glbModel, avatar, options){ return new Promise(async (resolve) => { - const exporter = isVrm0 ? new VRMExporterv0() : new VRMExporter() + const exporter = options.outputVRM0 ? new VRMExporterv0() : new VRMExporter() const vrmData = { ...getVRMBaseData(avatar), ...getAvatarData(glbModel, metadataMerged), From 91aee7306a0aaf0dd30b4791b6918342b4d35652 Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Wed, 23 Oct 2024 07:55:48 +1300 Subject: [PATCH 5/8] make export type more explicit --- src/components/ExportMenu.jsx | 26 +++++++++++++++++++++----- src/library/characterManager.js | 1 - src/library/download-utils.js | 9 +++++---- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/components/ExportMenu.jsx b/src/components/ExportMenu.jsx index 084b8271..656f80c7 100644 --- a/src/components/ExportMenu.jsx +++ b/src/components/ExportMenu.jsx @@ -20,7 +20,6 @@ export const ExportMenu = () => { const currentOption = local["mergeOptions_sel_option"] || 0; const createTextureAtlas = local["mergeOptions_create_atlas"] == null ? true:local["mergeOptions_create_atlas"] return { - // isVrm0 : true, createTextureAtlas : createTextureAtlas, mToonAtlasSize:getAtlasSize(local["mergeOptions_atlas_mtoon_size"] || 6), mToonAtlasSizeTransp:getAtlasSize(local["mergeOptions_atlas_mtoon_transp_size"] || 6), @@ -33,10 +32,16 @@ export const ExportMenu = () => { } } - const downloadVRM = () =>{ + const downloadVRM = (version) =>{ const options = getOptions(); + /** + * Blindly assume the whole avatar is VRM0 if the first vrm is VRM0 + */ + options.isVrm0 = Object.values(characterManager.avatar)[0].vrm.meta.metaVersion=='0' + options.outputVRM0 = !(version === 1) characterManager.downloadVRM(name, options); } + const downloadGLB = () =>{ const options = getOptions(); characterManager.downloadGLB(name, options); @@ -54,14 +59,25 @@ export const ExportMenu = () => { downloadGLB() }} /> - downloadVRM(0)} /> + {/** + * Only show VRM1 download button if the avatar is VRM1 + */} + {Object.values(characterManager.avatar)[0].vrm.meta.metaVersion=='1'&&downloadVRM(1)} + />} ) } diff --git a/src/library/characterManager.js b/src/library/characterManager.js index 3ebbafd0..addcafd2 100644 --- a/src/library/characterManager.js +++ b/src/library/characterManager.js @@ -252,7 +252,6 @@ export class CharacterManager { exportOptions = exportOptions || {}; const manifestOptions = this.manifestData.getExportOptions(); const finalOptions = { ...manifestOptions, ...exportOptions }; - finalOptions.isVrm0 = true; // currently vrm1 not supported finalOptions.screenshot = this._getPortaitScreenshotTexture(false, finalOptions); // Log the final export options diff --git a/src/library/download-utils.js b/src/library/download-utils.js index adeb76c9..fe4991a7 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -358,7 +358,7 @@ function getVRMMeta( vrmMeta){ function parseVRM (glbModel, avatar, options){ const { screenshot = null, - isVrm0 = false, + isVrm0 = false, vrmMeta = null, scale = 1, vrmName = "CharacterCreator" @@ -369,7 +369,8 @@ function parseVRM (glbModel, avatar, options){ return new Promise(async (resolve) => { - const exporter = options.outputVRM0 ? new VRMExporterv0() : new VRMExporter() + const isOutputVRM0 = options.outputVRM0 ?? options.isVrm0 ?? false; + const exporter = isOutputVRM0 ? new VRMExporterv0() : new VRMExporter() const vrmData = { ...getVRMBaseData(avatar), ...getAvatarData(glbModel, metadataMerged), @@ -397,13 +398,13 @@ function parseVRM (glbModel, avatar, options){ reverseBonesXZ(); - const isOutputVRM0 = options.outputVRM0 ?? options.isVrm0 ?? false; + // @TODO: change springBone selection logic for VRM1 const rootSpringBones = isOutputVRM0?getGroupSpringBones(avatar):getRootBones(avatar); // XXX collider bones should be taken from springBone.colliderBones // const colliderBones = []; - if(options.outputVRM0){ + if(isOutputVRM0){ // VRM 0.0 exporter.parse(vrmData, glbModel, screenshot, rootSpringBones, options.ktxCompression, scale, (vrm) => { resolve(vrm) From e22ca883f79354192515e8bd5e15d460178d4adf Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Wed, 23 Oct 2024 08:59:00 +1300 Subject: [PATCH 6/8] updates --- src/components/ExportMenu.jsx | 11 ----------- src/library/download-utils.js | 5 ++++- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/components/ExportMenu.jsx b/src/components/ExportMenu.jsx index 656f80c7..a5f1b111 100644 --- a/src/components/ExportMenu.jsx +++ b/src/components/ExportMenu.jsx @@ -67,17 +67,6 @@ export const ExportMenu = () => { className={styles.button} onClick={()=>downloadVRM(0)} /> - {/** - * Only show VRM1 download button if the avatar is VRM1 - */} - {Object.values(characterManager.avatar)[0].vrm.meta.metaVersion=='1'&&downloadVRM(1)} - />} ) } diff --git a/src/library/download-utils.js b/src/library/download-utils.js index fe4991a7..e66d67d7 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -369,7 +369,10 @@ function parseVRM (glbModel, avatar, options){ return new Promise(async (resolve) => { - const isOutputVRM0 = options.outputVRM0 ?? options.isVrm0 ?? false; + /** + * Because vrm1 Exporter is broken, always default to vrm0 exporter; + */ + const isOutputVRM0 = options.outputVRM0 ?? options.isVrm0 ?? true; const exporter = isOutputVRM0 ? new VRMExporterv0() : new VRMExporter() const vrmData = { ...getVRMBaseData(avatar), From dd282bc60245af1d36f6c6032b85b761e23b07b2 Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Wed, 23 Oct 2024 09:09:04 +1300 Subject: [PATCH 7/8] updates --- src/components/ExportMenu.jsx | 11 ----------- src/library/download-utils.js | 5 ++++- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/components/ExportMenu.jsx b/src/components/ExportMenu.jsx index 656f80c7..a5f1b111 100644 --- a/src/components/ExportMenu.jsx +++ b/src/components/ExportMenu.jsx @@ -67,17 +67,6 @@ export const ExportMenu = () => { className={styles.button} onClick={()=>downloadVRM(0)} /> - {/** - * Only show VRM1 download button if the avatar is VRM1 - */} - {Object.values(characterManager.avatar)[0].vrm.meta.metaVersion=='1'&&downloadVRM(1)} - />} ) } diff --git a/src/library/download-utils.js b/src/library/download-utils.js index fe4991a7..e66d67d7 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -369,7 +369,10 @@ function parseVRM (glbModel, avatar, options){ return new Promise(async (resolve) => { - const isOutputVRM0 = options.outputVRM0 ?? options.isVrm0 ?? false; + /** + * Because vrm1 Exporter is broken, always default to vrm0 exporter; + */ + const isOutputVRM0 = options.outputVRM0 ?? options.isVrm0 ?? true; const exporter = isOutputVRM0 ? new VRMExporterv0() : new VRMExporter() const vrmData = { ...getVRMBaseData(avatar), From 00a525012cbea5acd81fc5a54ddc254fb25aa1a4 Mon Sep 17 00:00:00 2001 From: Benjythebee Date: Wed, 23 Oct 2024 09:15:39 +1300 Subject: [PATCH 8/8] fix geometry vrm1 --- src/library/merge-geometry.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/library/merge-geometry.js b/src/library/merge-geometry.js index 24c3d425..d3334f4b 100644 --- a/src/library/merge-geometry.js +++ b/src/library/merge-geometry.js @@ -510,7 +510,6 @@ export async function combine(avatar, options) { stdAtlasSizeTransp = 4096, exportMtoonAtlas = false, exportStdAtlas = true, - isVrm0 = false, scale = 1, twoSidedMaterial = false, } = options; @@ -621,16 +620,15 @@ export async function combine(avatar, options) { } }); - const { dest } = mergeGeometry({ meshes:skinnedMeshes, scale },isVrm0); + const { dest } = mergeGeometry({ meshes:skinnedMeshes, scale }); const geometry = new THREE.BufferGeometry(); - // modify all merged vertices to reflect vrm0 format - if (isVrm0){ - for (let i = 0; i < dest.attributes.position.array.length; i+=3){ - dest.attributes.position.array[i] *= -1 - dest.attributes.position.array[i+2] *= -1 - } + + for (let i = 0; i < dest.attributes.position.array.length; i+=3){ + dest.attributes.position.array[i] *= -1 + dest.attributes.position.array[i+2] *= -1 } + geometry.attributes = dest.attributes; geometry.morphAttributes = dest.morphAttributes; @@ -712,7 +710,7 @@ function mergeSourceMorphTargetDictionaries({ sourceMorphTargetDictionaries }) { }); return destMorphTargetDictionary; } -function mergeSourceMorphAttributes({ meshes, sourceMorphTargetDictionaries, sourceMorphAttributes, destMorphTargetDictionary, scale}, isVrm0 = false) { +function mergeSourceMorphAttributes({ meshes, sourceMorphTargetDictionaries, sourceMorphAttributes, destMorphTargetDictionary, scale}) { const propertyNameSet = new Set(); // e.g. ["position", "normal"] const allSourceMorphAttributes = Array.from(sourceMorphAttributes.values()); allSourceMorphAttributes.forEach((sourceMorphAttributes) => { @@ -944,7 +942,7 @@ function mergeSourceIndices({ meshes }) { // } -export function mergeGeometry({ meshes, scale }, isVrm0 = false) { +export function mergeGeometry({ meshes, scale }) { // eslint-disable-next-line no-unused-vars let uvcount = 0; meshes.forEach(mesh => { @@ -985,7 +983,7 @@ export function mergeGeometry({ meshes, scale }, isVrm0 = false) { sourceMorphTargetDictionaries: source.morphTargetDictionaries, destMorphTargetDictionary, scale, - },isVrm0); + }); dest.morphTargetInfluences = mergeMorphTargetInfluences({ meshes, sourceMorphTargetDictionaries: source.morphTargetDictionaries,