<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Viewer OBJ</title>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #ffffff;
}
canvas {
display: block;
}
</style>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.180.0/examples/jsm/"
}
}
</script>
</head>
<body>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
/* =========================================================
FICHIERS À CHARGER
========================================================= */
const MODEL_FOLDER = "./model/";
const OBJ_FILE = "objet.obj";
const TEXTURE_FILE = "texture.jpg";
/* =========================================================
SCÈNE
========================================================= */
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
/* =========================================================
CAMÉRA
========================================================= */
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.01,
100000
);
camera.position.set(0, 0, 5);
/* =========================================================
RENDERER
========================================================= */
const renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(
Math.min(window.devicePixelRatio, 2)
);
renderer.setSize(
window.innerWidth,
window.innerHeight
);
renderer.outputColorSpace = THREE.SRGBColorSpace;
document.body.appendChild(
renderer.domElement
);
/* =========================================================
CONTRÔLES SOURIS
========================================================= */
const controls = new OrbitControls(
camera,
renderer.domElement
);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.enablePan = true;
controls.enableZoom = true;
controls.enableRotate = true;
/* =========================================================
LUMIÈRES
========================================================= */
const hemisphereLight = new THREE.HemisphereLight(
0xffffff,
0x777777,
2.5
);
scene.add(
hemisphereLight
);
const directionalLight1 = new THREE.DirectionalLight(
0xffffff,
2.5
);
directionalLight1.position.set(
4,
5,
5
);
scene.add(
directionalLight1
);
const directionalLight2 = new THREE.DirectionalLight(
0xffffff,
1.5
);
directionalLight2.position.set(
-4,
2,
-3
);
scene.add(
directionalLight2
);
/* =========================================================
TEXTURE
========================================================= */
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(
MODEL_FOLDER + TEXTURE_FILE,
function(texture) {
texture.colorSpace = THREE.SRGBColorSpace;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.flipY = true;
},
undefined,
function(error) {
console.warn(
"La texture n'a pas pu être chargée.",
error
);
}
);
/* =========================================================
MATÉRIAU
========================================================= */
const material = new THREE.MeshStandardMaterial({
map: texture,
color: 0xffffff,
roughness: 0.8,
metalness: 0,
side: THREE.DoubleSide
});
/* =========================================================
CHARGEMENT OBJ
========================================================= */
const objLoader = new OBJLoader();
objLoader.setPath(
MODEL_FOLDER
);
objLoader.load(
OBJ_FILE,
function(object) {
object.traverse(
function(child) {
if (child.isMesh) {
if (
!child.geometry.attributes.normal
) {
child.geometry.computeVertexNormals();
}
child.material =
material;
}
}
);
scene.add(
object
);
centerObject(
object
);
},
function(xhr) {
if (
xhr.total > 0
) {
const percent =
Math.round(
xhr.loaded /
xhr.total *
100
);
console.log(
percent +
"% chargé"
);
}
},
function(error) {
console.error(
"Erreur de chargement OBJ :",
error
);
}
);
/* =========================================================
CENTRER AUTOMATIQUEMENT L'OBJET
========================================================= */
function centerObject(
object
) {
const box =
new THREE.Box3()
.setFromObject(
object
);
const center =
box.getCenter(
new THREE.Vector3()
);
object.position.x -=
center.x;
object.position.y -=
center.y;
object.position.z -=
center.z;
const centeredBox =
new THREE.Box3()
.setFromObject(
object
);
const size =
centeredBox.getSize(
new THREE.Vector3()
);
const maxSize =
Math.max(
size.x,
size.y,
size.z
);
if (
!Number.isFinite(maxSize) ||
maxSize <= 0
) {
return;
}
const fov =
camera.fov *
Math.PI /
180;
let distance =
maxSize /
(
2 *
Math.tan(
fov / 2
)
);
distance *= 1.5;
camera.position.set(
distance * 0.65,
distance * 0.35,
distance
);
camera.near =
Math.max(
distance / 1000,
0.001
);
camera.far =
distance * 100;
camera.updateProjectionMatrix();
controls.target.set(
0,
0,
0
);
controls.minDistance =
distance * 0.05;
controls.maxDistance =
distance * 10;
controls.update();
}
/* =========================================================
REDIMENSIONNEMENT
========================================================= */
window.addEventListener(
"resize",
function() {
camera.aspect =
window.innerWidth /
window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(
window.innerWidth,
window.innerHeight
);
}
);
/* =========================================================
BOUCLE D'AFFICHAGE
========================================================= */
function animate() {
requestAnimationFrame(
animate
);
controls.update();
renderer.render(
scene,
camera
);
}
animate();
</script>
</body>
</html>