From cc2a3cc4d048d617f03b03a67d3d07c31487a06a Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Thu, 9 Nov 2023 22:10:00 -0600 Subject: [PATCH 01/28] set ui for different material size --- src/pages/Optimizer.jsx | 118 ++++++++++++++++++++++----------- src/pages/Optimizer.module.css | 2 +- 2 files changed, 79 insertions(+), 41 deletions(-) diff --git a/src/pages/Optimizer.jsx b/src/pages/Optimizer.jsx index b726254a..23728b8d 100644 --- a/src/pages/Optimizer.jsx +++ b/src/pages/Optimizer.jsx @@ -25,8 +25,10 @@ function Optimizer({ const [currentVRM, setCurrentVRM] = useState(null); const [lastVRM, setLastVRM] = useState(null); const [nameVRM, setNameVRM] = useState(""); - const [atlasSize, setAtlasSize] = useState(4096); - const [atlasValue, setAtlasValue] = useState(6); + const [atlasStd, setAtlasStd] = useState(6); + const [atlasStdTransp, setAtlasStdTransp] = useState(6); + const [atlasMtoon, setAtlasMtoon] = useState(6); + const [atlasMtoonTransp, setAtlasMtoonTransp] = useState(6); const [downloadOnDrop, setDownloadOnDrop] = useState(false) const { playSound } = React.useContext(SoundContext) @@ -39,8 +41,11 @@ function Optimizer({ const download = () => { const vrmData = currentVRM.userData.vrm - downloadVRM(model, vrmData,nameVRM + "_merged",null,atlasSize,1,true, null, true) + downloadVRM(model, vrmData,nameVRM + "_merged",null,getAtlasSize(atlasMtoon),1,true, null, true) } + useEffect(()=>{ + console.log("onlaod"); + },[]) useEffect(() => { const fetchData = async () => { @@ -79,43 +84,57 @@ function Optimizer({ setDownloadOnDrop(event.target.checked); } - const handleChangeAtlasSize = async (event) => { - const val = parseInt(event.target.value); - if (val > 8) - setAtlasValue(8) - else if (val < 0) - setAtlasValue(0) - else - setAtlasValue(val) - - switch (val){ + const getAtlasSize = (value) =>{ + switch (value){ case 1: - setAtlasSize(128); - break; + return 128; case 2: - setAtlasSize(256); - break; + return 256; case 3: - setAtlasSize(512); - break; + return 512; case 4: - setAtlasSize(1024); - break; + return 1024; case 5: - setAtlasSize(2048); - break; + return 2048; case 6: - setAtlasSize(4096); - break; + return 4096; case 7: - setAtlasSize(8192); - break; + return 8192; case 8: - setAtlasSize(16384); - break; + return 16384; default: - break; + return 4096; } + } + + const handleChangeAtlasSize = async (event, type) => { + console.log(event.target); + console.log(type); + let val = parseInt(event.target.value); + if (val > 8) + val = 8; + else if (val < 0) + val = 0; + + const setAtlasSize = (size) => { + switch (type){ + case 'standard opaque': + // save to user prefs + setAtlasStd(size); + break; + case 'standard transparent': + setAtlasStdTransp(size); + break; + case 'mtoon opaque': + setAtlasMtoon(size); + break; + case 'mtoon transparent': + setAtlasMtoonTransp(size); + break; + } + } + setAtlasSize(val) + } @@ -124,16 +143,9 @@ function Optimizer({ const vrm = await loadVRM(path); const name = getFileNameWithoutExtension(file.name); - // if (currentVRM != null){ - // disposeVRM(currentVRM); - // } setNameVRM(name); setCurrentVRM(vrm); - - - - //downloadVRM(model, vrmData,nameVRM + "_merged",null,atlasSize,1,true, null, true) - //setUploadVRMURL(path); + console.log(vrm) } const handleFilesDrop = async(files) => { @@ -160,11 +172,37 @@ function Optimizer({
- Atlas size: {atlasSize + " x " + atlasSize} + Standard Atlas Size +
+
+
+ Opaque: {getAtlasSize(atlasStd) + " x " + getAtlasSize(atlasStd)} +
+ + handleChangeAtlasSize(value, 'standard opaque')} min={1} max={8} step={1}/> +
+
+ Transparent: {getAtlasSize(atlasStdTransp) + " x " + getAtlasSize(atlasStdTransp)}
+ handleChangeAtlasSize(value, 'standard transparent')} min={1} max={8} step={1}/> +


- +
+ MToon Atlas Size +
+
+
+ Opaque: {getAtlasSize(atlasMtoon) + " x " + getAtlasSize(atlasMtoon)} +
+ + handleChangeAtlasSize(value, 'mtoon opaque')} min={1} max={8} step={1}/>
+
+ Transparent: {getAtlasSize(atlasMtoonTransp) + " x " + getAtlasSize(atlasMtoonTransp)} +
+ handleChangeAtlasSize(value, 'mtoon transparent')} min={1} max={8} step={1}/> +


+
Drag Drop - Download
diff --git a/src/pages/Optimizer.module.css b/src/pages/Optimizer.module.css index 6868d52f..6958c001 100644 --- a/src/pages/Optimizer.module.css +++ b/src/pages/Optimizer.module.css @@ -24,7 +24,7 @@ text-shadow: 1px 1px 2px black; font-size: 16px; word-spacing: 2px; - margin-bottom: 30px; + margin-bottom: 10px; } .scrollContainer { height: 100%; From 9b40ace2f75c09551b87e2bac840c7c3e003fdc7 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Fri, 10 Nov 2023 11:38:23 -0600 Subject: [PATCH 02/28] add select export material option --- src/pages/Optimizer.jsx | 85 ++++++++++++++++++++++++++++++----------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/src/pages/Optimizer.jsx b/src/pages/Optimizer.jsx index 23728b8d..5805cc36 100644 --- a/src/pages/Optimizer.jsx +++ b/src/pages/Optimizer.jsx @@ -30,6 +30,8 @@ function Optimizer({ const [atlasMtoon, setAtlasMtoon] = useState(6); const [atlasMtoonTransp, setAtlasMtoonTransp] = useState(6); const [downloadOnDrop, setDownloadOnDrop] = useState(false) + const [currentOption, setCurrentOption] = useState(0); + const [options] = useState(["Merge to Standard", "Merge to MToon", "Keep Both"]) const { playSound } = React.useContext(SoundContext) const { isMute } = React.useContext(AudioContext) @@ -43,9 +45,6 @@ function Optimizer({ const vrmData = currentVRM.userData.vrm downloadVRM(model, vrmData,nameVRM + "_merged",null,getAtlasSize(atlasMtoon),1,true, null, true) } - useEffect(()=>{ - console.log("onlaod"); - },[]) useEffect(() => { const fetchData = async () => { @@ -84,6 +83,25 @@ function Optimizer({ setDownloadOnDrop(event.target.checked); } + const prevOption = () => { + console.log(currentOption); + console.log(options.length); + console.log(options) + if (currentOption <= 0) + setCurrentOption(options.length-1); + else + setCurrentOption(currentOption - 1) + } + + const nextOption = () => { + + if (currentOption >= options.length-1) + setCurrentOption(0); + else + setCurrentOption(currentOption + 1); + + } + const getAtlasSize = (value) =>{ switch (value){ case 1: @@ -108,8 +126,6 @@ function Optimizer({ } const handleChangeAtlasSize = async (event, type) => { - console.log(event.target); - console.log(type); let val = parseInt(event.target.value); if (val > 8) val = 8; @@ -133,9 +149,7 @@ function Optimizer({ break; } } - setAtlasSize(val) - - + setAtlasSize(val) } const handleVRMDrop = async (file) =>{ @@ -171,26 +185,51 @@ function Optimizer({
-
- Standard Atlas Size + +
+ Merge Atlas Type

-
- Opaque: {getAtlasSize(atlasStd) + " x " + getAtlasSize(atlasStd)} +
+
+
{options[currentOption]}
+
+


- handleChangeAtlasSize(value, 'standard opaque')} min={1} max={8} step={1}/> -
+ {(currentOption === 0 || currentOption == 2)&&( + <> +
+ Standard Atlas Size +
+
- Transparent: {getAtlasSize(atlasStdTransp) + " x " + getAtlasSize(atlasStdTransp)} -
- handleChangeAtlasSize(value, 'standard transparent')} min={1} max={8} step={1}/> -


+ Opaque: {getAtlasSize(atlasStd) + " x " + getAtlasSize(atlasStd)} +
+ handleChangeAtlasSize(value, 'standard opaque')} min={1} max={8} step={1}/> +
+
+ Transparent: {getAtlasSize(atlasStdTransp) + " x " + getAtlasSize(atlasStdTransp)} +
+ handleChangeAtlasSize(value, 'standard transparent')} min={1} max={8} step={1}/> +


+ + )} + + {(currentOption === 1 || currentOption == 2)&&( + <>
- MToon Atlas Size -
-
+ MToon Atlas Size +
+
Opaque: {getAtlasSize(atlasMtoon) + " x " + getAtlasSize(atlasMtoon)}
@@ -202,10 +241,12 @@ function Optimizer({
handleChangeAtlasSize(value, 'mtoon transparent')} min={1} max={8} step={1}/>


- + + )}
Drag Drop - Download
+
From b8543f41f6ac41025b055ea52623945fe8dd892d Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Fri, 10 Nov 2023 17:02:24 -0600 Subject: [PATCH 03/28] update css for optimizer --- src/pages/Optimizer.module.css | 90 ++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/src/pages/Optimizer.module.css b/src/pages/Optimizer.module.css index 6958c001..c08fbb74 100644 --- a/src/pages/Optimizer.module.css +++ b/src/pages/Optimizer.module.css @@ -9,33 +9,69 @@ background: rgba(5, 11, 14, 0.8); z-index: 1000; user-select: none; - } - .traitInfoTitle { - color: white; - text-transform: uppercase; - text-shadow: 1px 1px 2px black; - font-size: 16px; - word-spacing: 2px; - margin-bottom: 10px; - } - .traitInfoText { - color: rgb(179, 179, 179); - /* text-transform: uppercase; */ - text-shadow: 1px 1px 2px black; - font-size: 16px; - word-spacing: 2px; - margin-bottom: 10px; - } - .scrollContainer { - height: 100%; - width: 80%; - overflow-y: scroll; - position: relative; - overflow-x: hidden !important; - margin: 30px; - height: -webkit-calc(100% - 40px); - height: calc(100% - 40px); - } +} +.traitInfoTitle { + color: white; + text-transform: uppercase; + text-shadow: 1px 1px 2px black; + font-size: 16px; + word-spacing: 2px; + margin-bottom: 10px; +} +.traitInfoText { + color: rgb(179, 179, 179); + /* text-transform: uppercase; */ + text-shadow: 1px 1px 2px black; + font-size: 16px; + word-spacing: 2px; + margin-bottom: 10px; +} + +.flexSelect{ + display: flex; + justify-content: space-between; + width: 90%; + height:40px; + align-items: center; +} +.arrow-button { + cursor: pointer; + overflow: hidden; + opacity: 0.8; + width: 32px; + height: 32px; + margin: 2px; + text-align: center; + outline-color: #3b434f; + outline-width: 2px; + outline-style: solid; + align-items: center; + background-color: #1e2530; +} +.left-button{ + background: url('/ui/backButton_small.png'); + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +.right-button{ + background: url('/ui/nextButton_small.png'); + background-position: center; + background-repeat: no-repeat; + background-size: cover; +} + +.scrollContainer { + height: 100%; + width: 80%; + overflow-y: scroll; + position: relative; + overflow-x: hidden !important; + margin: 30px; + height: -webkit-calc(100% - 40px); + height: calc(100% - 40px); +} /* Hide the default checkbox */ .custom-checkbox input[type="checkbox"] { display: none; From b07e8e53f977811437b25a0cf9da45f698a443c5 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Fri, 10 Nov 2023 17:02:44 -0600 Subject: [PATCH 04/28] set text to right side in model information --- src/components/ModelInformation.module.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/ModelInformation.module.css b/src/components/ModelInformation.module.css index 9b7e2753..1288a135 100644 --- a/src/components/ModelInformation.module.css +++ b/src/components/ModelInformation.module.css @@ -10,6 +10,7 @@ background: rgba(5, 11, 14, 0.8); z-index: 1000; user-select: none; + text-align: right; } .scrollContainer { @@ -21,6 +22,7 @@ margin: 30px; height: -webkit-calc(100% - 40px); height: calc(100% - 40px); + } .traitInfoTitle { @@ -30,6 +32,8 @@ font-size: 16px; word-spacing: 2px; margin-bottom: 10px; + text-align: right; + } .traitInfoText { color: rgb(179, 179, 179); @@ -38,6 +42,7 @@ font-size: 16px; word-spacing: 2px; margin-bottom: 30px; + text-align: right; } .input-box { width: 60px; /* Adjust as needed */ From 00d7e0f5d0b14837cdbf67a4c137aba4e5dd3d8f Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Fri, 10 Nov 2023 17:03:32 -0600 Subject: [PATCH 05/28] move clone mesh outside create texture-atlas --- src/library/create-texture-atlas.js | 20 +---------------- src/library/merge-geometry.js | 33 ++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/src/library/create-texture-atlas.js b/src/library/create-texture-atlas.js index 52650529..2921af03 100644 --- a/src/library/create-texture-atlas.js +++ b/src/library/create-texture-atlas.js @@ -233,26 +233,8 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize = // save material color from here meshes.forEach((mesh) => { - //console.log(mesh.geometry.attributes.uv) - - const boneName = mesh.type == "Mesh" ? mesh.parent.name:null; - const originalGlobalPosition = new THREE.Vector3(); - const originalGlobalScale = new THREE.Vector3(); - mesh.getWorldPosition(originalGlobalPosition); - mesh.getWorldScale(originalGlobalScale) - mesh = mesh.clone(); - - if (mesh.type == "Mesh"){ - const rotationMatrix = new THREE.Matrix4(); - const rotation = new THREE.Quaternion() - mesh.getWorldQuaternion(rotation); - rotationMatrix.makeRotationFromQuaternion(rotation); - mesh.userData.boneName = boneName; - mesh.userData.globalPosition = originalGlobalPosition; - mesh.userData.globalScale = originalGlobalScale; - mesh.userData.globalRotationMatrix = rotationMatrix; - } + mesh = mesh.clone(); const material = mesh.material.length == null ? mesh.material : mesh.material[0]; // use the vrmData of the first material, and call it atlas if it exists diff --git a/src/library/merge-geometry.js b/src/library/merge-geometry.js index 89005a95..15c4a3ff 100644 --- a/src/library/merge-geometry.js +++ b/src/library/merge-geometry.js @@ -360,9 +360,40 @@ export async function combineNoAtlas({ avatar, scale = 1 }, isVrm0 = false) { return group; } +function cloneAndStoreParentInfo(mesh){ + const boneName = mesh.parent.name; + const originalGlobalPosition = new THREE.Vector3(); + const originalGlobalScale = new THREE.Vector3(); + mesh.getWorldPosition(originalGlobalPosition); + mesh.getWorldScale(originalGlobalScale) + mesh = mesh.clone(); + + const rotationMatrix = new THREE.Matrix4(); + const rotation = new THREE.Quaternion() + mesh.getWorldQuaternion(rotation); + rotationMatrix.makeRotationFromQuaternion(rotation); + + mesh.userData.boneName = boneName; + mesh.userData.globalPosition = originalGlobalPosition; + mesh.userData.globalScale = originalGlobalScale; + mesh.userData.globalRotationMatrix = rotationMatrix; + + return mesh; +} + export async function combine({ transparentColor, avatar, atlasSize = 4096, scale = 1 }, isVrm0 = false) { + // convert meshes to skinned meshes first + const cloneNonSkinnedMeshes = findChildrenByType(avatar, ["Mesh"]); + for (let i =0; i < cloneNonSkinnedMeshes.length;i++){ + cloneNonSkinnedMeshes[i] = cloneAndStoreParentInfo(cloneNonSkinnedMeshes[i]); + } + + const cloneSkinnedMeshes = findChildrenByType(avatar, ["SkinnedMesh"]); + + const allMeshes = [...cloneNonSkinnedMeshes, ...cloneSkinnedMeshes]; + const { bakeObjects, textures, vrmMaterial } = - await createTextureAtlas({ transparentColor, atlasSize, meshes: findChildrenByType(avatar, ["SkinnedMesh","Mesh"])}); + await createTextureAtlas({ transparentColor, atlasSize, meshes: allMeshes}); const meshes = bakeObjects.map((bakeObject) => bakeObject.mesh); const material = new THREE.MeshStandardMaterial({ From ce1b1950b7b962b1ab620cd90a86379067cf3360 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Fri, 10 Nov 2023 19:04:50 -0600 Subject: [PATCH 06/28] utils function to get sorted materials --- src/library/utils.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/library/utils.js b/src/library/utils.js index bbc3c938..e239f311 100644 --- a/src/library/utils.js +++ b/src/library/utils.js @@ -115,6 +115,41 @@ export const cullHiddenMeshes = (avatar) => { CullHiddenFaces(models) } +export function getMaterialsSortedByArray (meshes){ + const stdMats = []; + const stdCutoutpMats = []; + const stdTranspMats = []; + const mToonMats = []; + const mToonCutoutMats = []; + const mToonTranspMats = []; + + meshes.forEach(mesh => { + const mats = getAsArray(mesh.material); + mats.forEach(mat => { + if (mat.type == "ShaderMaterial"){ + if (mat.transparent == true) + mToonTranspMats.push(mat); + else if (mat.uniforms.alphaTest.value != 0) + mToonCutoutMats.push(mat); + else + mToonMats.push(mat); + } + else{ + if (mat.transparent == true) + stdTranspMats.push(mat); + else if (mat.alphaTest != 0) + stdCutoutpMats.push(mat); + else + stdMats.push(mat); + + } + }); + }); + + + return { stdMats, stdCutoutpMats, stdTranspMats , mToonMats, mToonCutoutMats , mToonTranspMats } +} + export async function getModelFromScene(avatarScene, format = 'glb', skinColor = new THREE.Color(1, 1, 1), scale = 1) { if (format && format === 'glb') { const exporter = new GLTFExporter(); From 953863f412318babf3f0dc1df8c4021d09f06952 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Fri, 10 Nov 2023 19:05:16 -0600 Subject: [PATCH 07/28] update modelinformation component to use new function to sort materials --- src/components/ModelInformation.jsx | 47 ++++++----------------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/src/components/ModelInformation.jsx b/src/components/ModelInformation.jsx index 8e9b9c04..5713bacb 100644 --- a/src/components/ModelInformation.jsx +++ b/src/components/ModelInformation.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react" import styles from "./ModelInformation.module.css" import MenuTitle from "./MenuTitle" import { findChildrenByType } from "../library/utils"; -import { getAsArray } from "../library/utils"; +import { getAsArray, getMaterialsSortedByArray } from "../library/utils"; export default function ModelInformation({currentVRM}){ const [meshQty, setMeshQty] = useState(0); @@ -24,46 +24,17 @@ export default function ModelInformation({currentVRM}){ setMeshQty(meshes.length) setSkinnedMeshQty(skinnedMesh.length) - const allMeshes = meshes.concat(skinnedMesh); - let stdMaterialCount = 0; - let stdTranspMaterialCount = 0; - let stdCutoutMaterialCount = 0; - let shaderMaterialCount = 0; - let shaderTranspMaterialCount = 0; - let shaderTCutoutMaterialCount = 0; - allMeshes.forEach(mesh => { - const mats = getAsArray(mesh.material); - mats.forEach(mat => { - if (mat.type == "ShaderMaterial"){ - if (mat.transparent == true) - shaderTranspMaterialCount++ - else if (mat.uniforms.alphaTest.value != 0) - shaderTCutoutMaterialCount++ - else - shaderMaterialCount++; - } - else{ - if (mat.transparent == true) - stdTranspMaterialCount++ - else if (mat.alphaTest != 0) - stdCutoutMaterialCount++ - else - stdMaterialCount++; - - } - // if (mat.type == "MeshStandardMaterial") - // stdMaterialCount++; - }); - }); - setStandardMaterialQty(stdMaterialCount); - setStandardTranspMaterialQty(stdTranspMaterialCount); - setStandardCutoutMaterialQty(stdCutoutMaterialCount); + const {stdMats,stdCutoutpMats,stdTranspMats,mToonMats,mToonCutoutMats,mToonTranspMats} = getMaterialsSortedByArray(allMeshes); + + setStandardMaterialQty(stdMats.length); + setStandardTranspMaterialQty(stdTranspMats.length); + setStandardCutoutMaterialQty(stdCutoutpMats.length); - setVrmMaterialQty(shaderMaterialCount); - setVrmTranspMaterialQty(shaderTranspMaterialCount); - setVrmCutoutMaterialQty(shaderTCutoutMaterialCount); + setVrmMaterialQty(mToonMats.length); + setVrmTranspMaterialQty(mToonTranspMats.length); + setVrmCutoutMaterialQty(mToonCutoutMats.length); } }, [currentVRM]) From 390612a24bb4e9ca79b43df008bb85b9b610b5e0 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Fri, 10 Nov 2023 19:06:02 -0600 Subject: [PATCH 08/28] update name to be mToonAtlasSize --- 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 f808ff9d..eb8bcba7 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -68,7 +68,7 @@ function getOptimizedGLB(avatarToDownload, atlasSize, scale = 1, isVrm0 = false, return combine({ transparentColor: new Color(1,1,1), avatar: avatarToDownloadClone, - atlasSize, + mToonAtlasSize: atlasSize, scale }, isVrm0) } From 1eaf356b640526d5fca9ce0a6489f475d98cfc70 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sat, 11 Nov 2023 01:40:56 -0600 Subject: [PATCH 09/28] correctly detect std material or vrm material --- src/library/VRMExporterv0.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/library/VRMExporterv0.js b/src/library/VRMExporterv0.js index 14a12051..d3d5a3c0 100644 --- a/src/library/VRMExporterv0.js +++ b/src/library/VRMExporterv0.js @@ -165,6 +165,7 @@ export default class VRMExporterv0 { const outputSamplers = toOutputSamplers(outputImages); const outputTextures = toOutputTextures(outputImages); const outputMaterials = toOutputMaterials(uniqueMaterials, images); + console.log("outputmat", outputMaterials); const rootNode = avatar.children.filter((child) => child.children.length > 0 && child.children[0].type === VRMObjectType.Bone)[0]; const nodes = getNodes(rootNode).filter((node) => node.name !== SPRINGBONE_COLLIDER_NAME); @@ -395,7 +396,7 @@ export default class VRMExporterv0 { // upperLegTwist: humanoid.humanDescription.upperLegTwist, // }; - const materialProperties = [{ + const vrmMaterialProperties = { floatProperties : { // _BlendMode : 0, // _BumpScale : 1, @@ -430,7 +431,7 @@ export default class VRMExporterv0 { MTOON_OUTLINE_COLOR_FIXED : true, MTOON_OUTLINE_WIDTH_WORLD : true }, - name : "CombinedMat", + name : "VRMCombinedMat", renderQueue : 2000, shader : "VRM/MToon", tagMap : { @@ -456,8 +457,26 @@ export default class VRMExporterv0 { // _SphereAdd : [0, 0, 1, 1], // _UvAnimMaskTexture : [0, 0, 1, 1] } - }] + } + + const stdMaterialProperties ={ + name : "STDCombinedMat", + shader : "VRM_USE_GLTFSHADER", + } + const materialProperties = [] + uniqueMaterials.forEach(mat => { + if (mat.type == "ShaderMaterial"){ + materialProperties.push( + materialProperties.push(Object.assign({}, vrmMaterialProperties)) + ) + } + else{ + materialProperties.push( + materialProperties.push(Object.assign({}, stdMaterialProperties)) + ) + } + }); //const outputVrmMeta = ToOutputVRMMeta(vrmMeta, icon, outputImages); const outputVrmMeta = vrmMeta; @@ -624,6 +643,8 @@ export default class VRMExporterv0 { const outputScenes = toOutputScenes(avatar, outputNodes); + + const outputData = { accessors: outputAccessors, asset: exporterInfo, @@ -668,7 +689,6 @@ export default class VRMExporterv0 { skins: outputSkins, textures: outputTextures, }; - console.log(outputData) const jsonChunk = new GlbChunk(parseString2Binary(JSON.stringify(outputData, undefined, 2)), "JSON"); const binaryChunk = new GlbChunk(concatBinary(bufferViews.map((buf) => buf.buffer)), "BIN\x00"); const fileData = concatBinary([jsonChunk.buffer, binaryChunk.buffer]); @@ -976,6 +996,7 @@ const toOutputMaterials = (uniqueMaterials, images) => { let VRMC_materials_mtoon = null; material = material.userData.vrmMaterial?material.userData.vrmMaterial:material; + console.log(material); if (material.type === "ShaderMaterial") { VRMC_materials_mtoon = material.userData.gltfExtensions.VRMC_materials_mtoon; VRMC_materials_mtoon.shadeMultiplyTexture = {index:images.map((image) => image.name).indexOf(material.uniforms.shadeMultiplyTexture.name)}; From 829dccbd317ffd6d0f0d5602b78680ec4dc5174d Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 18:21:26 -0600 Subject: [PATCH 10/28] texture atlas per material type --- src/library/create-texture-atlas.js | 89 +++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/src/library/create-texture-atlas.js b/src/library/create-texture-atlas.js index 2921af03..15fb5584 100644 --- a/src/library/create-texture-atlas.js +++ b/src/library/create-texture-atlas.js @@ -1,16 +1,23 @@ import * as THREE from "three"; import { mergeGeometry } from "./merge-geometry.js"; +import { MToonMaterial } from "@pixiv/three-vrm"; let container, cameraRTT, sceneRTT, material, quad, renderer, rtTexture; +function ResetRenderTextureContainer(){ + if (renderer != null) + renderer.clear(true, true); +} -function RenderTextureImageData(texture, multiplyColor, clearColor, width, height) { +function RenderTextureImageData(texture, multiplyColor, clearColor, width, height, isTransparent) { if (texture == null) { const data = new Uint8Array([clearColor.r * 255, clearColor.g * 255, clearColor.b * 255]); // Convert color to Uint8Array texture = new THREE.DataTexture(data, width, height, THREE.RGBFormat); // Create a new texture texture.needsUpdate = true; // Make sure to update the texture } - + + + // if texture is nuill, create a texture only with clearColor (that is color type) if (container == null) { container = document.createElement("div"); @@ -35,7 +42,7 @@ function RenderTextureImageData(texture, multiplyColor, clearColor, width, heigh renderer = new THREE.WebGLRenderer(); renderer.setPixelRatio(1); renderer.setSize(width, height); - renderer.setClearColor(new THREE.Color(1, 1, 1), 1); + //renderer.setClearColor(new THREE.Color(1, 1, 1), 1); renderer.autoClear = false; container.appendChild(renderer.domElement); @@ -57,7 +64,8 @@ function RenderTextureImageData(texture, multiplyColor, clearColor, width, heigh material.map = texture; material.color = multiplyColor.clone(); - renderer.setClearColor(clearColor.clone(), 1); + // set opacoty to 0 if texture is transparent + renderer.setClearColor(clearColor.clone(), isTransparent ? 0 : 1); renderer.setRenderTarget(rtTexture); renderer.clear(); @@ -70,13 +78,16 @@ function RenderTextureImageData(texture, multiplyColor, clearColor, width, heigh return imgData; } -function createContext({ width, height }) { +function createContext({ width, height, transparent }) { const canvas = document.createElement("canvas"); canvas.width = width; canvas.height = height; const context = canvas.getContext("2d"); context.fillStyle = "white"; + if (transparent) + context.globalAlpha = 0; context.fillRect(0, 0, canvas.width, canvas.height); + context.globalAlpha = 1; return context; } function getTextureImage(material, textureName) { @@ -98,19 +109,19 @@ function lerp(t, min, max, newMin, newMax) { return newMin + progress * (newMax - newMin); } -export const createTextureAtlas = async ({ transparentColor, meshes, atlasSize = 4096 }) => { +export const createTextureAtlas = async ({ transparentColor, meshes, atlasSize = 4096, mtoon=true, transparentMaterial=false, transparentTexture = false }) => { // detect whether we are in node or the browser const isNode = typeof window === 'undefined'; // if we are in node, call createTextureAtlasNode if (isNode) { - return await createTextureAtlasNode({ meshes, atlasSize }); + return await createTextureAtlasNode({ meshes, atlasSize, mtoon, transparentMaterial, transparentTexture }); } else { - return await createTextureAtlasBrowser({ backColor: transparentColor, meshes, atlasSize }); + return await createTextureAtlasBrowser({ backColor: transparentColor, meshes, atlasSize, mtoon, transparentMaterial, transparentTexture }); //return await createTextureAtlasBrowser({ meshes, atlasSize }); } }; -export const createTextureAtlasNode = async ({ meshes, atlasSize = 4096 }) => { +export const createTextureAtlasNode = async ({ meshes, atlasSize, mtoon, transparentMaterial, transparentTexture }) => { const ATLAS_SIZE_PX = atlasSize; const IMAGE_NAMES = ["diffuse"]; const bakeObjects = []; @@ -126,7 +137,8 @@ export const createTextureAtlasNode = async ({ meshes, atlasSize = 4096 }) => { bakeObject.mesh.geometry = dest; } }); - const contexts = Object.fromEntries(IMAGE_NAMES.map((name) => [name, createContext({ width: ATLAS_SIZE_PX, height: ATLAS_SIZE_PX })])); + + const contexts = Object.fromEntries(IMAGE_NAMES.map((name) => [name, createContext({ width: ATLAS_SIZE_PX, height: ATLAS_SIZE_PX , transparent: transparentTexture})])); const numTiles = Math.floor(Math.sqrt(meshes.length) + 1); const tileSize = ATLAS_SIZE_PX / numTiles; const originalUVs = new Map(bakeObjects.map((bakeObject, i) => { @@ -224,22 +236,27 @@ export const createTextureAtlasNode = async ({ meshes, atlasSize = 4096 }) => { return { bakeObjects, textures, uvs }; }; -export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize = 4096 }) => { +export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, mtoon, transparentMaterial, transparentTexture }) => { + // make sure to reset texture renderer container + ResetRenderTextureContainer(); + const ATLAS_SIZE_PX = atlasSize; const IMAGE_NAMES = ["diffuse"]; const bakeObjects = []; // save if there is vrm data let vrmMaterial = null; + // save material color from here meshes.forEach((mesh) => { mesh = mesh.clone(); - + const material = mesh.material.length == null ? mesh.material : mesh.material[0]; // use the vrmData of the first material, and call it atlas if it exists - if (vrmMaterial == null) { + if (mtoon && vrmMaterial == null && material.type == "ShaderMaterial") { vrmMaterial = material.clone(); + console.log("vrmmat", vrmMaterial) } // check if bakeObjects objects that contain the material property with value of mesh.material @@ -256,8 +273,9 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize = }); // create the canvas to draw textures + //transparent: (name == "diffuse" && drawTransparent) const contexts = Object.fromEntries( - IMAGE_NAMES.map((name) => [name, createContext({ width: ATLAS_SIZE_PX, height: ATLAS_SIZE_PX })]) + IMAGE_NAMES.map((name) => [name, createContext({ width: ATLAS_SIZE_PX, height: ATLAS_SIZE_PX, transparent:transparentTexture })]) ); const numTiles = Math.floor(Math.sqrt(meshes.length) + 1); @@ -322,9 +340,11 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize = bakeObjects.forEach((bakeObject) => { const { material, mesh } = bakeObject; + console.log(material); const { min, max } = uvs.get(mesh); IMAGE_NAMES.forEach((name) => { const context = contexts[name]; + //context.globalAlpha = transparent ? 0.2 : 1; context.globalCompositeOperation = "source-over"; // set white color base @@ -348,8 +368,8 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize = } // iterate through imageToMaterialMapping[name] and find the first image that is not null let texture = getTexture(material, imageToMaterialMapping[name].find((textureName) => getTextureImage(material, textureName))); - const imgData = RenderTextureImageData(texture, multiplyColor, clearColor, ATLAS_SIZE_PX, ATLAS_SIZE_PX); - createImageBitmap(imgData) + const imgData = RenderTextureImageData(texture, multiplyColor, clearColor, ATLAS_SIZE_PX, ATLAS_SIZE_PX,transparentTexture); + createImageBitmap(imgData)// bmp is trasnaprent .then((bmp) => context.drawImage(bmp, min.x * ATLAS_SIZE_PX, min.y * ATLAS_SIZE_PX, xTileSize, yTileSize)); } ); @@ -367,7 +387,7 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize = } } //geometry.setAttribute( 'uv', uv.array); - //geometry.attributes.uv = + //geometry.attributes.uv = //mesh.geom const uv2 = geometry.attributes.uv2; if (uv2) { @@ -390,9 +410,42 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize = IMAGE_NAMES.map(async (name) => { const texture = new THREE.Texture(contexts[name].canvas) texture.flipY = false; + return [name, texture]; }) ) ); - return { bakeObjects, textures, uvs, vrmMaterial }; + + let material; + console.log("CONTRINUE HERE"); + const materialPostName = transparentMaterial ? "transparent":"opaque" + if (mtoon){ + // xxx set textures and colors + material = new MToonMaterial(); + material.uniforms.map = textures["diffuse"]; + material.uniforms.shadeMultiplyTexture = textures["diffuse"]; + + // is this necessary?, or what should i include from this section? + material.userData.vrmMaterial = material; + + // uniform color is not defined, remove or check why + material.userData.shadeTexture = textures["uniformColor"]; + + material.name = "mToon_" + materialPostName; + } + else{ + // xxx set textures and colors + material = new THREE.MeshStandardMaterial({ + map: textures["diffuse"], + transparent: transparentMaterial + }); + if (transparentTexture){ + material.alphaTest = 0.5; + } + + material.name = "standard_" + materialPostName; + console.log(material.name); + } + // xxxreturn material with textures, dont return uvs nor textures + return { bakeObjects, material }; }; \ No newline at end of file From 38e3fde03c807d1779f67bae31c742ac1f412af9 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 18:22:18 -0600 Subject: [PATCH 11/28] modularize code and update texture atlas calls --- src/library/merge-geometry.js | 320 +++++++++++++++++++--------------- 1 file changed, 177 insertions(+), 143 deletions(-) diff --git a/src/library/merge-geometry.js b/src/library/merge-geometry.js index 15c4a3ff..1e000d0c 100644 --- a/src/library/merge-geometry.js +++ b/src/library/merge-geometry.js @@ -1,7 +1,7 @@ import * as THREE from "three"; import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'; // import { GLTFCubicSplineInterpolant } from "./gltf-cubic-spline-interpolant.js"; -import { findChildrenByType } from "./utils.js"; +import { findChildrenByType, getMeshesSortedByMaterialArray } from "./utils.js"; import { createTextureAtlas } from "./create-texture-atlas.js"; import { BufferAttribute } from "three"; @@ -360,7 +360,7 @@ export async function combineNoAtlas({ avatar, scale = 1 }, isVrm0 = false) { return group; } -function cloneAndStoreParentInfo(mesh){ +function cloneMeshAndSaveSkinInfo(mesh){ const boneName = mesh.parent.name; const originalGlobalPosition = new THREE.Vector3(); const originalGlobalScale = new THREE.Vector3(); @@ -381,179 +381,213 @@ function cloneAndStoreParentInfo(mesh){ return mesh; } -export async function combine({ transparentColor, avatar, atlasSize = 4096, scale = 1 }, isVrm0 = false) { +function createSkinnedMeshFromMesh(baseSkeleton, mesh){ + const skinnedMesh = new THREE.SkinnedMesh(mesh.geometry, mesh.material); + + // Clone the existing skeleton and find the bone by name + const skeleton = baseSkeleton.clone(); + const boneIndex = skeleton.bones.findIndex(bone => bone.name === mesh.userData.boneName); + + // stored original world position as this is a new cloned mesh + const globalPosition = mesh.userData.globalPosition; + const globalScale = mesh.userData.globalScale || new THREE.Vector3(1,1,1); + const globalRotationMatrix = mesh.userData.globalRotationMatrix; + + // Add the bone to the skinned mesh + skinnedMesh.add(skeleton.bones[0]); + + // Create the skin data (with a single bone) + const boneIndices = []; + const weights = []; + + // Assign the bone index (0) and weight (1.0) to each vertex + const vertices = skinnedMesh.geometry.attributes.position.array; + // used to apply rotations + const vertex = new THREE.Vector3(); + + // in vrm0 case, multiply x and z times -1 + const vrm0Mult = mesh.userData.isVRM0 ? -1 : 1; + for (let i = 0; i < vertices.length; i+=3 ) { + // first set rotation + vertex.set(vertices[i], vertices[i + 1], vertices[i + 2]); + vertex.applyMatrix4(globalRotationMatrix); + + vertices[i] = (vrm0Mult * globalScale.x *vertex.x) + globalPosition.x; + vertices[i+1] = (globalScale.y * vertex.y) + globalPosition.y; // no negative vrm0 multiply here + vertices[i+2] = (vrm0Mult * globalScale.z * vertex.z) + globalPosition.z; + boneIndices.push(boneIndex, 0, 0, 0); + weights.push(1.0, 0, 0, 0); + } + + // Set the skin data directly on the SkinnedMesh's geometry + skinnedMesh.geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(boneIndices, 4)); + skinnedMesh.geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(weights, 4)); + + // Bind the skinned mesh to the skeletond + // do not pose! + skinnedMesh.bind(skeleton); + + return skinnedMesh; +} + +export async function combine({ + transparentColor, + avatar, + scale = 1, + mToonAtlasSize = 4096, + mToonAtlasSizeTransp = 4096, + stdAtlasSize = 4096, + stdAtlasSizeTransp = 4096, + exportMtoonAtlas = false, + exportStdAtlas = true }, isVrm0 = false) { + // convert meshes to skinned meshes first const cloneNonSkinnedMeshes = findChildrenByType(avatar, ["Mesh"]); for (let i =0; i < cloneNonSkinnedMeshes.length;i++){ - cloneNonSkinnedMeshes[i] = cloneAndStoreParentInfo(cloneNonSkinnedMeshes[i]); + cloneNonSkinnedMeshes[i] = cloneMeshAndSaveSkinInfo(cloneNonSkinnedMeshes[i]); } const cloneSkinnedMeshes = findChildrenByType(avatar, ["SkinnedMesh"]); const allMeshes = [...cloneNonSkinnedMeshes, ...cloneSkinnedMeshes]; - const { bakeObjects, textures, vrmMaterial } = - await createTextureAtlas({ transparentColor, atlasSize, meshes: allMeshes}); - - const meshes = bakeObjects.map((bakeObject) => bakeObject.mesh); - const material = new THREE.MeshStandardMaterial({ - map: textures["diffuse"], - }); + // make sure to have at least 1 atlas set + if (exportMtoonAtlas == false && exportStdAtlas == false) exportMtoonAtlas = true; - // for Mtoon material - if (vrmMaterial.uniforms != null){ - vrmMaterial.uniforms.map = textures["diffuse"]; - vrmMaterial.uniforms.shadeMultiplyTexture = textures["diffuse"]; + + // to implement + let {stdMesh, stdTranspMesh, mToonMesh, mToonTranspMesh, requiresTransparency} = getMeshesSortedByMaterialArray(allMeshes); + + if (exportMtoonAtlas == false){ + stdMesh = [...stdMesh, ...mToonMesh] + stdTranspMesh = [...stdTranspMesh, ...mToonTranspMesh] + mToonTranspMesh = []; + mToonMesh = []; } - // for Standard Material - else{ - vrmMaterial.map = textures["diffuse"]; + if (exportStdAtlas == false){ + mToonMesh = [...mToonMesh, ...stdMesh] + mToonTranspMesh = [...mToonTranspMesh, ...stdTranspMesh] + stdMesh =[]; + stdTranspMesh = []; } - material.userData.vrmMaterial = vrmMaterial; - material.userData.shadeTexture = textures["uniformColor"]; + const group = new THREE.Object3D(); + group.name = "AvatarRoot"; + group.userData.atlasMaterial = []; - - const newSkeleton = createMergedSkeleton(meshes, scale); - // do not pose! - const skinnedMeshes = []; + const newSkeleton = createMergedSkeleton(allMeshes, scale); - meshes.forEach((mesh) => { + // arrange each mesh by material type + const meshArrayData = { + standard:{meshArray:stdMesh,size:stdAtlasSize, isMtoon:false, transparentMaterial:false }, + standardTransparent:{meshArray:stdTranspMesh,size:stdAtlasSizeTransp, isMtoon:false, transparentMaterial:true }, + mToon:{meshArray: mToonMesh, size: mToonAtlasSize, isMtoon:true, transparentMaterial:false}, + mToonTransparent:{meshArray: mToonTranspMesh, size: mToonAtlasSizeTransp, isMtoon:true, transparentMaterial:true}}; + + for (const prop in meshArrayData){ + const meshData = meshArrayData[prop]; + const arr = meshData.meshArray; + if (arr.length > 0) + { + console.log(arr); + const { bakeObjects, material } = + await createTextureAtlas({ transparentColor, atlasSize:meshData.size, meshes: arr, mtoon:meshData.isMtoon, transparentMaterial:meshData.transparentMaterial, transparentTexture:requiresTransparency}); + const meshes = bakeObjects.map((bakeObject) => bakeObject.mesh); + + const skinnedMeshes = []; - if (mesh.type == "Mesh"){ - const skinnedMesh = new THREE.SkinnedMesh(mesh.geometry, mesh.material); + meshes.forEach((mesh) => { - // Clone the existing skeleton and find the bone by name - const skeleton = newSkeleton.clone(); - const boneIndex = skeleton.bones.findIndex(bone => bone.name === mesh.userData.boneName); + if (mesh.type == "Mesh"){ + mesh = createSkinnedMeshFromMesh(newSkeleton, mesh) + } - // stored original world position as this is a new cloned mesh - const globalPosition = mesh.userData.globalPosition; - const globalScale = mesh.userData.globalScale || new THREE.Vector3(1,1,1); - const globalRotationMatrix = mesh.userData.globalRotationMatrix; - - // Add the bone to the skinned mesh - skinnedMesh.add(skeleton.bones[0]); - - // Create the skin data (with a single bone) - const boneIndices = []; - const weights = []; - - // Assign the bone index (0) and weight (1.0) to each vertex - const vertices = skinnedMesh.geometry.attributes.position.array; - // used to apply rotations - const vertex = new THREE.Vector3(); - - // in vrm0 case, multiply x and z times -1 - const vrm0Mult = mesh.userData.isVRM0 ? -1 : 1; - for (let i = 0; i < vertices.length; i+=3 ) { - // first set rotation - vertex.set(vertices[i], vertices[i + 1], vertices[i + 2]); - vertex.applyMatrix4(globalRotationMatrix); - - vertices[i] = (vrm0Mult * globalScale.x *vertex.x) + globalPosition.x; - vertices[i+1] = (globalScale.y * vertex.y) + globalPosition.y; // no negative vrm0 multiply here - vertices[i+2] = (vrm0Mult * globalScale.z * vertex.z) + globalPosition.z; - boneIndices.push(boneIndex, 0, 0, 0); - weights.push(1.0, 0, 0, 0); - } - - // Set the skin data directly on the SkinnedMesh's geometry - skinnedMesh.geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(boneIndices, 4)); - skinnedMesh.geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(weights, 4)); + skinnedMeshes.push(mesh) + // remove vertices from culled faces from the mesh + const geometry = mesh.geometry; - // Bind the skinned mesh to the skeletond - // do not pose! - skinnedMesh.bind(skeleton); + const baseIndArr = geometry.index.array + const offsetIndexArr = getOrderedNonDupArray(mesh.geometry.index.array); - mesh = skinnedMesh; - } - - skinnedMeshes.push(mesh) - // remove vertices from culled faces from the mesh - const geometry = mesh.geometry; - - const baseIndArr = geometry.index.array - const offsetIndexArr = getOrderedNonDupArray(mesh.geometry.index.array); + const indArrange = [] + for (let i =0 ; i < baseIndArr.length ;i++){ + indArrange[i] = offsetIndexArr.indexOf(baseIndArr[i]) + } + const indexArr = new Uint32Array(indArrange); + const indexAttribute = new BufferAttribute(indexArr,1,false); - const indArrange = [] - for (let i =0 ; i < baseIndArr.length ;i++){ - indArrange[i] = offsetIndexArr.indexOf(baseIndArr[i]) - } - const indexArr = new Uint32Array(indArrange); - const indexAttribute = new BufferAttribute(indexArr,1,false); + // update attributes indices to match new offsetIndexArr + geometry.setIndex(indexAttribute) + for (const att in geometry.attributes){ + geometry.setAttribute(att, removeUnusedAttributes(geometry.getAttribute(att),offsetIndexArr)) + } + + // update morph attributes indices to match new offsetIndexArr + for (const att in geometry.morphAttributes){ + const attribute = geometry.morphAttributes[att]; + for (let i =0; i < attribute.length ;i++){ + attribute[i] = removeUnusedAttributes(attribute[i],offsetIndexArr) + } + } - // update attributes indices to match new offsetIndexArr - geometry.setIndex(indexAttribute) - for (const att in geometry.attributes){ - geometry.setAttribute(att, removeUnusedAttributes(geometry.getAttribute(att),offsetIndexArr)) - } - - // update morph attributes indices to match new offsetIndexArr - for (const att in geometry.morphAttributes){ - const attribute = geometry.morphAttributes[att]; - for (let i =0; i < attribute.length ;i++){ - attribute[i] = removeUnusedAttributes(attribute[i],offsetIndexArr) - } - } + // assign secondary uvs in case they ar not present + if (!geometry.attributes.uv2) { + geometry.attributes.uv2 = geometry.attributes.uv; + } - // assign secondary uvs in case they ar not present - if (!geometry.attributes.uv2) { - geometry.attributes.uv2 = geometry.attributes.uv; - } + // update mesh skeleton indices + if (mesh.skeleton != null) + mesh.geometry.setAttribute("skinIndex", getUpdatedSkinIndex(newSkeleton, mesh)) + + // Exlude the currently "activated" morph attributes before merging. + // The BufferAttributes are not lost; they remain in `mesh.geometry.morphAttributes` + // and the influences remain in `mesh.morphTargetInfluences`. + for (let i = 0; i < 8; i++) { + delete geometry.attributes[`morphTarget${i}`]; + delete geometry.attributes[`morphNormal${i}`]; + } - // update mesh skeleton indices - if (mesh.skeleton != null) - mesh.geometry.setAttribute("skinIndex", getUpdatedSkinIndex(newSkeleton, mesh)) - - // Exlude the currently "activated" morph attributes before merging. - // The BufferAttributes are not lost; they remain in `mesh.geometry.morphAttributes` - // and the influences remain in `mesh.morphTargetInfluences`. - for (let i = 0; i < 8; i++) { - delete geometry.attributes[`morphTarget${i}`]; - delete geometry.attributes[`morphNormal${i}`]; - } + }); + const { dest } = mergeGeometry({ meshes:skinnedMeshes, scale },isVrm0); + const geometry = new THREE.BufferGeometry(); - }); - const { dest } = mergeGeometry({ meshes:skinnedMeshes, scale },isVrm0); - 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 - } - } + // 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 + } + } - geometry.attributes = dest.attributes; - geometry.morphAttributes = dest.morphAttributes; - geometry.morphTargetsRelative = true; - geometry.setIndex(dest.index); + geometry.attributes = dest.attributes; + geometry.morphAttributes = dest.morphAttributes; + geometry.morphTargetsRelative = true; + geometry.setIndex(dest.index); - const vertices = geometry.attributes.position.array; - for (let i = 0; i < vertices.length; i += 3) { - vertices[i] *= scale; - vertices[i + 1] *= scale; - vertices[i + 2] *= scale; - } + const vertices = geometry.attributes.position.array; + for (let i = 0; i < vertices.length; i += 3) { + vertices[i] *= scale; + vertices[i + 1] *= scale; + vertices[i + 2] *= scale; + } - const mesh = new THREE.SkinnedMesh(geometry, material); - mesh.name = "CombinedMesh"; - mesh.morphTargetInfluences = dest.morphTargetInfluences; - mesh.morphTargetDictionary = dest.morphTargetDictionary; + const mesh = new THREE.SkinnedMesh(geometry, material); + mesh.name = "CombinedMesh_" + prop; + mesh.morphTargetInfluences = dest.morphTargetInfluences; + mesh.morphTargetDictionary = dest.morphTargetDictionary; - mesh.bind(newSkeleton); + mesh.bind(newSkeleton); + + //group.animations = dest.animations; + group.add(mesh); + - const group = new THREE.Object3D(); - group.name = "AvatarRoot"; - group.animations = dest.animations; - group.add(mesh); + group.userData.atlasMaterial.push(material); + } + } group.add(newSkeleton.bones[0]); - - - group.userData.atlasMaterial = material; + console.log(group); return group; } From 01f4863c684dd3c0a0944e4627fafaff969cb071 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 18:22:38 -0600 Subject: [PATCH 12/28] functions to separate materials --- src/library/utils.js | 45 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/library/utils.js b/src/library/utils.js index e239f311..6eeb1e1f 100644 --- a/src/library/utils.js +++ b/src/library/utils.js @@ -115,6 +115,43 @@ export const cullHiddenMeshes = (avatar) => { CullHiddenFaces(models) } +export function getMeshesSortedByMaterialArray(meshes){ + const stdMesh = []; + const stdTranspMesh = []; + const mToonMesh = []; + const mToonTranspMesh = []; + let requiresTransparency = false; + + meshes.forEach(mesh => { + const mats = getAsArray(mesh.material); + const mat = mats[0]; + + if (mat.type == "ShaderMaterial"){ + if (mat.transparent == true){ + mToonTranspMesh.push(mesh); + requiresTransparency = true; + } + else{ + mToonMesh.push(mesh); + if (mat.uniforms.alphaTest.value != 0) + requiresTransparency = true + } + } + else{ + if (mat.transparent == true){ + stdTranspMesh.push(mesh); + requiresTransparency = true; + } + else{ + stdMesh.push(mesh); + if (mat.alphaTest != 0) + requiresTransparency = true; + } + } + }); + return {stdMesh, stdTranspMesh, mToonMesh, mToonTranspMesh, requiresTransparency} +} + export function getMaterialsSortedByArray (meshes){ const stdMats = []; const stdCutoutpMats = []; @@ -124,8 +161,8 @@ export function getMaterialsSortedByArray (meshes){ const mToonTranspMats = []; meshes.forEach(mesh => { - const mats = getAsArray(mesh.material); - mats.forEach(mat => { + const mats = getAsArray(mesh.material); + mats.forEach(mat => { if (mat.type == "ShaderMaterial"){ if (mat.transparent == true) mToonTranspMats.push(mat); @@ -599,11 +636,11 @@ export function findChildrenByType(root, types) { predicate: (o) => getAsArray(types).includes(o.type), }); } -export function getAvatarData (avatarModel, modelName, atlasMaterial, vrmMeta){ +export function getAvatarData (avatarModel, modelName, vrmMeta){ const skinnedMeshes = findChildrenByType(avatarModel, "SkinnedMesh") return{ humanBones:getHumanoidByBoneNames(skinnedMeshes[0]), - materials : atlasMaterial ? [avatarModel.userData.atlasMaterial] : avatarModel.userData.atlasMaterial, + materials : avatarModel.userData.atlasMaterial, meta : getVRMMeta(modelName, vrmMeta) } From 409e59c465db5e5acaa9035a7e599a266442fa32 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 18:23:10 -0600 Subject: [PATCH 13/28] remove logs --- src/pages/Optimizer.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/Optimizer.jsx b/src/pages/Optimizer.jsx index 5805cc36..a6165267 100644 --- a/src/pages/Optimizer.jsx +++ b/src/pages/Optimizer.jsx @@ -84,9 +84,6 @@ function Optimizer({ } const prevOption = () => { - console.log(currentOption); - console.log(options.length); - console.log(options) if (currentOption <= 0) setCurrentOption(options.length-1); else From 4b45dbae80fd0b511869d0d2a608f19f84385ff5 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 21:00:02 -0600 Subject: [PATCH 14/28] update downloadvrm call to code refactor --- src/components/ExportMenu.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/ExportMenu.jsx b/src/components/ExportMenu.jsx index 90c051b4..30e556c9 100644 --- a/src/components/ExportMenu.jsx +++ b/src/components/ExportMenu.jsx @@ -46,8 +46,8 @@ export const ExportMenu = ({getFaceScreenshot}) => { className={styles.button} onClick={() => { const screenshot = getFaceScreenshot(); - - downloadVRMWithAvatar(model, avatar, name, screenshot, 4096,templateInfo.exportScale||1, true, templateInfo.vrmMeta,false) + const options = {screenshot:screenshot, atlasSize: 4096, scale:templateInfo.exportScale||1, isVrm0:true, vrmMeta:templateInfo.vrmMeta,createTextureAtlas:false} + downloadVRMWithAvatar(model, avatar, name, options) }} /> { className={styles.button} onClick={() => { const screenshot = getFaceScreenshot(); - console.log(model); - console.log(avatar) - downloadVRMWithAvatar(model, avatar, name, screenshot, 4096,templateInfo.exportScale||1, true, templateInfo.vrmMeta,true) + const options = {screenshot:screenshot, atlasSize: 4096, scale:templateInfo.exportScale||1, isVrm0:true, vrmMeta:templateInfo.vrmMeta,createTextureAtlas:true} + downloadVRMWithAvatar(model, avatar, name, options) }} /> From 3501783b6e8b0993930b5d149b4b967b014bbfe2 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 21:00:35 -0600 Subject: [PATCH 15/28] code refactor: save variables into object options --- src/library/download-utils.js | 135 +++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 53 deletions(-) diff --git a/src/library/download-utils.js b/src/library/download-utils.js index eb8bcba7..bef25d99 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -7,9 +7,9 @@ import VRMExporterv0 from "./VRMExporterv0" import { VRMHumanBoneName } from "@pixiv/three-vrm"; -function cloneAvatarModel (avatarToClone){ +function cloneAvatarModel (model){ - const clone = avatarToClone.clone() + const clone = model.clone() /* NOTE: After avatar clone, the origIndexBuffer/BufferAttribute in userData will lost many infos: From: BufferAttribute {isBufferAttribute: true, name: '', array: Uint32Array(21438), itemSize: 1, count: 21438, …} @@ -18,7 +18,7 @@ function cloneAvatarModel (avatarToClone){ So have to reassign `userData.origIndexBuffer` after avatar clone. */ const origIndexBuffers = [] - avatarToClone.traverse((child) => { + model.traverse((child) => { if (child.userData.origIndexBuffer) origIndexBuffers.push(child.userData.origIndexBuffer) }) @@ -28,13 +28,13 @@ function cloneAvatarModel (avatarToClone){ }) return clone; } -function getUnopotimizedGLB (avatarToDownload){ +function getUnopotimizedGLB (model){ - const avatarToDownloadClone = cloneAvatarModel(avatarToDownload) + const modelClone = cloneAvatarModel(model) let skeleton const skinnedMeshes = [] - avatarToDownloadClone.traverse((child) => { + modelClone.traverse((child) => { if (!skeleton && child.isSkinnedMesh) { skeleton = cloneSkeleton(child) } @@ -61,82 +61,103 @@ function getUnopotimizedGLB (avatarToDownload){ return unoptimizedGLB; } -function getOptimizedGLB(avatarToDownload, atlasSize, scale = 1, isVrm0 = false, createTextureAtlas = true){ - const avatarToDownloadClone = cloneAvatarModel(avatarToDownload) - - if (createTextureAtlas){ - return combine({ - transparentColor: new Color(1,1,1), - avatar: avatarToDownloadClone, - mToonAtlasSize: atlasSize, - scale - }, isVrm0) - } - else{ - console.log("no atlas"); - return combineNoAtlas({ - avatar: avatarToDownloadClone, - scale - }, isVrm0) - } -} -export async function getGLBBlobData(avatarToDownload, atlasSize = 4096, optimized = true, scale = 1){ - const model = await (optimized ? - getOptimizedGLB(avatarToDownload, atlasSize,scale) : - getUnopotimizedGLB(avatarToDownload)) - const glb = await parseGLB(model); + +export async function getGLBBlobData(model, options){ + const {optimized = true} = options; + const finalModel = await (optimized ? + getOptimizedGLB(model, options) : + getUnopotimizedGLB(model)) + const glb = await parseGLB(finalModel); return new Blob([glb], { type: 'model/gltf-binary' }); } -export async function getVRMBlobData(avatarToDownload, avatar, screenshot = null, atlasSize = 4096, scale = 1, isVrm0 = false, vrmMeta= null){ - const model = await getOptimizedGLB(avatarToDownload, atlasSize,scale, isVrm0) - const vrm = await parseVRM(model, avatar, screenshot, isVrm0, vrmMeta); +export async function getVRMBlobData(model, avatar, options){ + const finalModel = await getOptimizedGLB(model, options) + const vrm = await parseVRM(finalModel, avatar, options); // save it as glb now return new Blob([vrm], { type: 'model/gltf-binary' }); } // returns a promise with the parsed data -async function getGLBData(avatarToDownload, atlasSize = 4096, optimized = true, scale = 1){ +async function getGLBData(model, options){ if (optimized){ - const model = await getOptimizedGLB(avatarToDownload, atlasSize,scale) - return parseGLB(model); + const finalModel = await getOptimizedGLB(model, options) + return parseGLB(finalModel); } else{ - const model = getUnopotimizedGLB(avatarToDownload) - return parseGLB(model); + const finalModel = getUnopotimizedGLB(model) + return parseGLB(finalModel); } } -async function getVRMData(avatarToDownload, avatar, screenshot = null, atlasSize = 4096, scale = 1, isVrm0 = false, vrmMeta = null, createTextureAtlas= true){ - const vrmModel = await getOptimizedGLB(avatarToDownload, atlasSize, scale, isVrm0,createTextureAtlas); - return parseVRM(vrmModel,avatar,screenshot, isVrm0, vrmMeta, createTextureAtlas) + + + +//required: model, name, vrmData +//parameters {screenshot, scale, isVrm0, vrmMeta, createTextureAtlas} +export async function downloadVRM(model,vrmData,fileName, options){ + const { + screenshot = null, + //transparentColor = new THREE.Color(1,1,1) , + mToonAtlasSize = 4096, + mToonAtlasSizeTransp = 4096, + stdAtlasSize = 4096, + stdAtlasSizeTransp = 4096, + exportMtoonAtlas = false, + exportStdAtlas = true, + scale = 1, + isVrm0 = false, + vrmMeta = null, + createTextureAtlas = true, + optimized=true + } = options; + + const avatar = {_optimized:{vrm:vrmData}} + downloadVRMWithAvatar(model, avatar, fileName, options) } -export async function downloadVRMWithAvatar(avatarToDownload, avatar, fileName = "", screenshot = null, atlasSize = 4096, scale = 1, isVrm0 = false, vrmMeta = null, createTextureAtlas = true){ +export async function downloadVRMWithAvatar(model, avatar, fileName, options){ const downloadFileName = `${ fileName && fileName !== "" ? fileName : "AvatarCreatorModel" }` - getVRMData(avatarToDownload, avatar, screenshot, atlasSize,scale, isVrm0, vrmMeta, createTextureAtlas).then((vrm)=>{ + getVRMData(model, avatar, options).then((vrm)=>{ saveArrayBuffer(vrm, `${downloadFileName}.vrm`) }) } -export async function downloadVRM(avatarToDownload,vrmData,fileName = "", screenshot = null, atlasSize = 4096, scale = 1, isVrm0 = false, vrmMeta = null, createTextureAtlas = true){ - const avatar = {_optimized:{vrm:vrmData}} - downloadVRMWithAvatar(avatarToDownload, avatar, fileName, screenshot, atlasSize, scale,isVrm0,vrmMeta,createTextureAtlas) +async function getVRMData(model, avatar, options){ + const vrmModel = await getOptimizedGLB(model, options); + return parseVRM(vrmModel,avatar,options) } -export async function downloadGLB(avatarToDownload, optimized = true, fileName = "", atlasSize = 4096){ + + +function getOptimizedGLB(model, options){ + const modelClone = cloneAvatarModel(model) + const { createTextureAtlas } = options; + if (createTextureAtlas){ + return combine(modelClone, options); + } + else{ + console.log("no atlas"); + return combineNoAtlas(modelClone,options) + } +} + + +export async function downloadGLB(model, fileName = "", options){ const downloadFileName = `${ fileName && fileName !== "" ? fileName : "AvatarCreatorModel" }` - const model = optimized ? - await getOptimizedGLB(avatarToDownload, atlasSize, scale): - getUnopotimizedGLB(avatarToDownload) + const {optimized = true} = options; + + const finalModel = optimized ? + await getOptimizedGLB(model, options): + getUnopotimizedGLB(model) - parseGLB(model) + parseGLB(finalModel) .then((result) => { if (result instanceof ArrayBuffer) { saveArrayBuffer(result, `${downloadFileName}.glb`) @@ -170,13 +191,21 @@ function parseGLB (glbModel){ }) } -function parseVRM (glbModel, avatar, screenshot = null, isVrm0 = false, vrmMeta = null, atlasMaterial = false){ +function parseVRM (glbModel, avatar, options){ + const { + screenshot = null, + isVrm0 = false, + vrmMeta = null + } = options + return new Promise((resolve) => { const exporter = isVrm0 ? new VRMExporterv0() : new VRMExporter() + + const vrmData = { ...getVRMBaseData(avatar), - ...getAvatarData(glbModel, "CharacterCreator", atlasMaterial, vrmMeta), + ...getAvatarData(glbModel, "CharacterCreator", vrmMeta), } let skinnedMesh; glbModel.traverse(child => { From 71c56433cf1cfc954ff4be172c4778c5a78c4a13 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 21:00:54 -0600 Subject: [PATCH 16/28] update functions to work with code refactor --- src/library/merge-geometry.js | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/library/merge-geometry.js b/src/library/merge-geometry.js index 1e000d0c..59c0df09 100644 --- a/src/library/merge-geometry.js +++ b/src/library/merge-geometry.js @@ -239,8 +239,10 @@ function mapOldBoneIndexToNew(oldBoneIndex, oldSkeleton, newSkeleton) { } } -export async function combineNoAtlas({ avatar, scale = 1 }, isVrm0 = false) { +export async function combineNoAtlas(avatar, options) { + const { scale, isVrm0 } = options + const clonedMeshes = []; const material = []; @@ -430,16 +432,19 @@ function createSkinnedMeshFromMesh(baseSkeleton, mesh){ return skinnedMesh; } -export async function combine({ - transparentColor, - avatar, - scale = 1, - mToonAtlasSize = 4096, - mToonAtlasSizeTransp = 4096, - stdAtlasSize = 4096, - stdAtlasSizeTransp = 4096, - exportMtoonAtlas = false, - exportStdAtlas = true }, isVrm0 = false) { +export async function combine(avatar, options) { + + const { + transparentColor = new THREE.Color(1,1,1), + mToonAtlasSize = 4096, + mToonAtlasSizeTransp = 4096, + stdAtlasSize = 4096, + stdAtlasSizeTransp = 4096, + exportMtoonAtlas = false, + exportStdAtlas = true, + isVrm0 = false, + scale = 1, + } = options; // convert meshes to skinned meshes first const cloneNonSkinnedMeshes = findChildrenByType(avatar, ["Mesh"]); From 4676aeee1fae9204fa2be96436ccaa595a69fcd2 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 21:01:19 -0600 Subject: [PATCH 17/28] update optimizer to reflect code refactor --- src/pages/Optimizer.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/Optimizer.jsx b/src/pages/Optimizer.jsx index a6165267..a49d86d2 100644 --- a/src/pages/Optimizer.jsx +++ b/src/pages/Optimizer.jsx @@ -43,7 +43,12 @@ function Optimizer({ const download = () => { const vrmData = currentVRM.userData.vrm - downloadVRM(model, vrmData,nameVRM + "_merged",null,getAtlasSize(atlasMtoon),1,true, null, true) + const options = { + atlasSize : 4096, + isVrm0 : true, + createTextureAtlas : true, + } + downloadVRM(model, vrmData,nameVRM + "_merged", options) } useEffect(() => { From f3a15a4424c4facef014100892716d902211b687 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 21:15:44 -0600 Subject: [PATCH 18/28] send atlas size in options parameter --- src/pages/Optimizer.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pages/Optimizer.jsx b/src/pages/Optimizer.jsx index a49d86d2..6cd3e9be 100644 --- a/src/pages/Optimizer.jsx +++ b/src/pages/Optimizer.jsx @@ -47,6 +47,12 @@ function Optimizer({ atlasSize : 4096, isVrm0 : true, createTextureAtlas : true, + mToonAtlasSize:getAtlasSize(atlasMtoon), + mToonAtlasSizeTransp:getAtlasSize(atlasMtoonTransp), + stdAtlasSize:getAtlasSize(atlasStd), + stdAtlasSizeTransp:getAtlasSize(atlasStdTransp), + exportStdAtlas:(currentOption === 0 || currentOption == 2), + exportMtoonAtlas:(currentOption === 1 || currentOption == 2) } downloadVRM(model, vrmData,nameVRM + "_merged", options) } From 458730ba2f6dfaac8dda8c787197cbb293148167 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Sun, 12 Nov 2023 21:16:11 -0600 Subject: [PATCH 19/28] add comment description to downloadVRM call --- src/library/download-utils.js | 38 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/library/download-utils.js b/src/library/download-utils.js index bef25d99..baa68b52 100644 --- a/src/library/download-utils.js +++ b/src/library/download-utils.js @@ -94,24 +94,28 @@ async function getGLBData(model, options){ -//required: model, name, vrmData -//parameters {screenshot, scale, isVrm0, vrmMeta, createTextureAtlas} +/** + * Downloads a VRM model with specified options. + * + * @param {Object} model - The 3D model object. + * @param {Object} vrmData - The VRM data for the model. + * @param {string} fileName - The name of the file to be downloaded. + * @param {Object} options - Additional options for the download. + * @param {Object} options.screenshot - An optional screenshot for the model. + * @param {number} options.mToonAtlasSize - Atlas size for opaque parts when using MToon material. + * @param {number} options.mToonAtlasSizeTransp - Atlas size for transparent parts when using MToon material. + * @param {number} options.stdAtlasSize - Atlas size for opaque parts when using standard materials. + * @param {number} options.stdAtlasSizeTransp - Atlas size for transparent parts when using standard materials. + * @param {boolean} options.exportMtoonAtlas - Whether to export the MToon material atlas. + * @param {boolean} options.exportStdAtlas - Whether to export the standard material atlas. + * @param {number} options.scale - Scaling factor for the model. + * @param {boolean} options.isVrm0 - Whether the VRM version is 0 (true) or 1 (false). + * @param {Object} options.vrmMeta - Additional metadata for the VRM model. + * @param {boolean} options.createTextureAtlas - Whether to create a texture atlas. + * @param {boolean} options.optimized - Whether to optimize the VRM model. + */ export async function downloadVRM(model,vrmData,fileName, options){ - const { - screenshot = null, - //transparentColor = new THREE.Color(1,1,1) , - mToonAtlasSize = 4096, - mToonAtlasSizeTransp = 4096, - stdAtlasSize = 4096, - stdAtlasSizeTransp = 4096, - exportMtoonAtlas = false, - exportStdAtlas = true, - scale = 1, - isVrm0 = false, - vrmMeta = null, - createTextureAtlas = true, - optimized=true - } = options; + const avatar = {_optimized:{vrm:vrmData}} downloadVRMWithAvatar(model, avatar, fileName, options) From 76523c60c32d3dc55fc9a75f7034e83f6acccd91 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 11:01:59 -0600 Subject: [PATCH 20/28] src/library/VRMExporterv0.js --- src/library/VRMExporterv0.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/library/VRMExporterv0.js b/src/library/VRMExporterv0.js index d3d5a3c0..c005b2b7 100644 --- a/src/library/VRMExporterv0.js +++ b/src/library/VRMExporterv0.js @@ -165,7 +165,6 @@ export default class VRMExporterv0 { const outputSamplers = toOutputSamplers(outputImages); const outputTextures = toOutputTextures(outputImages); const outputMaterials = toOutputMaterials(uniqueMaterials, images); - console.log("outputmat", outputMaterials); const rootNode = avatar.children.filter((child) => child.children.length > 0 && child.children[0].type === VRMObjectType.Bone)[0]; const nodes = getNodes(rootNode).filter((node) => node.name !== SPRINGBONE_COLLIDER_NAME); @@ -996,11 +995,10 @@ const toOutputMaterials = (uniqueMaterials, images) => { let VRMC_materials_mtoon = null; material = material.userData.vrmMaterial?material.userData.vrmMaterial:material; - console.log(material); if (material.type === "ShaderMaterial") { - VRMC_materials_mtoon = material.userData.gltfExtensions.VRMC_materials_mtoon; + //VRMC_materials_mtoon = material.userData.gltfExtensions.VRMC_materials_mtoon; + VRMC_materials_mtoon = {}; VRMC_materials_mtoon.shadeMultiplyTexture = {index:images.map((image) => image.name).indexOf(material.uniforms.shadeMultiplyTexture.name)}; - const mtoonMaterial = material; baseColor = mtoonMaterial.color ? [ 1, From 90dc90328ea7d974e208555eccd69c81e790acbe Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 11:03:06 -0600 Subject: [PATCH 21/28] save vrm material to material userdata --- src/library/create-texture-atlas.js | 31 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/library/create-texture-atlas.js b/src/library/create-texture-atlas.js index 15fb5584..3f178f32 100644 --- a/src/library/create-texture-atlas.js +++ b/src/library/create-texture-atlas.js @@ -410,28 +410,43 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, IMAGE_NAMES.map(async (name) => { const texture = new THREE.Texture(contexts[name].canvas) texture.flipY = false; - + //const matName = (mtoon ? "mtoon_" : "standard") + (transparentMaterial ? "transp_":"opaque_"); + //texture.name = matName + name; return [name, texture]; }) ) ); let material; - console.log("CONTRINUE HERE"); const materialPostName = transparentMaterial ? "transparent":"opaque" if (mtoon){ // xxx set textures and colors - material = new MToonMaterial(); - material.uniforms.map = textures["diffuse"]; - material.uniforms.shadeMultiplyTexture = textures["diffuse"]; + // save material as standard material + material = new THREE.MeshStandardMaterial({ + map: textures["diffuse"], + transparent: transparentMaterial + }); + if (transparentTexture){ + material.alphaTest = 0.5; + } + + // but also store a vrm material that will hold the extension information + if (vrmMaterial == null){ + vrmMaterial = new MToonMaterial(); + } + + vrmMaterial.uniforms.map = textures["diffuse"]; + vrmMaterial.uniforms.shadeMultiplyTexture = textures["diffuse"]; + vrmMaterial.alphaTest = 0.5; - // is this necessary?, or what should i include from this section? - material.userData.vrmMaterial = material; + + material.userData.vrmMaterial = vrmMaterial; // uniform color is not defined, remove or check why material.userData.shadeTexture = textures["uniformColor"]; - material.name = "mToon_" + materialPostName; + console.log("Temporal hack, we need to assign with texture name, not material name") + material.map.name = material.name; } else{ // xxx set textures and colors From cd5ce0557863e92ced0f78a4985ee1779ab8220e Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 13:06:06 -0600 Subject: [PATCH 22/28] add support for metallic roughness texture --- src/library/VRMExporterv0.js | 85 +++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/src/library/VRMExporterv0.js b/src/library/VRMExporterv0.js index c005b2b7..58e6fa45 100644 --- a/src/library/VRMExporterv0.js +++ b/src/library/VRMExporterv0.js @@ -150,17 +150,22 @@ export default class VRMExporterv0 { if (!material.map) throw new Error(material.name + " map is null"); return { name: material.name, imageBitmap: material.map.image }; - }); // TODO: 画像がないMaterialもある + }); const shadeImages = uniqueMaterials .filter((material) => material.userData.shadeTexture) .map((material) => { if (!material.userData.shadeTexture) throw new Error(material.userData.shadeTexture + " map is null"); return { name: material.name + "_shade", imageBitmap: material.userData.shadeTexture.image }; - }); // TODO: 画像がないMaterialもある\ - - const images = mainImages.concat(shadeImages); - + }); + const ormImages = uniqueMaterials + .filter((material) => material.roughnessMap) + .map((material) => { + if (!material.roughnessMap) + throw new Error(material.roughnessMap + " roughnessMap is null"); + return { name: material.name + "_orm", imageBitmap: material.roughnessMap.image }; + }); + const images = [...mainImages, ...shadeImages, ...ormImages]; const outputImages = toOutputImages(images, icon); const outputSamplers = toOutputSamplers(outputImages); const outputTextures = toOutputTextures(outputImages); @@ -1026,6 +1031,11 @@ const toOutputMaterials = (uniqueMaterials, images) => { } } + let metalicRoughnessIndex = -1; + if (material.roughnessMap) + metalicRoughnessIndex = images.map((image) => image.name).indexOf(material.name + "_orm"); + + const baseTexture = baseTxrIndex >= 0 ? { extensions: { KHR_texture_transform: { @@ -1037,26 +1047,46 @@ const toOutputMaterials = (uniqueMaterials, images) => { texCoord: 0, // TODO: } : undefined; - const metallicFactor = (() => { - switch (material.type) { - case MaterialType.MeshStandardMaterial: - return material.metalness; - case MaterialType.MeshBasicMaterial: - return 0; - default: - return 0; - } - })(); - const roughnessFactor = (() => { - switch (material.type) { - case MaterialType.MeshStandardMaterial: - return material.roughness; - case MaterialType.MeshBasicMaterial: - return 0.9; - default: - return 0.9; + + const pbrMetallicRoughness = { + baseColorFactor: baseColor, + baseColorTexture: baseTexture, } - })(); + + const metalRoughTexture = metalicRoughnessIndex >= 0 ?{ + index: metalicRoughnessIndex, + texCoord: 0, // TODO: + }:undefined + + if (metalRoughTexture){ + pbrMetallicRoughness.metallicRoughnessTexture = metalRoughTexture; + } + else{ + const metallicFactor = (() => { + switch (material.type) { + case MaterialType.MeshStandardMaterial: + return material.metalness; + case MaterialType.MeshBasicMaterial: + return 0; + default: + return 0; + } + })(); + const roughnessFactor = (() => { + switch (material.type) { + case MaterialType.MeshStandardMaterial: + return material.roughness; + case MaterialType.MeshBasicMaterial: + return 0.9; + default: + return 0.9; + } + })(); + + pbrMetallicRoughness.metallicFactor = metallicFactor; + pbrMetallicRoughness.roughnessFactor = roughnessFactor; + } + return { alphaCutoff: material.alphaTest > 0 ? material.alphaTest : undefined, alphaMode: material.transparent ? @@ -1068,12 +1098,7 @@ const toOutputMaterials = (uniqueMaterials, images) => { VRMC_materials_mtoon } : undefined, name: material.name, - pbrMetallicRoughness: { - baseColorFactor: baseColor, - baseColorTexture: baseTexture, - metallicFactor: metallicFactor, - roughnessFactor: roughnessFactor, - }, + pbrMetallicRoughness }; }); }; From 1e4df6ca5eddc67ffd1843180acec1f45caa82b9 Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 13:06:32 -0600 Subject: [PATCH 23/28] update texture atlas generation to include metallic roughness --- src/library/create-texture-atlas.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/library/create-texture-atlas.js b/src/library/create-texture-atlas.js index 3f178f32..a7d18f8b 100644 --- a/src/library/create-texture-atlas.js +++ b/src/library/create-texture-atlas.js @@ -91,6 +91,7 @@ function createContext({ width, height, transparent }) { return context; } function getTextureImage(material, textureName) { + // material can come in arrays or single values, in case of ccoming in array take the first one material = material.length == null ? material : material[0]; return material[textureName] && material[textureName].image; @@ -241,7 +242,7 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, ResetRenderTextureContainer(); const ATLAS_SIZE_PX = atlasSize; - const IMAGE_NAMES = ["diffuse"]; + const IMAGE_NAMES = ["diffuse", "orm"]; const bakeObjects = []; // save if there is vrm data let vrmMaterial = null; @@ -256,7 +257,6 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, // use the vrmData of the first material, and call it atlas if it exists if (mtoon && vrmMaterial == null && material.type == "ShaderMaterial") { vrmMaterial = material.clone(); - console.log("vrmmat", vrmMaterial) } // check if bakeObjects objects that contain the material property with value of mesh.material @@ -340,7 +340,6 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, bakeObjects.forEach((bakeObject) => { const { material, mesh } = bakeObject; - console.log(material); const { min, max } = uvs.get(mesh); IMAGE_NAMES.forEach((name) => { const context = contexts[name]; @@ -445,21 +444,22 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, // uniform color is not defined, remove or check why material.userData.shadeTexture = textures["uniformColor"]; material.name = "mToon_" + materialPostName; - console.log("Temporal hack, we need to assign with texture name, not material name") material.map.name = material.name; } else{ // xxx set textures and colors material = new THREE.MeshStandardMaterial({ map: textures["diffuse"], + roughnessMap: textures["orm"], + metalnessMap: textures["orm"], transparent: transparentMaterial }); if (transparentTexture){ material.alphaTest = 0.5; } - material.name = "standard_" + materialPostName; - console.log(material.name); + + material.roughnessMap.name = material.name + "_orm"; } // xxxreturn material with textures, dont return uvs nor textures return { bakeObjects, material }; From 550a3ff61711e6765015558abacf97ed73759faa Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 13:08:17 -0600 Subject: [PATCH 24/28] remove logs --- src/library/merge-geometry.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/library/merge-geometry.js b/src/library/merge-geometry.js index 59c0df09..4d0c7872 100644 --- a/src/library/merge-geometry.js +++ b/src/library/merge-geometry.js @@ -494,7 +494,6 @@ export async function combine(avatar, options) { const arr = meshData.meshArray; if (arr.length > 0) { - console.log(arr); const { bakeObjects, material } = await createTextureAtlas({ transparentColor, atlasSize:meshData.size, meshes: arr, mtoon:meshData.isMtoon, transparentMaterial:meshData.transparentMaterial, transparentTexture:requiresTransparency}); const meshes = bakeObjects.map((bakeObject) => bakeObject.mesh); @@ -592,7 +591,6 @@ export async function combine(avatar, options) { } } group.add(newSkeleton.bones[0]); - console.log(group); return group; } From ae1c5eeb3c67d2a821f1c5fec2c6c7312ea9fe5c Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 14:40:35 -0600 Subject: [PATCH 25/28] filter null images --- src/library/VRMExporterv0.js | 52 +++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/library/VRMExporterv0.js b/src/library/VRMExporterv0.js index 58e6fa45..a3816f1d 100644 --- a/src/library/VRMExporterv0.js +++ b/src/library/VRMExporterv0.js @@ -162,10 +162,18 @@ export default class VRMExporterv0 { .filter((material) => material.roughnessMap) .map((material) => { if (!material.roughnessMap) - throw new Error(material.roughnessMap + " roughnessMap is null"); + return null; return { name: material.name + "_orm", imageBitmap: material.roughnessMap.image }; }); - const images = [...mainImages, ...shadeImages, ...ormImages]; + + const normalImages = uniqueMaterials + .filter((material) => material.roughnessMap) + .map((material) => { + if (!material.normalMap) + return null + return { name: material.name + "_normal", imageBitmap: material.normalMap.image }; + }); + const images = [...mainImages, ...shadeImages, ...ormImages,...normalImages].filter(element => element !== null); const outputImages = toOutputImages(images, icon); const outputSamplers = toOutputSamplers(outputImages); const outputTextures = toOutputTextures(outputImages); @@ -1035,6 +1043,9 @@ const toOutputMaterials = (uniqueMaterials, images) => { if (material.roughnessMap) metalicRoughnessIndex = images.map((image) => image.name).indexOf(material.name + "_orm"); + let normalTextureIndex = -1; + if (material.normalMap) + normalTextureIndex = images.map((image) => image.name).indexOf(material.name + "_normal"); const baseTexture = baseTxrIndex >= 0 ? { extensions: { @@ -1058,6 +1069,11 @@ const toOutputMaterials = (uniqueMaterials, images) => { texCoord: 0, // TODO: }:undefined + const normalMapTexture = normalTextureIndex >= 0 ? { + index: normalTextureIndex, + texCoord: 0, + }:undefined; + if (metalRoughTexture){ pbrMetallicRoughness.metallicRoughnessTexture = metalRoughTexture; } @@ -1086,20 +1102,24 @@ const toOutputMaterials = (uniqueMaterials, images) => { pbrMetallicRoughness.metallicFactor = metallicFactor; pbrMetallicRoughness.roughnessFactor = roughnessFactor; } - - return { - alphaCutoff: material.alphaTest > 0 ? material.alphaTest : undefined, - alphaMode: material.transparent ? - "BLEND" : material.alphaTest > 0 ? - "MASK" : "OPAQUE", - doubleSided: material.side === 2, - extensions: material.type === "ShaderMaterial" ? { - KHR_materials_unlit: {}, // TODO: - VRMC_materials_mtoon - } : undefined, - name: material.name, - pbrMetallicRoughness - }; + + const parseMaterial = { + alphaCutoff: material.alphaTest > 0 ? material.alphaTest : undefined, + alphaMode: material.transparent ? + "BLEND" : material.alphaTest > 0 ? + "MASK" : "OPAQUE", + doubleSided: material.side === 2, + extensions: material.type === "ShaderMaterial" ? { + KHR_materials_unlit: {}, // TODO: + VRMC_materials_mtoon + } : undefined, + name: material.name, + pbrMetallicRoughness + } + if (normalMapTexture){ + parseMaterial.normalTexture = normalMapTexture; + } + return parseMaterial; }); }; const toOutputImages = (images, icon) => { From 4fc93b76434087ebb770824a250117c0c97797ae Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 14:41:17 -0600 Subject: [PATCH 26/28] remove normal map from atlas for now --- src/library/create-texture-atlas.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/library/create-texture-atlas.js b/src/library/create-texture-atlas.js index a7d18f8b..f55f3856 100644 --- a/src/library/create-texture-atlas.js +++ b/src/library/create-texture-atlas.js @@ -242,7 +242,7 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, ResetRenderTextureContainer(); const ATLAS_SIZE_PX = atlasSize; - const IMAGE_NAMES = ["diffuse", "orm"]; + const IMAGE_NAMES = mtoon ? ["diffuse"] : ["diffuse", "orm"];// not using normal texture for now const bakeObjects = []; // save if there is vrm data let vrmMaterial = null; @@ -452,14 +452,19 @@ export const createTextureAtlasBrowser = async ({ backColor, meshes, atlasSize, map: textures["diffuse"], roughnessMap: textures["orm"], metalnessMap: textures["orm"], + normalMap: textures["normal"], transparent: transparentMaterial }); + if (transparentTexture){ material.alphaTest = 0.5; } material.name = "standard_" + materialPostName; - material.roughnessMap.name = material.name + "_orm"; + if (material.roughnessMap != null) + material.roughnessMap.name = material.name + "_orm"; + if (material.normalMap != null) + material.normalMap.name = material.name + "_normal"; } // xxxreturn material with textures, dont return uvs nor textures return { bakeObjects, material }; From 3cc624cc5296d0671013cab3d70a999297aeed1d Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 14:44:48 -0600 Subject: [PATCH 27/28] remove download button when no vrm is uploadded --- src/pages/Optimizer.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/Optimizer.jsx b/src/pages/Optimizer.jsx index 6cd3e9be..a6000bd6 100644 --- a/src/pages/Optimizer.jsx +++ b/src/pages/Optimizer.jsx @@ -296,13 +296,14 @@ function Optimizer({ className={styles.buttonCenter} onClick={debugMode} /> */} + {(currentVRM)&&( + />)}
) From 80316e8074e5e2d7a5facaf70e42466dd168d19f Mon Sep 17 00:00:00 2001 From: memelotsqui Date: Mon, 13 Nov 2023 15:01:20 -0600 Subject: [PATCH 28/28] save selection to local storage --- src/pages/Optimizer.jsx | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/pages/Optimizer.jsx b/src/pages/Optimizer.jsx index a6000bd6..e6568629 100644 --- a/src/pages/Optimizer.jsx +++ b/src/pages/Optimizer.jsx @@ -13,6 +13,7 @@ import { downloadVRM } from "../library/download-utils" import ModelInformation from "../components/ModelInformation" import MenuTitle from "../components/MenuTitle" import Slider from "../components/Slider" +import { local } from "../library/store" function Optimizer({ animationManager, @@ -25,12 +26,12 @@ function Optimizer({ const [currentVRM, setCurrentVRM] = useState(null); const [lastVRM, setLastVRM] = useState(null); const [nameVRM, setNameVRM] = useState(""); - const [atlasStd, setAtlasStd] = useState(6); - const [atlasStdTransp, setAtlasStdTransp] = useState(6); - const [atlasMtoon, setAtlasMtoon] = useState(6); - const [atlasMtoonTransp, setAtlasMtoonTransp] = useState(6); + const [atlasStd, setAtlasStd] = useState(local["optimizer_atlas_std_size"] || 6); + const [atlasStdTransp, setAtlasStdTransp] = useState(local["optimizer_atlas_std_transp_size"] || 6); + const [atlasMtoon, setAtlasMtoon] = useState(local["optimizer_atlas_mtoon_size"] || 6); + const [atlasMtoonTransp, setAtlasMtoonTransp] = useState(local["optimizer_atlas_mtoon_transp_size"] || 6); const [downloadOnDrop, setDownloadOnDrop] = useState(false) - const [currentOption, setCurrentOption] = useState(0); + const [currentOption, setCurrentOption] = useState(local["optimizer_sel_option"] || 0); const [options] = useState(["Merge to Standard", "Merge to MToon", "Keep Both"]) const { playSound } = React.useContext(SoundContext) @@ -95,19 +96,25 @@ function Optimizer({ } const prevOption = () => { + let cur = currentOption; if (currentOption <= 0) - setCurrentOption(options.length-1); + cur = options.length-1 else - setCurrentOption(currentOption - 1) + cur -= 1 + + setCurrentOption(cur); + local["optimizer_sel_option"] = cur; } const nextOption = () => { - - if (currentOption >= options.length-1) - setCurrentOption(0); + let cur = currentOption; + if (currentOption >= options.length - 1) + cur = 0; else - setCurrentOption(currentOption + 1); - + cur +=1; + + setCurrentOption(cur); + local["optimizer_sel_option"] = cur; } const getAtlasSize = (value) =>{ @@ -145,15 +152,19 @@ function Optimizer({ case 'standard opaque': // save to user prefs setAtlasStd(size); + local["optimizer_atlas_std_size"] = size; break; case 'standard transparent': setAtlasStdTransp(size); + local["optimizer_atlas_std_transp_size"] = size; break; case 'mtoon opaque': setAtlasMtoon(size); + local["optimizer_atlas_mtoon_size"] = size; break; case 'mtoon transparent': setAtlasMtoonTransp(size); + local["optimizer_atlas_mtoon_transp_size"] = size; break; } }