import * as THREE from 'three'; import { state } from '../state.js'; import { screenVertexShader, screenFragmentShader } from '../shaders/screen-shaders.js'; export function createMagicMirror(x, z, rotY) { // --- Materials --- const frameMaterial = new THREE.MeshPhongMaterial({ color: 0x8B4513, shininess: 40, specular: 0x333333 }); const metalMaterial = new THREE.MeshPhongMaterial({ color: 0xd4af37, shininess: 100, specular: 0xeeeeff }); // Gold-like const mirrorGroup = new THREE.Group(); // --- 1. Mirror Stand Base --- const baseWidth = 1.5; const baseHeight = 0.2; const baseDepth = 0.6; const baseGeo = new THREE.BoxGeometry(baseWidth, baseHeight, baseDepth); const base = new THREE.Mesh(baseGeo, frameMaterial); base.position.y = baseHeight / 2; base.castShadow = true; base.receiveShadow = true; mirrorGroup.add(base); // --- 2. Stand Uprights --- const uprightHeight = 2.4; const uprightWidth = 0.15; const uprightGeo = new THREE.BoxGeometry(uprightWidth, uprightHeight, uprightWidth); const createUpright = (posX) => { const upright = new THREE.Mesh(uprightGeo, frameMaterial); upright.position.set(posX, uprightHeight / 2, 0); upright.castShadow = true; upright.receiveShadow = true; return upright; }; const uprightOffset = baseWidth / 2 - 0.3; mirrorGroup.add(createUpright(-uprightOffset)); mirrorGroup.add(createUpright(uprightOffset)); // --- 3. The Elliptical Mirror Surface (The "Screen") --- const mirrorRadius = 0.8; // Adjusted radius for scaling const mirrorGeo = new THREE.CircleGeometry(mirrorRadius, 64); // --- 3a. The permanent reflective mirror surface --- const mirrorBackMaterial = new THREE.MeshPhongMaterial({ color: 0x051020, // Dark blue tint shininess: 100, specular: 0xcccccc, envMap: state.scene.background, // Reflect the room reflectivity: 0.9 // Increased reflectivity }); const mirrorBack = new THREE.Mesh(mirrorGeo, mirrorBackMaterial); mirrorBack.position.y = 1.4; // Center height mirrorBack.position.z = 0.1; // Slightly forward in the frame mirrorBack.scale.set(1, 1.5, 1); // Scale Y to make it a tall ellipse mirrorGroup.add(mirrorBack); // --- 3b. The video surface that appears when playing --- // This is what state.tvScreen will now refer to state.tvScreen = new THREE.Mesh(mirrorGeo, new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })); state.tvScreen.position.copy(mirrorBack.position); state.tvScreen.position.z += 0.01; // Place it just in front of the reflective surface state.tvScreen.scale.copy(mirrorBack.scale); state.tvScreen.visible = false; // Start invisible mirrorGroup.add(state.tvScreen); // --- 4. Ornate Elliptical Mirror Frame (Torus) --- const frameRadius = mirrorRadius; const frameTubeRadius = 0.04; // Made the rim thinner const frameRingGeo = new THREE.TorusGeometry(frameRadius, frameTubeRadius, 16, 100); const frameRing = new THREE.Mesh(frameRingGeo, metalMaterial); frameRing.position.copy(state.tvScreen.position); frameRing.scale.copy(state.tvScreen.scale); // Apply the same scale to the frame frameRing.castShadow = true; mirrorGroup.add(frameRing); // --- 5. Light from the Mirror --- state.screenLight = new THREE.PointLight(0xffffff, 0, 10); state.screenLight.position.copy(state.tvScreen.position); state.screenLight.position.z += 0.3; // Position light in front of the mirror state.screenLight.castShadow = true; state.screenLight.shadow.mapSize.width = 1024; state.screenLight.shadow.mapSize.height = 1024; state.screenLight.shadow.camera.near = 0.2; state.screenLight.shadow.camera.far = 5; //mirrorGroup.add(state.screenLight); // Position and rotate the entire group mirrorGroup.position.set(x, 0, z); mirrorGroup.rotation.y = rotY; state.scene.add(mirrorGroup); } export function turnTvScreenOff() { if (state.tvScreenPowered) { state.tvScreenPowered = false; setScreenEffect(2, () => { state.tvScreen.visible = false; // Hide the video surface on completion state.screenLight.intensity = 0.0; }); // Trigger power down effect } } export function turnTvScreenOn() { if (state.tvScreen.material) { state.tvScreen.material.dispose(); } state.tvScreen.visible = true; // Make the video surface visible // Use the shader material for video playback state.tvScreen.material = new THREE.ShaderMaterial({ uniforms: { videoTexture: { value: state.videoTexture }, u_effect_type: { value: 0.0 }, u_effect_strength: { value: 0.0 }, u_time: { value: 0.0 }, }, vertexShader: screenVertexShader, fragmentShader: screenFragmentShader, transparent: true, }); state.tvScreen.material.needsUpdate = true; if (!state.tvScreenPowered) { state.tvScreenPowered = true; setScreenEffect(1); // Trigger power on effect } } export function setScreenEffect(effectType, onComplete) { const material = state.tvScreen.material; if (!material.uniforms) return; state.screenEffect.active = true; state.screenEffect.type = effectType; state.screenEffect.startTime = state.clock.getElapsedTime() * 1000; state.screenEffect.onComplete = onComplete; } export function updateScreenEffect() { if (!state.screenEffect.active) return; const material = state.tvScreen.material; if (!material.uniforms) return; const elapsedTime = (state.clock.getElapsedTime() * 1000) - state.screenEffect.startTime; const progress = Math.min(elapsedTime / state.screenEffect.duration, 1.0); const easedProgress = state.screenEffect.easing(progress); material.uniforms.u_effect_type.value = state.screenEffect.type; material.uniforms.u_effect_strength.value = easedProgress; if (progress >= 1.0) { state.screenEffect.active = false; material.uniforms.u_effect_strength.value = (state.screenEffect.type === 2) ? 1.0 : 0.0; if (state.screenEffect.onComplete) { state.screenEffect.onComplete(); } material.uniforms.u_effect_type.value = 0.0; } }