<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Pendulum</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Barlow&display=swap" rel="stylesheet">
<style>
body {
overflow: hidden;
background-color: midnightblue;
color: white;
font-family: 'Barlow', 'Roboto', 'Source Sans Pro', 'Open Sans', 'Lato', Helvetica, Arial, sans-serif;
}
#header {
position: absolute;
padding-left: 2rem;
}
#footer {
position: absolute;
bottom: 0;
padding-left: 2rem;
}
a {
color: white;
}
</style>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.164.0/build/three.module.js"
}
}
</script>
</head>
<body>
<div id="header">
<h1>3D PENDULUM</h1>
<p>Left-click-drag to rotate view, right-click-drag to pan, mousewheel to zoom.</p>
<p><a href="code.html" target="_blank">Code</a>, <a href="pendulum.glb">Model</a></p>
</div>
<div id="footer">
<p><a href="https://jamesstorey.co.nz">jamesstorey.co.nz</a></p>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'https://unpkg.com/three@0.164.0/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'https://unpkg.com/three@0.164.0/examples/jsm/loaders/GLTFLoader.js';
import { RGBELoader } from 'https://unpkg.com/three@0.164.0/examples/jsm/loaders/RGBELoader.js';
var renderer, scene, camera;
var mixer, clock;
function init() {
// renderer
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.8;
document.body.appendChild(renderer.domElement);
// scene
scene = new THREE.Scene();
// camera
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(-10, 10, -20);
scene.add(camera);
// controls
var controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0,4,0);
controls.update();
controls.addEventListener('change', render);
controls.minDistance = 3;
controls.maxDistance = 50;
clock = new THREE.Clock();
//hdri lighting
//https://discourse.threejs.org/t/how-to-add-hdri-background/53675/4
const pmremGenerator = new THREE.PMREMGenerator( renderer );
const hdriLoader = new RGBELoader()
//hdri image sourced from
//https://hdri-haven.com/hdri/decorated-photography-atelier
hdriLoader.load( 'christmas_photo_studio_06_1k.hdr', function ( texture ) {
const envMap = pmremGenerator.fromEquirectangular( texture ).texture;
texture.dispose();
pmremGenerator.dispose();
scene.environment = envMap
} );
// load the model
var loader = new GLTFLoader();
loader.load('pendulum.glb', function (gltf) {
scene.add(gltf.scene);
//https://stackoverflow.com/questions/60704912/play-a-gltf-animation-three-js
mixer = new THREE.AnimationMixer(gltf.scene);
gltf.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
});
// handle window resize events
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
renderer.setSize(window.innerWidth, window.innerHeight);
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
render();
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
var delta = clock.getDelta();
if (mixer) mixer.update(delta);
render();
}
init();
animate();
</script>
</body>
</html>