Files
threedee/index.html
2025-08-24 19:48:10 +02:00

491 lines
20 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 Cube Viewer</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
#container {
width: 100vw;
height: 100vh;
position: relative;
}
#info {
position: absolute;
top: 20px;
left: 20px;
color: white;
z-index: 100;
background: rgba(0,0,0,0.5);
padding: 15px;
border-radius: 10px;
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
}
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 24px;
z-index: 200;
background: rgba(0,0,0,0.7);
padding: 20px;
border-radius: 10px;
}
.loading-spinner {
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 3px solid #ffffff;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div id="container">
<div id="loading">
<div class="loading-spinner"></div>
Loading 3D Model...
</div>
<div id="info">
<h2>3D Cube Viewer</h2>
<p>🖱️ Left click + drag: Rotate around model</p>
<p>🖱️ Right click + drag: Pan</p>
<p>⚙️ Mouse wheel: Zoom in/out</p>
</div>
</div>
<!-- Three.js Core -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- OBJ Loader -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js"></script>
<!-- MTL Loader -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/MTLLoader.js"></script>
<!-- Orbit Controls -->
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
<script>
// Global variables
let scene, camera, renderer, controls;
let cube;
// Initialize the 3D scene
function init() {
// Create container
const container = document.getElementById('container');
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x2c3e50);
// Create camera
camera = new THREE.PerspectiveCamera(
75, // Field of view
window.innerWidth / window.innerHeight, // Aspect ratio
0.1, // Near clipping plane
1000 // Far clipping plane
);
camera.position.set(20, 20, 20);
camera.lookAt(0, 0, 0);
// Create renderer
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
// Enable proper transparency rendering
renderer.sortObjects = true;
renderer.autoClear = false;
container.appendChild(renderer.domElement);
// Add orbit controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.enableZoom = true;
controls.enablePan = true;
controls.enableRotate = true;
// Add lighting
setupLighting();
// Load the cube model
loadCubeModel();
// Handle window resize
window.addEventListener('resize', onWindowResize, false);
// Start render loop
animate();
}
function setupLighting() {
// Ambient light for overall illumination
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
// Directional light (like sunlight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 10, 5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
scene.add(directionalLight);
// Point light for additional illumination
const pointLight = new THREE.PointLight(0xffffff, 0.6, 100);
pointLight.position.set(-10, 10, 10);
scene.add(pointLight);
// Interior point lights (will be repositioned when model loads)
window.interiorLight = new THREE.PointLight(0xffffff, 0.5, 50);
window.interiorLight.position.set(0, 0, 0); // Will be moved to model center
scene.add(window.interiorLight);
// Additional interior light for better coverage
window.interiorLight2 = new THREE.PointLight(0xffffff, 0.3, 30);
window.interiorLight2.position.set(5, 5, -5); // Will be adjusted relative to model
scene.add(window.interiorLight2);
// Hemisphere light for natural lighting
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.5);
scene.add(hemiLight);
// Create environment map for glass reflections
setupEnvironmentMap();
}
function setupEnvironmentMap() {
// Create a simple cube texture for reflections (makes glass more visible)
const cubeTextureLoader = new THREE.CubeTextureLoader();
// Create a procedural cube map for reflections
const envMapRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
format: THREE.RGBFormat,
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter
});
// Simple gradient environment for reflections
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
// Create gradient for reflections
const gradient = ctx.createLinearGradient(0, 0, 0, 256);
gradient.addColorStop(0, '#87CEEB'); // Sky blue
gradient.addColorStop(0.5, '#F0F8FF'); // Alice blue
gradient.addColorStop(1, '#4682B4'); // Steel blue
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 256);
// Convert to texture
const texture = new THREE.CanvasTexture(canvas);
texture.mapping = THREE.EquirectangularReflectionMapping;
// Store for glass materials
window.environmentMap = texture;
}
function loadCubeModel() {
// Create loaders
const mtlLoader = new THREE.MTLLoader();
const objLoader = new THREE.OBJLoader();
// Load material file first
mtlLoader.load('cube.mtl', function(materials) {
materials.preload();
// Configure materials for double-sided rendering with proper transparency
Object.keys(materials.materials).forEach(function(key) {
const material = materials.materials[key];
material.side = THREE.DoubleSide;
// Handle glass materials specially
if (key.toLowerCase().includes('glass')) {
material.transparent = true;
// Make glass more visible - realistic glass opacity
material.opacity = 0.7; // More visible than original MTL setting
// Enhanced glass properties for better visibility
material.shininess = 100;
material.reflectivity = 0.8;
// Glass-like color with slight tint for visibility
material.color = new THREE.Color(0.9, 0.95, 1.0);
// Add specular highlights to make glass more apparent
material.specular = new THREE.Color(0.8, 0.9, 1.0);
// Slight emissive to make glass edges more visible
if (material.emissive) {
material.emissive.setHex(0x001122); // Very subtle blue glow
}
// Apply environment map for reflections
if (window.environmentMap) {
material.envMap = window.environmentMap;
material.envMapIntensity = 0.9;
}
console.log(`Glass material '${key}' configured with opacity: ${material.opacity}`);
} else {
// Non-glass materials
material.transparent = false;
material.opacity = 1.0;
// Add slight emissive for interior visibility on solid materials
if (material.emissive) {
material.emissive.setHex(0x111111);
}
}
material.needsUpdate = true;
});
// Set materials to OBJ loader
objLoader.setMaterials(materials);
// Load the OBJ file
objLoader.load('cube.obj', function(object) {
// Scale and position the model
object.scale.set(0.5, 0.5, 0.5);
object.position.set(0, 0, 0);
// Enable shadows and fix materials for interior faces
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.castShadow = true;
child.receiveShadow = true;
// Special handling for glass materials to improve visibility
if (child.material && child.material.name && child.material.name.toLowerCase().includes('glass')) {
// Glass-specific settings
child.material.side = THREE.DoubleSide;
child.material.transparent = true;
child.material.opacity = 0.7;
child.material.alphaTest = 0.1;
// Enhanced reflective properties
child.material.envMapIntensity = 0.9;
// Apply environment map for reflections
if (window.environmentMap) {
child.material.envMap = window.environmentMap;
}
// Disable shadow casting for glass (more realistic)
child.castShadow = false;
child.receiveShadow = true;
console.log('Applied glass-specific settings to mesh');
// Add subtle wireframe to glass edges for better visibility
if (child.geometry) {
const wireframe = new THREE.EdgesGeometry(child.geometry);
const wireframeMaterial = new THREE.LineBasicMaterial({
color: 0x88aaff,
transparent: true,
opacity: 0.3,
linewidth: 2
});
const wireframeLines = new THREE.LineSegments(wireframe, wireframeMaterial);
child.add(wireframeLines);
}
} else {
// Non-glass materials
child.material.side = THREE.DoubleSide;
child.material.transparent = false;
child.material.opacity = 1.0;
// Ensure proper lighting on both sides
if (child.material.type === 'MeshLambertMaterial' || child.material.type === 'MeshPhongMaterial') {
child.material.emissive = new THREE.Color(0x111111); // Slight self-illumination
}
}
child.material.needsUpdate = true;
}
});
// Store reference and add to scene
cube = object;
scene.add(cube);
// Calculate model center and adjust controls
centerModelAndControls(cube);
// Hide loading indicator
document.getElementById('loading').style.display = 'none';
console.log('Cube loaded successfully!');
},
function(progress) {
console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%');
},
function(error) {
console.error('Error loading OBJ file:', error);
document.getElementById('loading').innerHTML =
'<div style="color: #ff6b6b;">Error loading model. Please check console for details.</div>';
});
},
function(progress) {
console.log('Loading MTL progress:', (progress.loaded / progress.total * 100) + '%');
},
function(error) {
console.error('Error loading MTL file:', error);
// Try to load OBJ without materials
loadObjWithoutMaterials();
});
}
function loadObjWithoutMaterials() {
const objLoader = new THREE.OBJLoader();
objLoader.load('cube.obj', function(object) {
// Apply default material with better interior visibility
const defaultMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
side: THREE.DoubleSide,
transparent: false,
opacity: 1.0,
emissive: new THREE.Color(0x111111), // Slight self-illumination for interior visibility
shininess: 30
});
object.traverse(function(child) {
if (child instanceof THREE.Mesh) {
child.material = defaultMaterial.clone(); // Clone to avoid sharing
child.castShadow = true;
child.receiveShadow = true;
}
});
// Scale and position
object.scale.set(0.5, 0.5, 0.5);
object.position.set(0, 0, 0);
cube = object;
scene.add(cube);
// Calculate model center and adjust controls
centerModelAndControls(cube);
document.getElementById('loading').style.display = 'none';
console.log('Cube loaded without materials!');
},
function(progress) {
console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%');
},
function(error) {
console.error('Error loading OBJ file:', error);
document.getElementById('loading').innerHTML =
'<div style="color: #ff6b6b;">Failed to load model. Check if cube.obj exists.</div>';
});
}
function centerModelAndControls(object) {
// Calculate the bounding box of the model
const boundingBox = new THREE.Box3().setFromObject(object);
// Get the center point of the bounding box
const center = boundingBox.getCenter(new THREE.Vector3());
// Get the size of the bounding box
const size = boundingBox.getSize(new THREE.Vector3());
// Set the orbit controls target to the model's center
controls.target.copy(center);
// Calculate optimal camera distance based on model size
const maxDimension = Math.max(size.x, size.y, size.z);
const fov = camera.fov * (Math.PI / 180);
const distance = Math.abs(maxDimension / Math.sin(fov / 2)) * 1.2; // 1.2 for some padding
// Position camera at optimal distance from model center
const direction = camera.position.clone().sub(center).normalize();
camera.position.copy(center).add(direction.multiplyScalar(distance));
// Update camera to look at the model center
camera.lookAt(center);
// Reposition interior lights relative to model center
if (window.interiorLight) {
window.interiorLight.position.copy(center);
// Adjust light range based on model size
window.interiorLight.distance = maxDimension * 2;
}
if (window.interiorLight2) {
window.interiorLight2.position.copy(center).add(new THREE.Vector3(
size.x * 0.3,
size.y * 0.3,
-size.z * 0.3
));
window.interiorLight2.distance = maxDimension * 1.5;
}
// Update controls
controls.update();
console.log('Model centered at:', center);
console.log('Model size:', size);
console.log('Camera positioned at:', camera.position);
}
function animate() {
requestAnimationFrame(animate);
// Update controls
controls.update();
// Clear and render with proper transparency sorting
renderer.clear();
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Initialize when page loads
window.addEventListener('load', init);
</script>
</body>
</html>