Refactor index.js to modularize functionality and improve readability. Removed unused variables and functions, integrated scene setup, lighting, and model loading into dedicated functions. Enhanced FPV controls and camera updates for better performance and maintainability.

This commit is contained in:
sebseb7
2025-08-25 22:32:36 +02:00
parent 51ad0743cf
commit f915bd29c0
7 changed files with 1158 additions and 1103 deletions

316
src/model-loader.js Normal file
View File

@@ -0,0 +1,316 @@
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 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;
// 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
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(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 =
'<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(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.DoubleSide,
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 =
'<div style="color: #ff6b6b;">Failed to load model. Check if cube.obj exists.</div>';
});
}
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.5;
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');
}