1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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>
|