318 lines
13 KiB
JavaScript
318 lines
13 KiB
JavaScript
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 =
|
|
'<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.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 =
|
|
'<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.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');
|
|
}
|