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:
316
src/model-loader.js
Normal file
316
src/model-loader.js
Normal 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');
|
||||
}
|
||||
Reference in New Issue
Block a user