← back to Ventura Bus 3d
add /models-preview.html: orbit-camera grid of all 8 sourced GLBs
6a8f84b95dac4ed7b0c340d7beb1edd6272a055f · 2026-05-10 09:01:19 -0700 · Steve Abrams
Files touched
A public/models-preview.html
Diff
commit 6a8f84b95dac4ed7b0c340d7beb1edd6272a055f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 10 09:01:19 2026 -0700
add /models-preview.html: orbit-camera grid of all 8 sourced GLBs
---
public/models-preview.html | 124 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/public/models-preview.html b/public/models-preview.html
new file mode 100644
index 0000000..91438ef
--- /dev/null
+++ b/public/models-preview.html
@@ -0,0 +1,124 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<title>Ventura Bus 3D — Model Preview</title>
+<style>
+ :root { --bg:#0b0d12; --fg:#e6eaf2; --muted:#8a93a6; --card:#141821; --accent:#ff8c00; }
+ *{box-sizing:border-box} html,body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.4 -apple-system,system-ui,sans-serif}
+ header{padding:18px 22px;border-bottom:1px solid #20242f;display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
+ h1{margin:0;font-size:18px;letter-spacing:.02em}
+ header .meta{color:var(--muted);font-size:12px}
+ header a{color:var(--accent);text-decoration:none}
+ .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:14px;padding:18px}
+ .card{background:var(--card);border:1px solid #20242f;border-radius:8px;overflow:hidden;display:flex;flex-direction:column}
+ .stage{aspect-ratio:1/1;background:linear-gradient(180deg,#161a24 0%,#0d1018 100%);position:relative}
+ .stage canvas{display:block;width:100%;height:100%}
+ .meta-row{padding:10px 12px;border-top:1px solid #20242f;display:flex;justify-content:space-between;align-items:baseline}
+ .name{font-weight:600}
+ .role{color:var(--muted);font-size:12px}
+ .size{font-variant-numeric:tabular-nums;color:var(--muted);font-size:12px}
+ .credit{padding:6px 12px 12px;font-size:11px;color:var(--muted)}
+ .credit a{color:#7eb1ff;text-decoration:none}
+ .controls{position:absolute;bottom:6px;right:8px;font-size:10px;color:var(--muted);user-select:none}
+</style>
+</head>
+<body>
+<header>
+ <h1>Ventura Bus 3D — sourced vehicle models</h1>
+ <div class="meta">8 glTF binary models · all CC-BY 3.0 · drag to orbit · scroll to zoom</div>
+ <div class="meta">↩ <a href="/">main scene</a> · <a href="/models/ATTRIBUTION.md">ATTRIBUTION.md</a></div>
+</header>
+<div class="grid" id="grid"></div>
+
+<script type="importmap">
+{
+ "imports": {
+ "three": "https://unpkg.com/three@0.160.0/build/three.module.js",
+ "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
+ }
+}
+</script>
+<script type="module">
+import * as THREE from 'three';
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+
+const MODELS = [
+ { file:'bus-metro-articulated.glb', name:'bus-metro-articulated', role:'Metro orange articulated (stand-in)', credit:'Bus by Poly by Google', url:'https://poly.pizza/m/9Rj7a89ypPQ' },
+ { file:'bus-ladot-dash.glb', name:'bus-ladot-dash', role:'LADOT DASH (stand-in)', credit:'Montreal Bus by Nick Ladd', url:'https://poly.pizza/m/fFCCghvRImG' },
+ { file:'bus-school.glb', name:'bus-school', role:'School bus', credit:'Schoolbus by Poly by Google', url:'https://poly.pizza/m/8xacyNqdJ3t' },
+ { file:'car-sedan.glb', name:'car-sedan', role:'Sedan', credit:'Car by Quaternius', url:'https://poly.pizza/m/Cz6yDaUcM9' },
+ { file:'car-suv.glb', name:'car-suv', role:'SUV', credit:'SUV by Quaternius', url:'https://poly.pizza/m/xsMtZhBkxL' },
+ { file:'car-sports.glb', name:'car-sports', role:'Sports car', credit:'Sports Car by Quaternius', url:'https://poly.pizza/m/1mkmFkAz5v' },
+ { file:'car-pickup.glb', name:'car-pickup', role:'Pickup truck', credit:'Pickup Truck by Quaternius', url:'https://poly.pizza/m/qn4grQgHm8' },
+ { file:'car-delivery-van.glb', name:'car-delivery-van', role:'Delivery van', credit:'Van by Poly by Google', url:'https://poly.pizza/m/aT_24cDaW1a' },
+];
+
+const grid = document.getElementById('grid');
+const loader = new GLTFLoader();
+const fmtKB = b => `${(b/1024).toFixed(0)} KB`;
+
+for (const m of MODELS) {
+ const card = document.createElement('div'); card.className = 'card';
+ const stage = document.createElement('div'); stage.className = 'stage';
+ const ctrl = document.createElement('div'); ctrl.className = 'controls'; ctrl.textContent = 'orbit · zoom';
+ stage.appendChild(ctrl);
+ const meta = document.createElement('div'); meta.className = 'meta-row';
+ meta.innerHTML = `<div><div class="name">${m.name}</div><div class="role">${m.role}</div></div><div class="size" data-size>—</div>`;
+ const credit = document.createElement('div'); credit.className = 'credit';
+ credit.innerHTML = `${m.credit} · <a href="${m.url}" target="_blank" rel="noopener">source</a> · <a href="https://creativecommons.org/licenses/by/3.0/" target="_blank" rel="noopener">CC-BY 3.0</a>`;
+ card.append(stage, meta, credit);
+ grid.appendChild(card);
+
+ // Per-card Three.js scene
+ const scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x10141d);
+ const camera = new THREE.PerspectiveCamera(40, 1, 0.05, 200);
+ camera.position.set(6, 4.5, 7);
+ const renderer = new THREE.WebGLRenderer({ antialias: true });
+ renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
+ const sizeObs = new ResizeObserver(() => {
+ const r = stage.getBoundingClientRect();
+ renderer.setSize(r.width, r.height, false);
+ camera.aspect = r.width / r.height; camera.updateProjectionMatrix();
+ });
+ sizeObs.observe(stage);
+ stage.appendChild(renderer.domElement);
+
+ scene.add(new THREE.HemisphereLight(0xffffff, 0x223044, 0.9));
+ const sun = new THREE.DirectionalLight(0xffe7b3, 1.2); sun.position.set(5, 8, 4); scene.add(sun);
+ const grid2 = new THREE.GridHelper(20, 20, 0x2a2f3d, 0x1a1d27); grid2.position.y = -0.001; scene.add(grid2);
+
+ const controls = new OrbitControls(camera, renderer.domElement);
+ controls.enableDamping = true; controls.target.set(0, 0.6, 0);
+
+ // Track byte size via fetch HEAD
+ fetch(`/models/${m.file}`, { method: 'HEAD' }).then(r => {
+ const len = r.headers.get('content-length');
+ if (len) meta.querySelector('[data-size]').textContent = fmtKB(Number(len));
+ });
+
+ loader.load(`/models/${m.file}`, (gltf) => {
+ const root = gltf.scene;
+ const box = new THREE.Box3().setFromObject(root);
+ const size = new THREE.Vector3(); box.getSize(size);
+ const center = new THREE.Vector3(); box.getCenter(center);
+ const maxDim = Math.max(size.x, size.y, size.z) || 1;
+ const scale = 4 / maxDim;
+ root.scale.setScalar(scale);
+ root.position.sub(center.multiplyScalar(scale));
+ root.position.y += (size.y * scale) / 2; // sit on grid
+ scene.add(root);
+ }, undefined, (err) => {
+ stage.style.background = '#3a1a1a';
+ ctrl.textContent = 'load error: ' + err.message;
+ });
+
+ let raf = 0;
+ function tick(){ controls.update(); renderer.render(scene, camera); raf = requestAnimationFrame(tick); }
+ tick();
+}
+</script>
+</body>
+</html>
← bcac288 add 8 CC-BY vehicle GLBs (3 buses + 5 cars) + ATTRIBUTION.md
·
back to Ventura Bus 3d
·
task #5: traffic.js — fixed-timestep bus + ambient car sim w 9e03e9b →