Spaces:
Sleeping
Sleeping
| import * as THREE from 'three'; | |
| import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; | |
| import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js' | |
| import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |
| import { VRMLoaderPlugin } from './three-vrm.module.js'; | |
| import { loadMixamoAnimation } from './loadMixamoAnimation.js'; | |
| // renderer | |
| let renderer = null; | |
| import JSZip from 'jszip'; | |
| import { OneMinusDstAlphaFactor } from 'three'; | |
| import { forEach } from 'jszip'; | |
| function initializeRenderer() { | |
| renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true}); | |
| renderer.setClearColor(0x000000, 0.0); | |
| // renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setSize(768, 768); | |
| renderer.setPixelRatio(1); | |
| } | |
| // camera | |
| const camera = new THREE.PerspectiveCamera(40, 1, 0.1, 20.0); | |
| const exporter = new GLTFExporter(); | |
| //camera.position.set(4.0, 0.0, 0.0); | |
| // camera controls | |
| let controls = null; | |
| let azimuth = Math.PI, elevation = 0, distance = 1.5; | |
| function updateControls(pos = {x: 0, y: 0, z: 0}) { | |
| controls.screenSpacePanning = true; | |
| controls.target.set(pos.x, pos.y, pos.z); | |
| controls.enableRotate = true; | |
| camera.position.set(distance * Math.cos(elevation) * Math.cos(azimuth), | |
| distance * Math.sin(elevation), | |
| distance * Math.cos(elevation) * Math.sin(azimuth)); | |
| controls.update(); | |
| } | |
| function initializeControls() { | |
| controls = new OrbitControls(camera, renderer.domElement); | |
| updateControls(); | |
| } | |
| // scene | |
| let scene = null; | |
| const ambientLight = new THREE.AmbientLight(0x404040, 1.0); // soft white light | |
| const light = new THREE.DirectionalLight(0xffffff); | |
| function initializeScene() { | |
| scene = new THREE.Scene(); | |
| scene.add(ambientLight); | |
| light.position.set(1.0, 1.0, 1.0).normalize(); | |
| scene.add(light); | |
| } | |
| // gltf and vrm | |
| let currentVrm = undefined; | |
| const loader = new GLTFLoader(); | |
| loader.crossOrigin = 'anonymous'; | |
| loader.register((parser) => { | |
| return new VRMLoaderPlugin(parser); | |
| }); | |
| // let vrmPaths = []; | |
| // fetch("./vroid.json").then( | |
| // (response) => { | |
| // if (!response.ok) { | |
| // throw new Error("Fetch request failed"); | |
| // } | |
| // return response.json(); | |
| // }).then((data) => { | |
| // console.log(data); | |
| // vrmPaths = data; | |
| // processNextVrm(); | |
| // }); | |
| let vrmPaths = []; | |
| const queryParams = new URLSearchParams(window.location.search); | |
| const jsonFileName = queryParams.get('file') || 'default.json'; | |
| fetch(`./${jsonFileName}`).then( | |
| (response) => { | |
| if (!response.ok) { | |
| throw new Error("Fetch request failed"); | |
| } | |
| return response.json(); | |
| }).then((data) => { | |
| console.log(data); | |
| vrmPaths = data; | |
| console.log("initializeRenderer"); | |
| processNextVrm(); | |
| }); | |
| let base_euler = null, euler_array = null, node_arr = null; | |
| let is_apose = false; | |
| let pose_euler = null, bone_arr = null; | |
| function changePose() { | |
| bone_arr = ["leftUpperArm", "rightUpperArm", | |
| "leftLowerArm", "rightLowerArm", | |
| "leftHand", "rightHand", | |
| "leftShoulder", "rightShoulder", | |
| "leftUpperLeg", "rightUpperLeg", | |
| "leftLowerLeg", "rightLowerLeg", | |
| "leftFoot", "rightFoot", "head", | |
| "leftIndexProximal", "rightIndexProximal", "leftIndexDistal", "rightIndexDistal", | |
| "leftIndexIntermediate", "rightIndexIntermediate", "leftToes", "rightToes", | |
| "upperChest", "neck", | |
| "hips", "spine"]; | |
| if (is_apose) { | |
| for (var i = 0; i < bone_arr.length; ++i) { | |
| if (i < 6) | |
| currentVrm.humanoid.getNormalizedBoneNode(bone_arr[i])?.rotation.copy(euler_array[i]); | |
| else | |
| currentVrm.humanoid.getNormalizedBoneNode(bone_arr[i])?.rotation.copy(new THREE.Euler(0, 0, 0, 'XYZ')); | |
| } | |
| } else { | |
| for(var i = 0; i < pose_euler.length; ++i) { | |
| if (bone_arr.includes(pose_euler[i].name)) | |
| currentVrm.humanoid.getNormalizedBoneNode(pose_euler[i].name)?.rotation.copy(pose_euler[i].euler); | |
| } | |
| } | |
| currentVrm.update(0); | |
| node_arr = {}; | |
| for (var i = 0; i < bone_arr.length; ++i) { | |
| var cur_node = currentVrm.humanoid.getNormalizedBoneNode(bone_arr[i]); | |
| // console.log(bone_arr[i]); | |
| if (cur_node != null) { | |
| node_arr[bone_arr[i]] = { | |
| world_position: cur_node.getWorldPosition(new THREE.Vector3()), | |
| position: cur_node.position, | |
| rotation: cur_node.rotation, | |
| quaternion: cur_node.quaternion | |
| } | |
| // console.log(cur_node); | |
| } | |
| // console.log(node_arr[bone_arr[i]]); | |
| } | |
| // exit(); | |
| } | |
| function aPose() { | |
| is_apose = true; | |
| let temp_arr = Array(6).fill(null).map(() => new THREE.Euler()); | |
| temp_arr[0] = new THREE.Euler(0, 0, Math.PI / 4, 'XYZ'); | |
| temp_arr[1] = new THREE.Euler(0, 0, -Math.PI / 4, 'XYZ'); | |
| temp_arr[2] = new THREE.Euler(0, 0, 0, 'XYZ'); | |
| temp_arr[3] = new THREE.Euler(0, 0, 0, 'XYZ'); | |
| temp_arr[4] = new THREE.Euler(0, 0, -Math.PI / 30, 'XYZ'); | |
| temp_arr[5] = new THREE.Euler(0, 0, Math.PI / 30, 'XYZ'); | |
| return temp_arr; | |
| } | |
| function randPose() { | |
| is_apose = false; | |
| let temp_arr = Array(6).fill(null).map(() => new THREE.Euler()); | |
| for (var i = 0; i < 6; ++i) { | |
| // randomize | |
| if (i < 4) | |
| temp_arr[i] = new THREE.Euler( | |
| (((Math.random() > 0.8) ^ (i & 1) ? -1 : 1)) * Math.random() * Math.PI / 180 * 30, | |
| (((Math.random() > 0.5) ^ (i & 1) ? -1 : 1)) * Math.random() * Math.PI / 180 * 30, | |
| (((Math.random() > 0.5) ^ (i & 1) ? -1 : 1)) * Math.random() * Math.PI / 180 * 50, | |
| 'XYZ' | |
| ) | |
| else | |
| temp_arr[i] = new THREE.Euler( | |
| ((Math.random() > 0.8) ^ (i & 1) ? -1 : 1) * Math.random() * Math.PI / 180 * 10, | |
| ((Math.random() > 0.5) ^ (i & 1) ? -1 : 1) * Math.random() * Math.PI / 180 * 10, | |
| ((Math.random() > 0.5) ^ (i & 1) ? -1 : 1) * Math.random() * Math.PI / 180 * 20, | |
| 'XYZ' | |
| ) | |
| } | |
| return temp_arr; | |
| } | |
| function normalizeVrm() { | |
| const box = new THREE.Box3().setFromObject(currentVrm.scene); | |
| const size = box.getSize(new THREE.Vector3()); | |
| const maxDimension = Math.max(size.x, size.y, size.z); | |
| const scale = 1 / maxDimension; | |
| currentVrm.scene.scale.set(scale, scale, scale); | |
| const center = box.getCenter(new THREE.Vector3()); | |
| currentVrm.scene.position.sub(center.multiplyScalar(scale)); | |
| currentVrm.update(0); | |
| } | |
| function loadVRM(path) { | |
| loader.load( | |
| path, | |
| (gltf) => { | |
| const vrm = gltf.userData.vrm; | |
| scene.add(vrm.scene); | |
| currentVrm = vrm; | |
| // print vrm | |
| // console.log(vrm); | |
| normalizeVrm(); | |
| //var fbx_id = 11; | |
| var fbx_id = Math.floor(Math.random() * 24); | |
| loadFBX("animation/test" + fbx_id + ".fbx"); | |
| base_euler = randPose(); | |
| loader.manager.onLoad = () => { | |
| animate(); | |
| }; | |
| }, | |
| (progress) => { }, | |
| //console.log('Loading model...', 100.0 * (progress.loaded / progress.total), '%'), | |
| (error) => console.error(error), | |
| ); | |
| } | |
| let currentIndex = 0; | |
| let Vrmname = ""; | |
| function loadFBX( animationUrl ) { | |
| loadMixamoAnimation( animationUrl, currentVrm ).then( ( result ) => { | |
| pose_euler = result; | |
| }) | |
| } | |
| function processNextVrm() { | |
| try { | |
| initializeRenderer(); | |
| console.log("initializeControls"); | |
| initializeControls(); | |
| console.log("initializeScene"); | |
| initializeScene(); | |
| if (currentIndex < vrmPaths.length) { | |
| Vrmname = vrmPaths[currentIndex].split("/")[2]; | |
| loadVRM(vrmPaths[currentIndex]); | |
| currentIndex++; | |
| } else { | |
| console.log('All VRMs processed.'); | |
| return; | |
| } | |
| } | |
| catch (e) { | |
| console.log(e); | |
| processNextVrm(); | |
| } | |
| } | |
| let cache_data = new JSZip(); | |
| function releaseCache() { | |
| console.log("release cache"); | |
| var formData = new FormData(); | |
| cache_data.forEach(function (path, file) { | |
| if (!file.dir) { | |
| cache_data.file(path).async('blob').then(function (blob) { | |
| formData.append('files', blob, path); | |
| }); | |
| } | |
| }); | |
| cache_data.generateAsync({ type: "blob" }) | |
| .then(function (content) { | |
| return fetch('http://localhost:17070/upload/', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| }); | |
| console.log("cache released!"); | |
| cache_data = new JSZip(); | |
| } | |
| function uploadCache(data, filename) { | |
| cache_data.file(filename, data); | |
| } | |
| function saveScreenshot(id, type) { | |
| var screenshotDataUrl = renderer.domElement.toDataURL("image/png"); | |
| // Convert DataURL to Blob | |
| fetch(screenshotDataUrl) | |
| .then(res => res.blob()) | |
| .then(blob => { | |
| let updateName = Vrmname + "_" + id.toString().padStart(3, '0'); | |
| uploadCache(blob, updateName + "_" + type + ".png"); | |
| var json = JSON.stringify({ | |
| name: updateName, | |
| elevation: elevation, | |
| azimuth: azimuth, | |
| distance: distance, | |
| extrinsicMatrix: camera.matrixWorld, | |
| intrinsicMatrix: camera.projectionMatrix, | |
| node_array: node_arr | |
| }); | |
| var json_blob = new Blob([json], { type: 'application/json' }); | |
| uploadCache(json_blob, updateName + ".json"); | |
| }) | |
| .catch((error) => { | |
| console.error('Error:', error); | |
| }); | |
| } | |
| var releaseRender = function (renderer, scene) { | |
| let clearScene = function (scene) { | |
| let arr = scene.children.filter(x => x); | |
| arr.forEach(item => { | |
| if (item.children.length) { clearScene(item); } | |
| else { if (item.type === 'Mesh') { item.geometry.dispose(); item.material.dispose(); !!item.clear && item.clear(); } } | |
| }); | |
| !!scene.clear && scene.clear(renderer); arr = null; | |
| } | |
| try { clearScene(scene); } catch (e) { } | |
| try { | |
| renderer.renderLists.dispose(); | |
| renderer.dispose(); renderer.forceContextLoss(); | |
| renderer.domElement = null; renderer.content = null; renderer = null; | |
| } catch (e) { } | |
| if (!!window.requestAnimationId) { cancelAnimationFrame(window.requestAnimationId); } THREE.Cache.clear(); | |
| } | |
| function removeCurrentVRM() { | |
| releaseRender(renderer, scene); | |
| } | |
| let frame = 0, param_aa, param_blinkl, param_blinkr; | |
| let start_azim; | |
| function animate() { | |
| // requestAnimationFrame(animate); | |
| if (frame == 0) { | |
| param_aa = Math.random(); | |
| param_blinkl = Math.random(); | |
| param_blinkr = Math.random(); | |
| } | |
| if (frame % 2 == 0) { | |
| if (frame < 8) { | |
| elevation = 0; | |
| distance = 1.5; | |
| azimuth = Math.PI / 2 * (frame / 2); | |
| } else if (frame < 32) { | |
| if (frame % 8 == 0) { | |
| elevation = (Math.random() - 0.5) * Math.PI / 6; | |
| start_azim = Math.PI / 2 * (Math.random()); | |
| } | |
| distance = 1.5; | |
| azimuth = Math.PI / 2 * ((frame - 8) / 2) + start_azim; | |
| } else { | |
| elevation = 0 + (Math.random() - 0.5) * Math.PI / 4; | |
| distance = 1.5 + (Math.random() - 0.5); | |
| azimuth = Math.random() * Math.PI * 2; | |
| } | |
| updateControls(); | |
| euler_array = aPose(); | |
| currentVrm.expressionManager.setValue('aa', 0); | |
| currentVrm.expressionManager.setValue('blinkLeft', 0); | |
| currentVrm.expressionManager.setValue('blinkRight', 0); | |
| } else { | |
| if (frame >= 32) { | |
| elevation = 0 + (Math.random() - 0.5) * Math.PI / 4; | |
| distance = 1.5 + (Math.random() - 0.5); | |
| azimuth = Math.random() * Math.PI * 2; | |
| var jitter = 0.2; | |
| updateControls({ x: (Math.random() - 0.5) * jitter, y: (Math.random() - 0.5) * jitter, z: (Math.random() - 0.5) * jitter }); | |
| } | |
| currentVrm.expressionManager.setValue('aa', param_aa); | |
| currentVrm.expressionManager.setValue('blinkLeft', param_blinkl); | |
| currentVrm.expressionManager.setValue('blinkRight', param_blinkr); | |
| is_apose = false; | |
| } | |
| changePose(); | |
| //currentMixer.update(0); | |
| //normalizeVrm(); | |
| function setMToonDebugMode(material, mode) { | |
| if ( material.isMToonMaterial ) { | |
| material.debugMode = mode; | |
| } | |
| } | |
| //const debugMode = ['none', 'normal', 'litShadeRate', 'uv'][debugModeIndex]; | |
| if (frame < 60) { | |
| currentVrm.scene.traverse( ( object ) => { | |
| if ( object.material ) { | |
| if ( Array.isArray( object.material ) ) { | |
| object.material.forEach( ( material ) => setMToonDebugMode( material, 'normal') ); | |
| } else { | |
| setMToonDebugMode( object.material, 'normal'); | |
| } | |
| } | |
| } ); | |
| renderer.render(scene, camera); | |
| saveScreenshot(frame, "normal"); | |
| currentVrm.scene.traverse( ( object ) => { | |
| if ( object.material ) { | |
| if ( Array.isArray( object.material ) ) { | |
| object.material.forEach( ( material ) => setMToonDebugMode( material, 'none') ); | |
| } else { | |
| setMToonDebugMode( object.material, 'none'); | |
| } | |
| } | |
| } ); | |
| renderer.render(scene, camera); | |
| saveScreenshot(frame, "rgb"); | |
| currentVrm.scene.traverse( ( object ) => { | |
| if ( object.material ) { | |
| if ( Array.isArray( object.material ) ) { | |
| object.material.forEach( ( material ) => setMToonDebugMode( material, 'depth') ); | |
| } else { | |
| setMToonDebugMode( object.material, 'depth'); | |
| } | |
| } | |
| } ); | |
| renderer.render(scene, camera); | |
| renderer.render(scene, camera); | |
| saveScreenshot(frame, "depth"); | |
| frame++; | |
| (async function () { | |
| await new Promise(resolve => setTimeout(() => { | |
| requestAnimationFrame(animate); | |
| resolve(); | |
| }, 100)); | |
| })(); | |
| } else { | |
| frame = 0; | |
| removeCurrentVRM(); | |
| releaseCache(); | |
| (async function () { | |
| await new Promise(resolve => setTimeout(() => { | |
| processNextVrm(); | |
| resolve(); | |
| }, 2000)); | |
| })(); | |
| } | |
| } |