diff options
Diffstat (limited to 'tools/portraits/render.html')
| -rw-r--r-- | tools/portraits/render.html | 85 |
1 files changed, 85 insertions, 0 deletions
diff --git a/tools/portraits/render.html b/tools/portraits/render.html new file mode 100644 index 0000000..8e0a26e --- /dev/null +++ b/tools/portraits/render.html @@ -0,0 +1,85 @@ +<!doctype html> +<meta charset="utf-8"> +<title>render</title> +<body style="margin:0"> +<script src="/vendor/aframe-1.7.1.min.js"></script> +<script> +// Renders a transparent head-and-torso portrait of each avatar, arms relaxed, +// three-quarter view, and POSTs each PNG back to the helper server. +const SLUGS = (new URLSearchParams(location.search).get('slugs') || + 'oldmoustache,bigbro,lilbro,teddy,froggy,astronaut,polybot,wizzir').split(','); +const SIZE = 384; + +const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true }); +renderer.setPixelRatio(1); +renderer.setSize(SIZE, SIZE); +renderer.outputColorSpace = THREE.SRGBColorSpace; +renderer.setClearColor(0x000000, 0); +document.body.appendChild(renderer.domElement); + +const loader = new THREE.GLTFLoader(); +const load = (url) => new Promise((res, rej) => loader.load(url, res, undefined, rej)); + +function relaxArms(bone) { + const shoulder = new THREE.Vector3(), wrist = new THREE.Vector3(), q = new THREE.Quaternion(), pq = new THREE.Quaternion(); + for (const [side, sign] of [['left', -1], ['right', 1]]) { + const upper = bone(`${side}UpperArm`), hand = bone(`${side}Hand`); + if (!upper || !hand) continue; + upper.getWorldPosition(shoulder); + hand.getWorldPosition(wrist); + const from = wrist.sub(shoulder).normalize(); + const to = new THREE.Vector3(sign * 0.28, -1, -0.12).normalize(); + q.setFromUnitVectors(from, to); + const world = upper.getWorldQuaternion(new THREE.Quaternion()).premultiply(q); + upper.parent.getWorldQuaternion(pq).invert(); + upper.quaternion.copy(pq).multiply(world); + upper.updateMatrixWorld(true); + } +} + +async function renderOne(slug) { + const gltf = await load(`/assets/avatars/${slug}.vrm`); + const vrm = gltf.parser.json.extensions.VRM; + const nodes = new Map(); + gltf.scene.traverse((o) => { + const a = gltf.parser.associations.get(o); + if (a && a.nodes !== undefined) nodes.set(a.nodes, o); + if (o.isMesh) o.frustumCulled = false; + }); + const bone = (name) => { + const hb = vrm.humanoid.humanBones.find((b) => b.bone === name); + return hb ? nodes.get(hb.node) : null; + }; + + const scene = new THREE.Scene(); + scene.add(gltf.scene); + scene.add(new THREE.HemisphereLight(0xffffff, 0x444444, 2)); + gltf.scene.updateMatrixWorld(true); + relaxArms(bone); + + const top = new THREE.Box3().setFromObject(gltf.scene).max.y; + const hips = bone('hips').getWorldPosition(new THREE.Vector3()).y; + const bottom = hips - (top - hips) * 0.05; + const centerY = (top + bottom) / 2; + const height = (top - bottom) * 1.2; + + const camera = new THREE.PerspectiveCamera(28, 1, 0.01, 100); + const dist = (height / 2) / Math.tan(THREE.MathUtils.degToRad(14)); + const yaw = THREE.MathUtils.degToRad(22); + camera.position.set(Math.sin(yaw) * dist, centerY + height * 0.04, -Math.cos(yaw) * dist); + camera.lookAt(0, centerY, 0); + + renderer.render(scene, camera); + const blob = await new Promise((res) => renderer.domElement.toBlob(res, 'image/png')); + await fetch(`/save/${slug}`, { method: 'POST', body: blob }); +} + +(async () => { + const results = {}; + for (const slug of SLUGS) { + try { await renderOne(slug); results[slug] = 'ok'; } catch (e) { results[slug] = String(e); } + } + window.results = results; + document.title = 'done'; +})(); +</script> |