initial migration, not vetted
This commit is contained in:
parent
41a32b8f68
commit
09bc6a5304
29 changed files with 122147 additions and 0 deletions
153
web/batch_glb_png/render.html
Normal file
153
web/batch_glb_png/render.html
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Headless GLB Renderer</title>
|
||||
<style>
|
||||
html, body { margin:0; padding:0; background:#fff; overflow:hidden; }
|
||||
canvas { display:block; }
|
||||
</style>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "./libs/three.module.js",
|
||||
"three/addons/": "./vendor/three/addons/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
|
||||
import * as THREE from 'three';
|
||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||
|
||||
function qs (k) { return new URLSearchParams(location.search).get(k); }
|
||||
|
||||
const glbUrl = qs('glb');
|
||||
const profileUrl = qs('profile');
|
||||
|
||||
window.__RENDER_STATUS__ = { ready:false, error:null, pngDataUrl:null };
|
||||
|
||||
if (!glbUrl || !profileUrl) {
|
||||
window.__RENDER_STATUS__.error = "Missing ?glb=... or ?profile=...";
|
||||
throw new Error(window.__RENDER_STATUS__.error);
|
||||
}
|
||||
|
||||
const profile = await (await fetch(profileUrl)).json();
|
||||
|
||||
// Renderer
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
preserveDrawingBuffer: true,
|
||||
alpha: false
|
||||
});
|
||||
renderer.setPixelRatio(profile.output?.pixelRatio ?? 1);
|
||||
renderer.setSize(profile.output?.width ?? 1024, profile.output?.height ?? 768, false);
|
||||
|
||||
// Three r152+ color management (safe-guard)
|
||||
if (THREE.SRGBColorSpace) renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||
|
||||
const bg = profile.scene?.background ?? 0xffffff;
|
||||
renderer.setClearColor(bg, 1);
|
||||
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
// Scene
|
||||
const scene = new THREE.Scene();
|
||||
|
||||
// Camera
|
||||
const cam = new THREE.PerspectiveCamera(
|
||||
profile.camera?.fov ?? 50,
|
||||
(profile.output?.width ?? 1024) / (profile.output?.height ?? 768),
|
||||
profile.camera?.near ?? 0.1,
|
||||
profile.camera?.far ?? 1000
|
||||
);
|
||||
cam.position.fromArray(profile.camera?.position ?? [0,0,10]);
|
||||
cam.up.fromArray(profile.camera?.up ?? [0,1,0]);
|
||||
|
||||
// We won’t use OrbitControls in headless; we just mimic its target by looking at it.
|
||||
const target = new THREE.Vector3().fromArray(profile.controls?.target ?? [0,0,0]);
|
||||
cam.lookAt(target);
|
||||
cam.updateProjectionMatrix();
|
||||
|
||||
// Lights
|
||||
const dir = new THREE.DirectionalLight(0xffffff, profile.lights?.directional?.intensity ?? 2.0);
|
||||
dir.position.fromArray(profile.lights?.directional?.position ?? [5,5,5]);
|
||||
scene.add(dir);
|
||||
|
||||
const amb = new THREE.AmbientLight(0xffffff, profile.lights?.ambient?.intensity ?? 0.5);
|
||||
scene.add(amb);
|
||||
|
||||
// Load GLB
|
||||
const loader = new GLTFLoader();
|
||||
const gltf = await loader.loadAsync(glbUrl);
|
||||
const root = gltf.scene;
|
||||
scene.add(root);
|
||||
|
||||
// --- Fit-to-frame while keeping the profile's viewing direction ---
|
||||
const box = new THREE.Box3().setFromObject(root);
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
|
||||
// Use profile target if present, but still re-center model so target makes sense
|
||||
const profTarget = new THREE.Vector3().fromArray(profile.controls?.target ?? [0, 0, 0]);
|
||||
|
||||
// Shift the model so its center sits at the profile target (usually [0,0,0] or your saved target)
|
||||
root.position.sub(center).add(profTarget);
|
||||
|
||||
// Recompute bounds after shifting
|
||||
const box2 = new THREE.Box3().setFromObject(root);
|
||||
const size2 = box2.getSize(new THREE.Vector3());
|
||||
const center2 = box2.getCenter(new THREE.Vector3());
|
||||
|
||||
// Keep the same view direction as the profile camera->target vector
|
||||
const profCamPos = new THREE.Vector3().fromArray(profile.camera?.position ?? [0, 0, 10]);
|
||||
const viewDir = profCamPos.clone().sub(profTarget).normalize();
|
||||
|
||||
// Compute distance so the whole object fits the camera FOV
|
||||
|
||||
const maxDim = Math.max(size2.x, size2.y, size2.z, 1e-6);
|
||||
const fov = cam.fov * (Math.PI / 180);
|
||||
const fitPadding = profile.camera?.fitPadding ?? 1.15; // you can add this to JSON later
|
||||
const distance = (maxDim / 2) / Math.tan(fov / 2) * fitPadding;
|
||||
|
||||
// Position camera and aim
|
||||
cam.position.copy(center2).add(viewDir.multiplyScalar(distance));
|
||||
cam.lookAt(center2);
|
||||
cam.updateProjectionMatrix();
|
||||
|
||||
// Adjust near/far safely
|
||||
cam.near = Math.max(0.01, distance / 100);
|
||||
cam.far = distance * 100;
|
||||
cam.updateProjectionMatrix();
|
||||
|
||||
// Apply renderParams
|
||||
const wireframe = !!profile.renderParams?.wireframe;
|
||||
root.traverse((obj) => {
|
||||
if (obj.isMesh && obj.material) {
|
||||
// If multi-material
|
||||
if (Array.isArray(obj.material)) {
|
||||
obj.material.forEach(m => { m.wireframe = wireframe; m.needsUpdate = true; });
|
||||
} else {
|
||||
obj.material.wireframe = wireframe;
|
||||
obj.material.needsUpdate = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Optional: edges overlay if you later add it (kept off unless you implement lines).
|
||||
// profile.renderParams.edgeAngle is available; current lab.html uses it to build edges.
|
||||
|
||||
renderer.render(scene, cam);
|
||||
|
||||
// Give the GPU a beat (helps on some headless setups)
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
|
||||
window.__RENDER_STATUS__.pngDataUrl = renderer.domElement.toDataURL('image/png');
|
||||
window.__RENDER_STATUS__.ready = true;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue