Spaces:
Running
on
A10G
Running
on
A10G
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)); | |
})(); | |
} | |
} |