js/kitthumbs.js renders each kit GLB to a 128x96 sprite once (offscreen renderer, framed by bounding sphere), POSTs to /api/kit-thumb, and all 52 are committed under assets/kit_thumbs/ so every checkout gets instant thumbs; the generator only runs for props without one (future kit drops). KIT drawer shows them with category groups and filter. js/colliders.js derives world-space colliders from kit manifest hints + a table for builtin props (bins, planters, trunks, fences...). Test ride resolves circle-vs-box/circle push-out — you scrub and stop instead of riding through furniture. Exports gain an additive COLLIDERS array for bookquoy to adopt when its player is ready; existing contract untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
59 lines
2.4 KiB
JavaScript
59 lines
2.4 KiB
JavaScript
// SKATEMAKER PRO — kit thumbnails. Each GLB renders to a 128x96 sprite ONCE, is
|
|
// POSTed to serve.py, saved under assets/kit_thumbs/ and committed — so every
|
|
// machine gets instant thumbs and the offscreen renderer only ever runs for
|
|
// props that don't have one yet (new kit drops).
|
|
import * as THREE from 'three';
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
import { KIT_BASE } from './parkbuild.js';
|
|
|
|
const W = 128, H = 96;
|
|
let ctx = null; // lazy shared renderer
|
|
|
|
function getCtx() {
|
|
if (ctx) return ctx;
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true,
|
|
preserveDrawingBuffer: true });
|
|
renderer.setSize(W, H);
|
|
const scene = new THREE.Scene();
|
|
scene.add(new THREE.HemisphereLight(0xffffff, 0x445544, 1.2));
|
|
const sun = new THREE.DirectionalLight(0xfff3d9, 1.6);
|
|
sun.position.set(3, 6, 4); scene.add(sun);
|
|
const cam = new THREE.PerspectiveCamera(38, W / H, 0.05, 100);
|
|
ctx = { renderer, scene, cam, loader: new GLTFLoader() };
|
|
return ctx;
|
|
}
|
|
|
|
async function renderOne(def) {
|
|
const { renderer, scene, cam, loader } = getCtx();
|
|
const glb = await new Promise((res, rej) =>
|
|
loader.load(KIT_BASE + 'props/' + def.id + '.glb', res, undefined, rej));
|
|
const obj = glb.scene;
|
|
scene.add(obj);
|
|
const box = new THREE.Box3().setFromObject(obj);
|
|
const c = box.getCenter(new THREE.Vector3()), s = box.getSize(new THREE.Vector3());
|
|
const rad = Math.max(s.x, s.y, s.z) * 0.62 / Math.tan(cam.fov * Math.PI / 360);
|
|
cam.position.set(c.x + rad * 0.72, c.y + rad * 0.5, c.z + rad * 0.72);
|
|
cam.lookAt(c);
|
|
renderer.render(scene, cam);
|
|
scene.remove(obj);
|
|
const blob = await new Promise(res => renderer.domElement.toBlob(res, 'image/png'));
|
|
await fetch('/api/kit-thumb?name=' + def.id + '.png', { method: 'POST', body: blob });
|
|
return true;
|
|
}
|
|
|
|
// ensure every kit prop has a thumb; returns ids rendered this pass
|
|
export async function ensureKitThumbs(kitList, onProgress) {
|
|
const missing = [];
|
|
await Promise.all(kitList.map(async d => {
|
|
const r = await fetch('assets/kit_thumbs/' + d.id + '.png', { method: 'HEAD' });
|
|
if (!r.ok) missing.push(d);
|
|
}));
|
|
const done = [];
|
|
for (const def of missing) {
|
|
try { await renderOne(def); done.push(def.id); onProgress?.(done.length, missing.length); }
|
|
catch (e) { console.warn('thumb failed', def.id, e); }
|
|
}
|
|
if (ctx && done.length) { ctx.renderer.dispose(); ctx = null; }
|
|
return done;
|
|
}
|