yp-rubix/public/agility.html
2025-08-28 13:57:09 +05:30

246 lines
8.9 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Model Viewer</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
</style>
</head>
<body>
<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 { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000); // Black background
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.physicallyCorrectLights = true;
document.body.appendChild(renderer.domElement);
// Post-processing: Bloom
const composer = new EffectComposer(renderer);
const renderPass = new RenderPass(scene, camera);
composer.addPass(renderPass);
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.0, // strength
0.45, // radius
0.85 // threshold
);
composer.addPass(bloomPass);
// Local procedural environment for better PBR response (no network)
const pmrem = new THREE.PMREMGenerator(renderer);
const roomEnv = new RoomEnvironment();
scene.environment = pmrem.fromScene(roomEnv).texture;
pmrem.dispose();
// Lighting is authored below.
// Lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.2);
scene.add(ambientLight);
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.9);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
const keyLight = new THREE.DirectionalLight(0xffffff, 2.0);
keyLight.position.set(8, 12, 10);
keyLight.castShadow = true;
keyLight.shadow.mapSize.width = 2048;
keyLight.shadow.mapSize.height = 2048;
scene.add(keyLight);
const fillLight = new THREE.DirectionalLight(0xffffff, 1.0);
fillLight.position.set(-8, 6, -10);
scene.add(fillLight);
const cameraLight = new THREE.PointLight(0xffffff, 1.2, 0, 2);
camera.add(cameraLight);
scene.add(camera);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
// Load GLTF model
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://unpkg.com/three@0.160.0/examples/jsm/libs/draco/');
loader.setDRACOLoader(dracoLoader);
let mixer = null;
let model = null;
loader.load('agility.glb', (gltf) => {
model = gltf.scene;
scene.add(model);
// --- Define and Apply Materials ---
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
metalness: 0.0,
roughness: 0.25,
transmission: 1.0,
ior: 1.5,
thickness: 2.0,
clearcoat: 0.75,
clearcoatRoughness: 0.25,
attenuationColor: new THREE.Color(0xffffff),
attenuationDistance: 1.5,
envMapIntensity: 1.25,
specularIntensity: 1.0,
specularColor: new THREE.Color(0xffffff),
transparent: true,
depthWrite: false,
side: THREE.DoubleSide
});
// Create materials with normal maps for dpd, dblsc, and gemini
// Apply different materials based on mesh names
console.log('=== Material Assignment Debug ===');
let meshCount = 0;
model.traverse((object) => {
if (object.isMesh) {
meshCount++;
console.log(`Found mesh: "${object.name}"`);
const previousMaterial = object.material;
// Apply materials by mesh name pattern
if (object.name.startsWith('base')) {
console.log(` → Applying glass material to "${object.name}"`);
object.material = glassMaterial.clone();
}
// For any other meshes, use glass material as fallback
else {
console.log(` → Applying glass material (fallback) to "${object.name}"`);
object.material = glassMaterial.clone();
}
console.log(` Material properties:`, {
name: object.material.name,
normalMap: object.material.normalMap ? 'Loaded' : 'None',
normalScale: object.material.normalScale,
transmission: object.material.transmission,
transparent: object.material.transparent
});
object.material.needsUpdate = true;
object.castShadow = true;
object.receiveShadow = true;
if (Array.isArray(previousMaterial)) {
previousMaterial.forEach((mat) => mat && mat.dispose && mat.dispose());
} else if (previousMaterial && previousMaterial.dispose) {
previousMaterial.dispose();
}
}
});
console.log(`Total meshes processed: ${meshCount}`);
console.log('=== End Material Assignment Debug ===');
// Set up animations
if (gltf.animations && gltf.animations.length > 0) {
mixer = new THREE.AnimationMixer(model);
gltf.animations.forEach((clip) => {
mixer.clipAction(clip).play();
});
}
// Center model at origin and frame it with the camera
const box = new THREE.Box3().setFromObject(model);
const center = box.getCenter(new THREE.Vector3());
model.position.sub(center);
model.updateMatrixWorld(true);
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
camera.position.set(0, 0, maxDim * 2);
controls.target.set(0, 0, 0);
controls.update();
// Ground plane removed per request
}, undefined, (error) => {
console.error('Error loading model:', error);
});
// Animation loop
const clock = new THREE.Clock();
let autoRotationAngle = 0;
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (mixer) mixer.update(delta);
// Auto-rotation (turntable effect)
if (model) {
autoRotationAngle += delta * 0.5; // Adjust speed here (0.5 = slow rotation)
model.rotation.y = autoRotationAngle;
}
controls.update();
composer.render();
}
animate();
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
composer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>