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:
co-authored by
Cursor
parent
710d262516
commit
1853222e01
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user