RealWonder / static /force_viewer.js
Wei Liu
Migrate to Gradio SDK with ZeroGPU + H200 support
6f37a6b
// RealWonder β€” Three.js 3D force direction viewer
// Exposes window.rwInitForceViewer() and window.rwUpdateArrow(fx, fy, fz)
(function () {
"use strict";
var renderer3d = null;
var scene3d = null;
var camera3d = null;
var forceArrow3d = null;
var viewer3dReady = false;
window.rwInitForceViewer = function () {
if (viewer3dReady) return;
if (typeof THREE === "undefined") {
console.warn("Three.js not loaded β€” 3D viewer disabled.");
return;
}
var viewerCanvas = document.getElementById("forceViewer3d");
if (!viewerCanvas) {
console.warn("forceViewer3d canvas not found β€” will retry via MutationObserver.");
return;
}
renderer3d = new THREE.WebGLRenderer({ canvas: viewerCanvas, antialias: true, alpha: true });
renderer3d.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer3d.setSize(160, 160);
scene3d = new THREE.Scene();
camera3d = new THREE.PerspectiveCamera(42, 1.0, 0.1, 50);
camera3d.position.set(1.6, 1.2, 1.9);
camera3d.lookAt(0, 0, 0);
// Origin sphere
var originMesh = new THREE.Mesh(
new THREE.SphereGeometry(0.07, 16, 16),
new THREE.MeshBasicMaterial({ color: 0x6b8aed })
);
scene3d.add(originMesh);
// Reference axis arrows
// World X (left-right) β†’ Three.js +X (red)
// World Z (up-down) β†’ Three.js +Y (green)
// World Y (in-out) β†’ Three.js -Z (blue)
var axLen = 0.65, hLen = 0.14, hW = 0.07;
scene3d.add(new THREE.ArrowHelper(new THREE.Vector3(1, 0, 0), new THREE.Vector3(), axLen, 0xcc3333, hLen, hW));
scene3d.add(new THREE.ArrowHelper(new THREE.Vector3(0, 1, 0), new THREE.Vector3(), axLen, 0x33994a, hLen, hW));
scene3d.add(new THREE.ArrowHelper(new THREE.Vector3(0, 0, -1), new THREE.Vector3(), axLen, 0x3355cc, hLen, hW));
// Force arrow (vivid orange, initially hidden)
forceArrow3d = new THREE.ArrowHelper(
new THREE.Vector3(1, 0, 0), new THREE.Vector3(), 0.5, 0xe06010, 0.22, 0.10
);
forceArrow3d.visible = false;
scene3d.add(forceArrow3d);
scene3d.add(new THREE.AmbientLight(0xffffff, 1.0));
viewer3dReady = true;
render3D();
console.log("RealWonder force viewer initialized.");
};
// forceX = world X (left/right), forceY = world Y (out/in), forceZ = world Z (down/up)
window.rwUpdateArrow = function (forceX, forceY, forceZ) {
if (!viewer3dReady || !forceArrow3d) return;
// Map world → Three.js: X→X, Z→Y(up), Y→-Z(into screen)
var tx = forceX, ty = forceZ, tz = -forceY;
var len = Math.sqrt(tx * tx + ty * ty + tz * tz);
if (len < 0.01) {
forceArrow3d.visible = false;
} else {
forceArrow3d.visible = true;
forceArrow3d.setDirection(new THREE.Vector3(tx / len, ty / len, tz / len));
// Scale arrow length 0.25–0.85 with normalized force magnitude (max strength β‰ˆ 2)
forceArrow3d.setLength(0.25 + 0.60 * Math.min(len / 2.0, 1.0), 0.20, 0.09);
}
render3D();
};
function render3D() {
if (renderer3d && scene3d && camera3d) {
renderer3d.render(scene3d, camera3d);
}
}
// Auto-init when #forceViewer3d appears in DOM (Gradio renders dynamically)
var _observer = new MutationObserver(function () {
if (!viewer3dReady && typeof THREE !== "undefined" && document.getElementById("forceViewer3d")) {
window.rwInitForceViewer();
}
});
_observer.observe(document.documentElement, { childList: true, subtree: true });
// Also attempt immediate init if DOM is already ready
if (document.readyState !== "loading") {
if (typeof THREE !== "undefined" && document.getElementById("forceViewer3d")) {
window.rwInitForceViewer();
}
}
})();