import * as THREE from 'three'; import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'; import { adjustLightsForModel } from './lighting.js'; export function loadCubeModel(scene, camera, controls, onLoadComplete) { // Create loaders const mtlLoader = new MTLLoader(); const objLoader = new OBJLoader(); // Load material file first mtlLoader.load('cube.mtl', function(materials) { materials.preload(); // Configure materials with proper transparency Object.keys(materials.materials).forEach(function(key) { const material = materials.materials[key]; // Only set DoubleSide for glass materials, others use FrontSide material.side = key.toLowerCase().includes('glass') ? THREE.DoubleSide : THREE.FrontSide; // Handle glass materials specially if (key.toLowerCase().includes('glass')) { material.transparent = true; // Preserve MTL file opacity values for glass materials // Only apply default opacity if not already set in MTL if (material.opacity === undefined || material.opacity === 1.0) { material.opacity = 0.7; // Fallback for visibility } // 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 emissive for interior visibility on solid materials if (material.emissive) { material.emissive.setHex(0x222222); // Increased emissive brightness } else { material.emissive = new THREE.Color(0x222222); } } 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; // Preserve original opacity from MTL if available if (child.material.opacity === undefined || child.material.opacity === 1.0) { 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 - fix texture appearing on both sides child.material.side = THREE.FrontSide; child.material.transparent = false; child.material.opacity = 1.0; // Ensure proper lighting if (child.material.type === 'MeshLambertMaterial' || child.material.type === 'MeshPhongMaterial') { child.material.emissive = new THREE.Color(0x222222); // Increased self-illumination for brightness } } child.material.needsUpdate = true; } }); // Store reference and add to scene window.cube = object; scene.add(object); // Calculate model center and adjust controls centerModelAndControls(object, camera, controls); // Hide loading indicator document.getElementById('loading').style.display = 'none'; console.log('Cube loaded successfully!'); if (onLoadComplete) { onLoadComplete(object); } }, function(progress) { console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%'); }, function(error) { console.error('Error loading OBJ file:', error); document.getElementById('loading').innerHTML = '
Error loading model. Please check console for details.
'; }); }, 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(scene, camera, controls, onLoadComplete); }); } export function loadObjWithoutMaterials(scene, camera, controls, onLoadComplete) { const objLoader = new OBJLoader(); objLoader.load('cube.obj', function(object) { // Apply default material with better interior visibility const defaultMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff, side: THREE.FrontSide, // Fix texture appearing on both sides transparent: false, opacity: 1.0, emissive: new THREE.Color(0x222222), // Increased self-illumination for brightness 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); window.cube = object; scene.add(object); // Calculate model center and adjust controls centerModelAndControls(object, camera, controls); document.getElementById('loading').style.display = 'none'; console.log('Cube loaded without materials!'); if (onLoadComplete) { onLoadComplete(object); } }, function(progress) { console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%'); }, function(error) { console.error('Error loading OBJ file:', error); document.getElementById('loading').innerHTML = '
Failed to load model. Check if cube.obj exists.
'; }); } function centerModelAndControls(object, camera, controls) { // 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()); // Store model bounds for FPV mode window.modelBounds = { boundingBox, center, size }; window.groundLevel = boundingBox.min.y; window.ceilingLevel = boundingBox.max.y; window.eyeHeight = window.groundLevel + (window.ceilingLevel - window.groundLevel) * 0.55; console.log('FPV bounds calculated:'); console.log('Ground level:', window.groundLevel); console.log('Ceiling level:', window.ceilingLevel); console.log('Eye height (50%):', window.eyeHeight); // 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); // Adjust lights based on model size adjustLightsForModel({ center, size }); // Update controls controls.update(); // Add rotation logging setupRotationLogging(camera, controls); console.log('Model centered at:', center); console.log('Model size:', size); console.log('Camera positioned at:', camera.position); } function setupRotationLogging(camera, controls) { let debounceTimer = null; // Log rotation and zoom changes with 1-second debounce controls.addEventListener('change', function() { // Clear existing timer if (debounceTimer) { clearTimeout(debounceTimer); } // Set new timer for 1 second delay debounceTimer = setTimeout(() => { const distance = camera.position.distanceTo(controls.target); const position = camera.position.clone(); const target = controls.target.clone(); // Calculate spherical coordinates for rotation const spherical = new THREE.Spherical().setFromVector3( position.clone().sub(target) ); console.log('=== CAMERA STATE ==='); console.log('Position:', { x: Math.round(position.x * 100) / 100, y: Math.round(position.y * 100) / 100, z: Math.round(position.z * 100) / 100 }); console.log('Target:', { x: Math.round(target.x * 100) / 100, y: Math.round(target.y * 100) / 100, z: Math.round(target.z * 100) / 100 }); console.log('Distance (Zoom):', Math.round(distance * 100) / 100); console.log('Spherical Rotation:'); console.log(' - Radius:', Math.round(spherical.radius * 100) / 100); console.log(' - Theta (horizontal):', Math.round(spherical.theta * 100) / 100, 'radians'); console.log(' - Phi (vertical):', Math.round(spherical.phi * 100) / 100, 'radians'); console.log('=================='); debounceTimer = null; }, 1000); }); console.log('Rotation logging enabled - camera state will be logged 1 second after movement stops'); }