added a step to obj conversion and a viewer.html based on three.js
This commit is contained in:
178
viewer.html
Normal file
178
viewer.html
Normal file
@@ -0,0 +1,178 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>OBJ Viewer (three.js modules)</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
html, body { margin: 0; height: 100%; overflow: hidden; background: #111; }
|
||||
#hud {
|
||||
position: fixed; left: 12px; top: 12px;
|
||||
padding: 10px 12px; border-radius: 10px;
|
||||
background: rgba(0,0,0,0.75); color: #fff;
|
||||
font: 13px/1.4 system-ui, sans-serif;
|
||||
max-width: 460px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
#hud b { display:block; margin-bottom: 6px; }
|
||||
code { background: rgba(255,255,255,0.08); padding: 1px 4px; border-radius: 6px; }
|
||||
</style>
|
||||
|
||||
<!-- Import map: map "three" and "three/addons/" to CDN module files -->
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
|
||||
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="hud"><b>OBJ Viewer</b>Waiting...</div>
|
||||
|
||||
<script type="module">
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
|
||||
import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
|
||||
|
||||
const OBJ_URL = "./output.obj";
|
||||
const MTL_URL = "./output.mtl"; // optional
|
||||
const hud = document.getElementById("hud");
|
||||
|
||||
function hudMsg(lines) { hud.textContent = lines.join("\n"); }
|
||||
|
||||
hudMsg(["OBJ Viewer", "Booting (modules)..."]);
|
||||
|
||||
// Scene
|
||||
const scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x111111);
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.01, 1e7);
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
|
||||
// Lights
|
||||
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
|
||||
const dir = new THREE.DirectionalLight(0xffffff, 1.0);
|
||||
dir.position.set(3, 5, 4);
|
||||
scene.add(dir);
|
||||
|
||||
function frameObject(obj) {
|
||||
const box = new THREE.Box3().setFromObject(obj);
|
||||
const size = box.getSize(new THREE.Vector3());
|
||||
const center = box.getCenter(new THREE.Vector3());
|
||||
|
||||
obj.position.sub(center);
|
||||
|
||||
const maxDim = Math.max(size.x, size.y, size.z);
|
||||
const fov = camera.fov * Math.PI / 180;
|
||||
let dist = (maxDim / 2) / Math.tan(fov / 2);
|
||||
dist *= 1.6;
|
||||
|
||||
camera.position.set(dist, dist * 0.6, dist);
|
||||
camera.near = Math.max(maxDim / 1000, 0.01);
|
||||
camera.far = maxDim * 1000;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
controls.target.set(0, 0, 0);
|
||||
controls.update();
|
||||
}
|
||||
|
||||
async function urlExists(url) {
|
||||
try {
|
||||
// Some servers block HEAD; fall back to GET if needed.
|
||||
let r = await fetch(url, { method: "HEAD" });
|
||||
if (!r.ok) r = await fetch(url, { method: "GET" });
|
||||
return r.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
hudMsg(["OBJ Viewer", `Loading: ${OBJ_URL}`]);
|
||||
|
||||
const objLoader = new OBJLoader();
|
||||
|
||||
// Optional MTL
|
||||
const hasMtl = await urlExists(MTL_URL);
|
||||
if (hasMtl) {
|
||||
hudMsg(["OBJ Viewer", `Loading MTL: ${MTL_URL}`]);
|
||||
const mtlLoader = new MTLLoader();
|
||||
const materials = await new Promise((resolve, reject) => {
|
||||
mtlLoader.load(MTL_URL, resolve, undefined, reject);
|
||||
});
|
||||
materials.preload();
|
||||
objLoader.setMaterials(materials);
|
||||
}
|
||||
|
||||
hudMsg(["OBJ Viewer", `Loading OBJ: ${OBJ_URL}`]);
|
||||
|
||||
const obj = await new Promise((resolve, reject) => {
|
||||
objLoader.load(
|
||||
OBJ_URL,
|
||||
resolve,
|
||||
(xhr) => {
|
||||
const mb = (xhr.loaded / (1024 * 1024)).toFixed(2);
|
||||
hudMsg(["OBJ Viewer", `Loading OBJ: ${OBJ_URL}`, `${mb} MB loaded`]);
|
||||
},
|
||||
reject
|
||||
);
|
||||
});
|
||||
|
||||
// Ensure normals/material
|
||||
obj.traverse((c) => {
|
||||
if (c.isMesh) {
|
||||
c.geometry.computeVertexNormals?.();
|
||||
if (!c.material) c.material = new THREE.MeshStandardMaterial({ color: 0xb0b0b0 });
|
||||
}
|
||||
});
|
||||
|
||||
scene.add(obj);
|
||||
frameObject(obj);
|
||||
|
||||
hudMsg([
|
||||
"OBJ Viewer",
|
||||
"Loaded OK",
|
||||
hasMtl ? "MTL: used" : "MTL: not used",
|
||||
"",
|
||||
"Left drag: rotate",
|
||||
"Wheel: zoom",
|
||||
"Right drag: pan"
|
||||
]);
|
||||
}
|
||||
|
||||
load().catch((e) => {
|
||||
console.error(e);
|
||||
hudMsg([
|
||||
"OBJ Viewer",
|
||||
"ERROR",
|
||||
String(e),
|
||||
"",
|
||||
"Open DevTools -> Console for details."
|
||||
]);
|
||||
});
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
animate();
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user