813 lines
31 KiB
JavaScript
813 lines
31 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 { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
|
|
import './styles.css';
|
|
|
|
// Global variables
|
|
let scene, camera, renderer, controls;
|
|
let cube;
|
|
let cameraConfig = null;
|
|
|
|
// Load camera configuration from config file
|
|
async function loadCameraConfig() {
|
|
try {
|
|
const response = await fetch('./camera-config.json');
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
}
|
|
cameraConfig = await response.json();
|
|
console.log('✅ Camera config loaded successfully:', cameraConfig);
|
|
return cameraConfig;
|
|
} catch (error) {
|
|
console.warn('⚠️ Could not load camera-config.json, using original defaults.');
|
|
console.log('📝 To use custom camera settings, ensure camera-config.json is accessible from your web server.');
|
|
|
|
// Return original simple defaults
|
|
return {
|
|
camera: {
|
|
position: { x: 20, y: 20, z: 20 },
|
|
target: { x: 0, y: 0, z: 0 },
|
|
distance: 34.64
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
// Initialize the 3D scene
|
|
async function init() {
|
|
// Load camera configuration first
|
|
cameraConfig = await loadCameraConfig();
|
|
// Create container
|
|
const container = document.getElementById('container');
|
|
|
|
// Create scene
|
|
scene = new THREE.Scene();
|
|
scene.background = new THREE.Color(0x2c3e50);
|
|
|
|
// Create camera with extended far clipping plane for better zoom out
|
|
camera = new THREE.PerspectiveCamera(
|
|
75, // Field of view
|
|
window.innerWidth / window.innerHeight, // Aspect ratio
|
|
0.1, // Near clipping plane
|
|
10000 // Extended far clipping plane for better zoom out
|
|
);
|
|
// Use camera config values or defaults
|
|
const camPos = cameraConfig.camera.position;
|
|
const camTarget = cameraConfig.camera.target;
|
|
camera.position.set(camPos.x, camPos.y, camPos.z);
|
|
camera.lookAt(camTarget.x, camTarget.y, camTarget.z);
|
|
|
|
// 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.outputColorSpace = THREE.SRGBColorSpace;
|
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
renderer.toneMappingExposure = 1.5;
|
|
|
|
// Enable proper transparency rendering
|
|
renderer.sortObjects = true;
|
|
renderer.autoClear = false;
|
|
|
|
container.appendChild(renderer.domElement);
|
|
|
|
// Add orbit controls with enhanced touch support
|
|
controls = new OrbitControls(camera, renderer.domElement);
|
|
controls.enableDamping = true;
|
|
controls.dampingFactor = 0.05;
|
|
controls.enableZoom = true;
|
|
controls.enablePan = true;
|
|
controls.enableRotate = true;
|
|
|
|
// Touch-specific settings
|
|
controls.touches = {
|
|
ONE: THREE.TOUCH.ROTATE, // Single finger touch for rotation
|
|
TWO: THREE.TOUCH.DOLLY_PAN // Two fingers for zoom and pan
|
|
};
|
|
|
|
// Enhanced touch sensitivity and limits
|
|
controls.rotateSpeed = 1.0; // Rotation speed
|
|
controls.zoomSpeed = 2.0; // Increased zoom speed for faster mouse wheel
|
|
controls.panSpeed = 0.8; // Pan speed
|
|
// Sync zoom limits with camera clipping planes
|
|
controls.minDistance = 0.1; // Match camera near plane
|
|
controls.maxDistance = 8000; // Stay within camera far plane (10000) with buffer
|
|
|
|
// Smooth touch interactions
|
|
controls.enableDamping = true;
|
|
controls.dampingFactor = 0.08; // Slightly higher for smoother touch
|
|
|
|
// Add custom touch event handlers
|
|
setupCustomTouchControls();
|
|
|
|
// Add lighting
|
|
setupLighting();
|
|
|
|
// Load the cube model
|
|
loadCubeModel();
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', onWindowResize, false);
|
|
|
|
// Handle print events
|
|
setupPrintHandlers();
|
|
|
|
// Setup manual print button
|
|
setupPrintButton();
|
|
|
|
// Start render loop
|
|
animate();
|
|
}
|
|
|
|
function setupCustomTouchControls() {
|
|
const canvas = renderer.domElement;
|
|
|
|
// Add touch-friendly CSS for better interaction
|
|
canvas.style.touchAction = 'none';
|
|
canvas.style.userSelect = 'none';
|
|
canvas.style.webkitUserSelect = 'none';
|
|
canvas.style.mozUserSelect = 'none';
|
|
canvas.style.msUserSelect = 'none';
|
|
|
|
// Prevent context menu on long press
|
|
canvas.addEventListener('contextmenu', function(event) {
|
|
event.preventDefault();
|
|
}, { passive: false });
|
|
|
|
console.log('Touch controls enabled - OrbitControls handles all touch gestures natively');
|
|
}
|
|
|
|
function setupPrintHandlers() {
|
|
const originalBackgroundColor = scene.background;
|
|
const originalRendererClearColor = renderer.getClearColor(new THREE.Color());
|
|
const originalRendererClearAlpha = renderer.getClearAlpha();
|
|
|
|
let isTestMode = false;
|
|
let testTimeout = null;
|
|
|
|
// Before print - change background to white and make renderer opaque
|
|
const beforePrint = () => {
|
|
if (isTestMode) {
|
|
console.log('🚫 Print event ignored - test mode is active');
|
|
return;
|
|
}
|
|
|
|
console.log('🖨️ PRINT MODE ACTIVATED - Changing background to white');
|
|
|
|
// Force scene background to white
|
|
scene.background = new THREE.Color(0xffffff);
|
|
|
|
// Set renderer clear color to white
|
|
renderer.setClearColor(0xffffff, 1.0);
|
|
|
|
// Force canvas background to white with multiple methods
|
|
const canvas = renderer.domElement;
|
|
canvas.style.setProperty('background', 'white', 'important');
|
|
canvas.style.setProperty('background-color', 'white', 'important');
|
|
canvas.style.setProperty('background-image', 'none', 'important');
|
|
|
|
// Also set body and html backgrounds
|
|
document.body.style.setProperty('background', 'white', 'important');
|
|
document.body.style.setProperty('background-color', 'white', 'important');
|
|
document.documentElement.style.setProperty('background', 'white', 'important');
|
|
document.documentElement.style.setProperty('background-color', 'white', 'important');
|
|
|
|
// Force a render to apply changes immediately
|
|
renderer.render(scene, camera);
|
|
|
|
console.log('✅ Print background forcibly set to white');
|
|
};
|
|
|
|
// After print - restore original background
|
|
const afterPrint = () => {
|
|
if (isTestMode) {
|
|
console.log('🚫 Print restore ignored - test mode is active');
|
|
return;
|
|
}
|
|
|
|
console.log('🖨️ PRINT MODE DEACTIVATED - Restoring original background');
|
|
|
|
// Restore original settings
|
|
scene.background = originalBackgroundColor;
|
|
renderer.setClearColor(originalRendererClearColor, originalRendererClearAlpha);
|
|
|
|
// Remove forced styles
|
|
const canvas = renderer.domElement;
|
|
canvas.style.removeProperty('background');
|
|
canvas.style.removeProperty('background-color');
|
|
canvas.style.removeProperty('background-image');
|
|
|
|
document.body.style.removeProperty('background');
|
|
document.body.style.removeProperty('background-color');
|
|
document.documentElement.style.removeProperty('background');
|
|
document.documentElement.style.removeProperty('background-color');
|
|
|
|
console.log('✅ Original background restored');
|
|
};
|
|
|
|
// Apply white background (used by both test and print modes)
|
|
const applyWhiteBackground = () => {
|
|
// Force scene background to white
|
|
scene.background = new THREE.Color(0xffffff);
|
|
|
|
// Set renderer clear color to white
|
|
renderer.setClearColor(0xffffff, 1.0);
|
|
|
|
// Force canvas background to white with multiple methods
|
|
const canvas = renderer.domElement;
|
|
canvas.style.setProperty('background', 'white', 'important');
|
|
canvas.style.setProperty('background-color', 'white', 'important');
|
|
canvas.style.setProperty('background-image', 'none', 'important');
|
|
|
|
// Also set body and html backgrounds
|
|
document.body.style.setProperty('background', 'white', 'important');
|
|
document.body.style.setProperty('background-color', 'white', 'important');
|
|
document.documentElement.style.setProperty('background', 'white', 'important');
|
|
document.documentElement.style.setProperty('background-color', 'white', 'important');
|
|
|
|
// Force a render to apply changes immediately
|
|
renderer.render(scene, camera);
|
|
};
|
|
|
|
// Restore original background (used by both test and print modes)
|
|
const restoreBackground = () => {
|
|
// Restore original settings
|
|
scene.background = originalBackgroundColor;
|
|
renderer.setClearColor(originalRendererClearColor, originalRendererClearAlpha);
|
|
|
|
// Remove forced styles
|
|
const canvas = renderer.domElement;
|
|
canvas.style.removeProperty('background');
|
|
canvas.style.removeProperty('background-color');
|
|
canvas.style.removeProperty('background-image');
|
|
|
|
document.body.style.removeProperty('background');
|
|
document.body.style.removeProperty('background-color');
|
|
document.documentElement.style.removeProperty('background');
|
|
document.documentElement.style.removeProperty('background-color');
|
|
};
|
|
|
|
// Test the print handler immediately (for debugging)
|
|
window.testPrintMode = () => {
|
|
console.log('🧪 Testing print mode (print events disabled during test)...');
|
|
isTestMode = true;
|
|
|
|
if (testTimeout) {
|
|
clearTimeout(testTimeout);
|
|
}
|
|
|
|
applyWhiteBackground();
|
|
console.log('✅ Test mode background set to white');
|
|
|
|
testTimeout = setTimeout(() => {
|
|
console.log('🧪 Test complete, restoring...');
|
|
restoreBackground();
|
|
isTestMode = false;
|
|
testTimeout = null;
|
|
console.log('✅ Test mode complete, print events re-enabled');
|
|
}, 6000);
|
|
};
|
|
|
|
// Cancel test mode early
|
|
window.cancelTestMode = () => {
|
|
if (testTimeout) {
|
|
clearTimeout(testTimeout);
|
|
testTimeout = null;
|
|
}
|
|
if (isTestMode) {
|
|
restoreBackground();
|
|
isTestMode = false;
|
|
console.log('🧪 Test mode cancelled, print events re-enabled');
|
|
}
|
|
};
|
|
|
|
// Listen for print events
|
|
if (window.matchMedia) {
|
|
const mediaQueryList = window.matchMedia('print');
|
|
mediaQueryList.addEventListener('change', (mql) => {
|
|
console.log('📱 Media query change detected:', mql.matches ? 'PRINT' : 'SCREEN');
|
|
if (mql.matches) {
|
|
beforePrint();
|
|
} else {
|
|
afterPrint();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Fallback for older browsers
|
|
window.addEventListener('beforeprint', () => {
|
|
console.log('🖨️ beforeprint event fired');
|
|
beforePrint();
|
|
});
|
|
window.addEventListener('afterprint', () => {
|
|
console.log('🖨️ afterprint event fired');
|
|
afterPrint();
|
|
});
|
|
|
|
console.log('🔧 Print event handlers set up.');
|
|
console.log(' Use window.testPrintMode() to test for 6 seconds');
|
|
console.log(' Use window.cancelTestMode() to end test early');
|
|
}
|
|
|
|
function setupPrintButton() {
|
|
const printBtn = document.getElementById('print-btn');
|
|
if (!printBtn) {
|
|
console.warn('Print button not found');
|
|
return;
|
|
}
|
|
|
|
const originalBackgroundColor = scene.background;
|
|
const originalRendererClearColor = renderer.getClearColor(new THREE.Color());
|
|
const originalRendererClearAlpha = renderer.getClearAlpha();
|
|
|
|
printBtn.addEventListener('click', () => {
|
|
console.log('🖨️ Manual print button clicked - forcing white background');
|
|
|
|
// Force white background immediately
|
|
scene.background = new THREE.Color(0xffffff);
|
|
renderer.setClearColor(0xffffff, 1.0);
|
|
|
|
// Force canvas and page backgrounds to white
|
|
const canvas = renderer.domElement;
|
|
canvas.style.setProperty('background', 'white', 'important');
|
|
canvas.style.setProperty('background-color', 'white', 'important');
|
|
canvas.style.setProperty('background-image', 'none', 'important');
|
|
|
|
document.body.style.setProperty('background', 'white', 'important');
|
|
document.body.style.setProperty('background-color', 'white', 'important');
|
|
document.documentElement.style.setProperty('background', 'white', 'important');
|
|
document.documentElement.style.setProperty('background-color', 'white', 'important');
|
|
|
|
// Force a render to apply changes
|
|
renderer.render(scene, camera);
|
|
|
|
console.log('✅ Background forced to white, opening print dialog...');
|
|
|
|
// Small delay to ensure changes are applied, then open print dialog
|
|
setTimeout(() => {
|
|
window.print();
|
|
|
|
// Restore background after a delay to account for print dialog
|
|
setTimeout(() => {
|
|
console.log('🔄 Restoring original background...');
|
|
|
|
scene.background = originalBackgroundColor;
|
|
renderer.setClearColor(originalRendererClearColor, originalRendererClearAlpha);
|
|
|
|
canvas.style.removeProperty('background');
|
|
canvas.style.removeProperty('background-color');
|
|
canvas.style.removeProperty('background-image');
|
|
|
|
document.body.style.removeProperty('background');
|
|
document.body.style.removeProperty('background-color');
|
|
document.documentElement.style.removeProperty('background');
|
|
document.documentElement.style.removeProperty('background-color');
|
|
|
|
console.log('✅ Original background restored');
|
|
}, 1000); // Wait 1 second after print dialog opens
|
|
}, 100); // Small delay to apply changes
|
|
});
|
|
|
|
console.log('🖨️ Manual print button set up');
|
|
}
|
|
|
|
function setupLighting() {
|
|
// Ambient light for overall illumination - increased intensity
|
|
const ambientLight = new THREE.AmbientLight(0x404040, 1.2);
|
|
scene.add(ambientLight);
|
|
|
|
// Directional light (like sunlight) - increased intensity
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
|
|
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 - increased intensity
|
|
const pointLight = new THREE.PointLight(0xffffff, 0.8, 100);
|
|
pointLight.position.set(-10, 10, 10);
|
|
scene.add(pointLight);
|
|
|
|
// Camera light - follows the camera to illuminate from viewing angle
|
|
window.cameraLight = new THREE.PointLight(0xffffff, 1.0, 200);
|
|
window.cameraLight.position.copy(camera.position);
|
|
scene.add(window.cameraLight);
|
|
|
|
// Additional camera spotlight for focused illumination
|
|
window.cameraSpotlight = new THREE.SpotLight(0xffffff, 0.8, 100, Math.PI / 4, 0.3);
|
|
window.cameraSpotlight.position.copy(camera.position);
|
|
window.cameraSpotlight.target.position.set(0, 0, 0); // Point at origin initially
|
|
scene.add(window.cameraSpotlight);
|
|
scene.add(window.cameraSpotlight.target);
|
|
|
|
// Interior point lights (will be repositioned when model loads) - increased intensity
|
|
window.interiorLight = new THREE.PointLight(0xffffff, 0.8, 50);
|
|
window.interiorLight.position.set(0, 0, 0); // Will be moved to model center
|
|
scene.add(window.interiorLight);
|
|
|
|
// Additional interior light for better coverage - increased intensity
|
|
window.interiorLight2 = new THREE.PointLight(0xffffff, 0.5, 30);
|
|
window.interiorLight2.position.set(5, 5, -5); // Will be adjusted relative to model
|
|
scene.add(window.interiorLight2);
|
|
|
|
// Hemisphere light for natural lighting - increased intensity
|
|
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.8);
|
|
scene.add(hemiLight);
|
|
|
|
console.log('✨ Camera lights added - they will follow your view');
|
|
|
|
// 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, {
|
|
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 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
|
|
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 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);
|
|
|
|
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 camera lights to target the model center
|
|
if (window.cameraLight) {
|
|
window.cameraLight.distance = maxDimension * 3; // Adjust range based on model size
|
|
}
|
|
|
|
if (window.cameraSpotlight) {
|
|
window.cameraSpotlight.target.position.copy(center);
|
|
window.cameraSpotlight.distance = maxDimension * 2;
|
|
}
|
|
|
|
// Update controls
|
|
controls.update();
|
|
|
|
// Add rotation logging
|
|
setupRotationLogging();
|
|
|
|
console.log('Model centered at:', center);
|
|
console.log('Model size:', size);
|
|
console.log('Camera positioned at:', camera.position);
|
|
}
|
|
|
|
function setupRotationLogging() {
|
|
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');
|
|
}
|
|
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
|
|
// Update controls
|
|
controls.update();
|
|
|
|
// Update camera lights to follow camera position
|
|
if (window.cameraLight) {
|
|
window.cameraLight.position.copy(camera.position);
|
|
}
|
|
|
|
if (window.cameraSpotlight) {
|
|
window.cameraSpotlight.position.copy(camera.position);
|
|
// Make spotlight point towards the orbit controls target (model center)
|
|
if (controls && controls.target) {
|
|
window.cameraSpotlight.target.position.copy(controls.target);
|
|
}
|
|
}
|
|
|
|
// 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', async () => {
|
|
await init();
|
|
});
|