Keep Loading gallery until Environment and gl.compileAsync finish (with timeouts), GPU-upload painting textures before dismiss, and drop Canvas shadow maps. Co-authored-by: Cursor <cursoragent@cursor.com>
107 lines
3.5 KiB
TypeScript
107 lines
3.5 KiB
TypeScript
import { useState } from 'react';
|
|
import * as THREE from 'three';
|
|
import { Text } from '@react-three/drei';
|
|
|
|
const DOOR_WIDTH = 2.4;
|
|
const DOOR_HEIGHT = 2.5;
|
|
const WALL_THICKNESS = 0.18;
|
|
|
|
/** Open archway connecting to the next movement wing. */
|
|
export default function HallPassage({
|
|
position,
|
|
active,
|
|
label,
|
|
onActivate,
|
|
wallColor = '#f0ebe3',
|
|
trimColor = '#ddd5c8',
|
|
}: {
|
|
position: [number, number, number];
|
|
active: boolean;
|
|
label: string;
|
|
onActivate: () => void;
|
|
wallColor?: string;
|
|
trimColor?: string;
|
|
}) {
|
|
const [hovered, setHovered] = useState(false);
|
|
const highlight = active || hovered;
|
|
const openingW = DOOR_WIDTH;
|
|
const openingH = DOOR_HEIGHT;
|
|
const jamb = 0.12;
|
|
const faceZ = 0.04;
|
|
|
|
const handleActivate = (e: THREE.Event & { stopPropagation: () => void }) => {
|
|
e.stopPropagation();
|
|
onActivate();
|
|
};
|
|
|
|
return (
|
|
<group position={position}>
|
|
<pointLight position={[0, openingH * 0.5, 0.3]} intensity={highlight ? 0.9 : 0.5} distance={5} color="#fff4e8" />
|
|
|
|
{/* Depth beyond passage */}
|
|
<mesh position={[0, openingH * 0.5, 0.15]}>
|
|
<planeGeometry args={[openingW * 0.9, openingH * 0.95]} />
|
|
<meshStandardMaterial color="#fff8f0" emissive="#ffe8c8" emissiveIntensity={highlight ? 0.28 : 0.14} />
|
|
</mesh>
|
|
|
|
{/* Side jambs */}
|
|
{([-1, 1] as const).map((sign) => (
|
|
<mesh key={sign} position={[sign * (openingW / 2 + jamb / 2), openingH / 2, faceZ]}>
|
|
<boxGeometry args={[jamb, openingH + 0.1, 0.1]} />
|
|
<meshStandardMaterial color={trimColor} roughness={0.45} metalness={0.25} />
|
|
</mesh>
|
|
))}
|
|
|
|
{/* Arch header */}
|
|
<mesh position={[0, openingH + 0.06, faceZ]}>
|
|
<boxGeometry args={[openingW + jamb * 2, 0.14, 0.1]} />
|
|
<meshStandardMaterial color={trimColor} roughness={0.4} metalness={0.3} />
|
|
</mesh>
|
|
|
|
<mesh position={[0, openingH + 0.22, faceZ]} rotation={[0, 0, Math.PI]}>
|
|
<sphereGeometry args={[openingW / 2 + 0.04, 16, 8, 0, Math.PI * 2, 0, Math.PI / 2]} />
|
|
<meshStandardMaterial color={wallColor} roughness={0.88} />
|
|
</mesh>
|
|
|
|
<group position={[0, openingH + 0.38, faceZ - 0.01]}>
|
|
<mesh onClick={handleActivate} onPointerOver={() => setHovered(true)} onPointerOut={() => setHovered(false)}>
|
|
<boxGeometry args={[1.4, 0.18, 0.02]} />
|
|
<meshStandardMaterial
|
|
color={highlight ? '#d4af37' : trimColor}
|
|
roughness={0.3}
|
|
metalness={0.5}
|
|
emissive={highlight ? '#5a4010' : '#000000'}
|
|
emissiveIntensity={highlight ? 0.2 : 0}
|
|
/>
|
|
</mesh>
|
|
<Text
|
|
position={[0, 0, -0.012]}
|
|
rotation={[0, Math.PI, 0]}
|
|
fontSize={0.1}
|
|
color={highlight ? '#fff8e8' : '#4a3020'}
|
|
anchorX="center"
|
|
anchorY="middle"
|
|
onClick={handleActivate}
|
|
onPointerOver={() => setHovered(true)}
|
|
onPointerOut={() => setHovered(false)}
|
|
>
|
|
{label.toUpperCase()}
|
|
</Text>
|
|
</group>
|
|
|
|
<mesh
|
|
position={[0, openingH / 2, faceZ - 0.12]}
|
|
rotation={[0, Math.PI, 0]}
|
|
onClick={handleActivate}
|
|
onPointerOver={() => setHovered(true)}
|
|
onPointerOut={() => setHovered(false)}
|
|
>
|
|
<planeGeometry args={[openingW * 1.1, openingH * 1.05]} />
|
|
<meshBasicMaterial transparent opacity={0} depthWrite={false} side={THREE.DoubleSide} />
|
|
</mesh>
|
|
</group>
|
|
);
|
|
}
|
|
|
|
export { DOOR_WIDTH, DOOR_HEIGHT, WALL_THICKNESS };
|