Fix movement-hall black walls and column clipping.

Cap shared hall lights (no per-painting spots) so MeshStandard walls stay within WebGL limits, tuck classical/neoclassical details into engaged corner pilasters, and document the light budget.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-07-31 00:26:14 +03:00
co-authored by Cursor
parent 710d262516
commit 1853222e01
9 changed files with 159 additions and 97 deletions
+66 -46
View File
@@ -844,25 +844,11 @@ function InfluencePictureLamp({
<meshStandardMaterial
color="#fff8e0"
emissive="#ffcc55"
emissiveIntensity={1.4 * glow}
emissiveIntensity={1.8 * glow}
toneMapped={false}
/>
</mesh>
<pointLight
position={[0, -0.03, 0.05]}
intensity={1.8 * glow}
distance={4}
color="#ffe4a8"
decay={2}
/>
<spotLight
position={[0, -0.03, 0.05]}
angle={0.75}
penumbra={0.95}
intensity={2.2 * glow}
distance={5}
color="#fff0c8"
/>
{/* No per-painting lights — too many MeshStandard lights break hall shaders. */}
</group>
</group>
);
@@ -970,17 +956,12 @@ function PaintingFrame({
const captionY =
-height / 2 - matBorder - rail - (hasCuratorNotes ? 0.18 : 0.1);
// Hall lighting is shared (track/ambient). Per-frame spotLights (× dozens of
// paintings) exceed WebGL light limits and make MeshStandard walls vanish.
const frameEmissiveBoost = hovered ? 0.35 : showCanvas ? 0.08 : 0.12;
return (
<group position={position} rotation={[0, rotationY, 0]}>
<spotLight
position={[0, height / 2 + 0.25, 0.3]}
angle={0.5}
penumbra={0.75}
intensity={showCanvas ? (hovered ? 0.9 : 0.55) : hovered ? 2.6 : 1.9}
distance={4.5}
color={showCanvas ? '#e8dcc8' : '#fff8ee'}
/>
<mesh
position={[0, 0, frameDepth / 2]}
renderOrder={1}
@@ -996,8 +977,8 @@ function PaintingFrame({
color={finish.color}
roughness={finish.roughness}
metalness={finish.metalness}
emissive={finish.emissive}
emissiveIntensity={finish.emissiveIntensity}
emissive={reviewed ? finish.emissive : '#d8c090'}
emissiveIntensity={finish.emissiveIntensity + frameEmissiveBoost}
/>
</mesh>
@@ -1441,7 +1422,7 @@ function GalleryFloor({
normalScale={new THREE.Vector2(texture.normalScale, texture.normalScale)}
roughness={texture.roughness}
metalness={texture.metalness}
envMapIntensity={0.65}
envMapIntensity={0.3}
color={interiorStyle.tints.floor}
/>
</mesh>
@@ -1661,18 +1642,21 @@ function ArtistHall({
)
) : null}
{interiorStyle && computedWindows && computedWindows.length > 0 && (
{interiorStyle && (
<>
<GalleryWindows
windows={computedWindows}
halfW={halfW}
halfD={halfD}
trimColor={interiorStyle.tints.trim}
/>
{computedWindows && computedWindows.length > 0 && (
<GalleryWindows
windows={computedWindows}
halfW={halfW}
halfD={halfD}
trimColor={interiorStyle.tints.trim}
/>
)}
{/* Always light the hall — do not gate track lights on window gaps. */}
<GalleryTrackLights
width={width}
depth={depth}
intensity={interiorStyle.trackLights}
intensity={Math.max(1.8, interiorStyle.trackLights * 2.8) * Math.PI}
color={interiorStyle.warmLight}
/>
</>
@@ -1750,15 +1734,24 @@ function ArtistHall({
</group>
))}
<pointLight position={[0, WALL_HEIGHT - 0.4, 0]} intensity={interiorStyle ? interiorStyle.ambient * 1.2 : 0.5} color={warmLight} distance={width + depth} />
<pointLight position={[0, WALL_HEIGHT - 0.4, -halfD / 2]} intensity={interiorStyle ? interiorStyle.ambient * 0.85 : 0.35} color={warmLight} distance={Math.max(12, depth * 0.75)} />
{depth > 14 && (
<pointLight position={[0, WALL_HEIGHT - 0.4, -halfD + 1.2]} intensity={interiorStyle ? interiorStyle.ambient * 0.7 : 0.3} color={warmLight} distance={14} />
)}
<pointLight
position={[0, WALL_HEIGHT - 0.4, 0]}
intensity={(interiorStyle ? Math.max(1.0, interiorStyle.ambient * 1.8) : 0.7) * Math.PI}
distance={width + depth}
decay={0}
color={warmLight}
/>
<pointLight
position={[0, WALL_HEIGHT - 0.4, -halfD / 2]}
intensity={(interiorStyle ? Math.max(0.7, interiorStyle.ambient * 1.2) : 0.5) * Math.PI}
distance={Math.max(12, depth * 0.75)}
decay={0}
color={warmLight}
/>
{interiorStyle && (
<directionalLight
position={[0, 6, -halfD - 2]}
intensity={interiorStyle.ambient * 0.9}
intensity={Math.max(0.55, interiorStyle.ambient * 0.85) * Math.PI}
color={interiorStyle.sunLight}
/>
)}
@@ -2043,6 +2036,10 @@ export default function VirtualGallery(props: Props) {
const handleCanvasCreated = useCallback((state: { gl: THREE.WebGLRenderer }) => {
const canvas = state.gl.domElement;
// Physically correct lights (three r155+) need higher exposure so stone/dark
// period halls stay readable without HDR IBL.
state.gl.toneMapping = THREE.ACESFilmicToneMapping;
state.gl.toneMappingExposure = 1.25;
// A freshly created canvas has a healthy context, so clear any lingering
// "restoring" state from a previous loss/remount.
setGlLost(false);
@@ -2432,7 +2429,9 @@ export default function VirtualGallery(props: Props) {
const sceneBackground = interiorStyle?.background ?? '#0d0906';
const sceneFog = interiorStyle?.fog ?? '#0d0906';
const ambientIntensity = interiorStyle?.ambient ?? 0.42;
// Floor ambient so dark period styles (Gothic, Byzantine) stay readable without HDR IBL.
// three r155+ physical lights need ~π× legacy intensity for similar brightness.
const ambientIntensity = Math.max(0.85, interiorStyle?.ambient ?? 0.55) * Math.PI;
const handleCanvasPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
if (!active || showExitNav || e.button !== 0) return;
@@ -2539,13 +2538,34 @@ export default function VirtualGallery(props: Props) {
>
<FrameloopSync active={active} />
<color attach="background" args={[sceneBackground]} />
<fog attach="fog" args={[sceneFog, 18, fogFar]} />
<fog
attach="fog"
args={[sceneFog, Math.max(45, layout.depth * 1.15), Math.max(fogFar, layout.depth + 55)]}
/>
<ambientLight intensity={ambientIntensity} />
<hemisphereLight
color={interiorStyle?.warmLight ?? '#fff8f0'}
groundColor={sceneFog}
intensity={0.55 * Math.PI}
/>
<directionalLight
position={interiorStyle ? [2, 10, -4] : [3, 9, 4]}
intensity={interiorStyle ? 0.85 : 0.65}
intensity={(interiorStyle ? 1.4 : 0.9) * Math.PI}
color={interiorStyle?.sunLight ?? '#fff8f0'}
/>
<directionalLight
position={[-4, 6, 5]}
intensity={0.55 * Math.PI}
color={interiorStyle?.warmLight ?? '#fff5e8'}
/>
{/* Guaranteed fill so walls never disappear if HDR Environment fails. */}
<pointLight
position={[0, 3.2, 0]}
intensity={2.2 * Math.PI}
distance={40}
decay={0}
color={interiorStyle?.warmLight ?? '#fff5e8'}
/>
<SceneErrorBoundary
key={`env-${hallKey}-${glEpoch}`}
fallback={null}
@@ -2553,7 +2573,7 @@ export default function VirtualGallery(props: Props) {
<Suspense fallback={null}>
<Environment
preset={interiorStyle?.details === 'modern' || interiorStyle?.details === 'industrial' ? 'city' : 'warehouse'}
environmentIntensity={interiorStyle ? 0.35 : 0.15}
environmentIntensity={interiorStyle ? 0.55 : 0.25}
/>
</Suspense>
</SceneErrorBoundary>