Rework Byzantine hall as a late-antique basilica interior.
Replace the flat gold-mosaic chapel with marble revetment walls, a Cosmatesque stone floor, and a coffered timber ceiling, matching Santa Sabina / San Vitale reference interiors. New procedural surfaces in galleryProceduralTextures.ts: - marble-revetment: book-matched veined panels in moulded travertine surrounds (each panel mirrored about its centre) - marble-opus-sectile: cut-stone bays with diagonal diamond inlays and corner triangles in porphyry, verd-antique, giallo, and grey - coffered-wood: recessed walnut coffers with lit beam chamfers Add a byzantine detail set to MovementHallDetails.tsx: engaged porphyry colonnettes with white-marble basket capitals and impost blocks carrying the ceiling, a marble revetment dado, and hanging brass polycandela. Colonnettes stay flush in the corner pockets so they do not clip the ~0.7m frame hang margin. Basilicas are lit by shafts from high windows rather than evenly flooded, but the shared hall lights carry a brightness floor that prevented dimming. Add an opt-in lightScale to the interior style that scales the scene, hall, track, and HDR environment lights. It defaults to 1, so all other halls are unchanged; only Byzantine opts down. Window fill lights are left unscaled so the daylight shafts still read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1853222e01
commit
1cb9eef935
5 files changed
+400
-37
No files matched your search
@@ -139,6 +139,106 @@ function MedievalDetails({ halfW, halfD }: Pick<Props, 'halfW' | 'halfD'>) {
|
||||
);
|
||||
}
|
||||
|
||||
function ByzantineDetails({ halfW, halfD, trim }: Pick<Props, 'halfW' | 'halfD'> & { trim: string }) {
|
||||
// Engaged porphyry colonnettes with Byzantine basket/impost capitals at the
|
||||
// corners (flush — never proud of the ~0.7m frame hang margin), a marble
|
||||
// revetment dado at spring-line height, and hanging brass polycandela in
|
||||
// place of wall torches (a Byzantine basilica hangs its light, not sconces it).
|
||||
const colonnettes = useMemo(
|
||||
() =>
|
||||
[
|
||||
[-halfW + 0.08, -halfD + 0.08],
|
||||
[halfW - 0.08, -halfD + 0.08],
|
||||
[-halfW + 0.08, halfD - 0.08],
|
||||
[halfW - 0.08, halfD - 0.08],
|
||||
] as [number, number][],
|
||||
[halfW, halfD]
|
||||
);
|
||||
|
||||
const lampPositions = useMemo(
|
||||
() =>
|
||||
[
|
||||
[0, -halfD * 0.32],
|
||||
[0, halfD * 0.18],
|
||||
] as [number, number][],
|
||||
[halfD]
|
||||
);
|
||||
|
||||
const dadoW = Math.max(0, halfW * 2 - 0.3);
|
||||
const dadoD = Math.max(0, halfD * 2 - 0.3);
|
||||
|
||||
return (
|
||||
<group>
|
||||
{/* Marble revetment dado — imperial banding course at chair-rail height */}
|
||||
{[
|
||||
[0, -halfD + 0.07, dadoW, 0.1] as const,
|
||||
[0, halfD - 0.07, dadoW, 0.1] as const,
|
||||
[-halfW + 0.07, 0, 0.1, dadoD] as const,
|
||||
[halfW - 0.07, 0, 0.1, dadoD] as const,
|
||||
].map(([x, z, w, d], i) => (
|
||||
<group key={i}>
|
||||
<mesh position={[x, 0.62, z]}>
|
||||
<boxGeometry args={[w, 1.24, d]} />
|
||||
<meshStandardMaterial color="#6d5644" roughness={0.34} metalness={0.12} />
|
||||
</mesh>
|
||||
<mesh position={[x, 1.28, z]}>
|
||||
<boxGeometry args={[w * 1.004, 0.09, d * 1.004]} />
|
||||
<meshStandardMaterial color="#d8c8a8" roughness={0.42} metalness={0.1} />
|
||||
</mesh>
|
||||
</group>
|
||||
))}
|
||||
|
||||
{/* Engaged colonnettes, basket capital, and impost block */}
|
||||
{colonnettes.map(([x, z], i) => (
|
||||
<group key={i} position={[x, 0, z]}>
|
||||
{/* Polished porphyry shaft — the imperial stone of the reference basilicas */}
|
||||
<mesh position={[0, 1.95, 0]}>
|
||||
<cylinderGeometry args={[0.14, 0.16, 3.4, 14]} />
|
||||
<meshStandardMaterial color="#43222a" roughness={0.32} metalness={0.16} />
|
||||
</mesh>
|
||||
<mesh position={[0, 0.12, 0]}>
|
||||
<cylinderGeometry args={[0.17, 0.19, 0.24, 14]} />
|
||||
<meshStandardMaterial color="#d8cdb4" roughness={0.5} metalness={0.08} />
|
||||
</mesh>
|
||||
{/* Basket capital in white marble, then an impost block carrying the ceiling */}
|
||||
<mesh position={[0, 3.8, 0]}>
|
||||
<cylinderGeometry args={[0.22, 0.11, 0.3, 8]} />
|
||||
<meshStandardMaterial color="#e8e0cc" roughness={0.44} metalness={0.06} />
|
||||
</mesh>
|
||||
<mesh position={[0, 4.05, 0]}>
|
||||
<boxGeometry args={[0.28, 0.22, 0.28]} />
|
||||
<meshStandardMaterial color="#ded4bc" roughness={0.5} metalness={0.05} />
|
||||
</mesh>
|
||||
</group>
|
||||
))}
|
||||
|
||||
{/* Hanging polycandela — brass ring lamps suspended from the vault */}
|
||||
{lampPositions.map(([x, z], i) => (
|
||||
<group key={i} position={[x, 0, z]}>
|
||||
<mesh position={[0, 4.1, 0]}>
|
||||
<cylinderGeometry args={[0.012, 0.012, 0.5, 6]} />
|
||||
<meshStandardMaterial color="#5a4a20" roughness={0.4} metalness={0.6} />
|
||||
</mesh>
|
||||
<mesh position={[0, 3.84, 0]} rotation={[Math.PI / 2, 0, 0]}>
|
||||
<torusGeometry args={[0.32, 0.025, 8, 20]} />
|
||||
<meshStandardMaterial color={trim} roughness={0.25} metalness={0.8} emissive="#3a2808" emissiveIntensity={0.1} />
|
||||
</mesh>
|
||||
{Array.from({ length: 6 }, (_, j) => {
|
||||
const a = (j / 6) * Math.PI * 2;
|
||||
return (
|
||||
<mesh key={j} position={[Math.cos(a) * 0.32, 3.76, Math.sin(a) * 0.32]}>
|
||||
<sphereGeometry args={[0.045, 8, 8]} />
|
||||
<meshStandardMaterial color="#ffcf80" emissive="#ff9830" emissiveIntensity={0.9} toneMapped={false} />
|
||||
</mesh>
|
||||
);
|
||||
})}
|
||||
<pointLight position={[0, 3.76, 0]} intensity={0.85} distance={6} color="#ffb060" />
|
||||
</group>
|
||||
))}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
function NeoclassicalDetails({ halfW, halfD, trim }: Pick<Props, 'halfW' | 'halfD'> & { trim: string }) {
|
||||
// Shallow engaged corner pilasters only. Freestanding columns (~0.5m into the
|
||||
// room) overlap the outermost frames — hang margin is only ~0.7m from each end.
|
||||
@@ -226,6 +326,8 @@ export default function MovementHallDetails({ style, width, depth, halfW, halfD
|
||||
return <BaroqueDetails style={style} width={width} depth={depth} halfW={halfW} halfD={halfD} trim={trim} />;
|
||||
case 'medieval':
|
||||
return <MedievalDetails halfW={halfW} halfD={halfD} />;
|
||||
case 'byzantine':
|
||||
return <ByzantineDetails halfW={halfW} halfD={halfD} trim={trim} />;
|
||||
case 'neoclassical':
|
||||
return <NeoclassicalDetails halfW={halfW} halfD={halfD} trim={trim} />;
|
||||
case 'classical':
|
||||
|
||||
@@ -1484,6 +1484,7 @@ function ArtistHall({
|
||||
);
|
||||
const titleColor = interiorStyle?.titleColor ?? '#4a3020';
|
||||
const warmLight = interiorStyle?.warmLight ?? '#fff5e8';
|
||||
const hallLightScale = interiorStyle?.lightScale ?? 1;
|
||||
const wallRoughness = interiorStyle ? 0.75 : 0.92;
|
||||
const wallMetalness = interiorStyle ? 0.08 : 0.06;
|
||||
|
||||
@@ -1656,7 +1657,7 @@ function ArtistHall({
|
||||
<GalleryTrackLights
|
||||
width={width}
|
||||
depth={depth}
|
||||
intensity={Math.max(1.8, interiorStyle.trackLights * 2.8) * Math.PI}
|
||||
intensity={Math.max(1.8, interiorStyle.trackLights * 2.8) * Math.PI * interiorStyle.lightScale}
|
||||
color={interiorStyle.warmLight}
|
||||
/>
|
||||
</>
|
||||
@@ -1736,14 +1737,14 @@ function ArtistHall({
|
||||
|
||||
<pointLight
|
||||
position={[0, WALL_HEIGHT - 0.4, 0]}
|
||||
intensity={(interiorStyle ? Math.max(1.0, interiorStyle.ambient * 1.8) : 0.7) * Math.PI}
|
||||
intensity={(interiorStyle ? Math.max(1.0, interiorStyle.ambient * 1.8) : 0.7) * Math.PI * hallLightScale}
|
||||
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}
|
||||
intensity={(interiorStyle ? Math.max(0.7, interiorStyle.ambient * 1.2) : 0.5) * Math.PI * hallLightScale}
|
||||
distance={Math.max(12, depth * 0.75)}
|
||||
decay={0}
|
||||
color={warmLight}
|
||||
@@ -1751,7 +1752,7 @@ function ArtistHall({
|
||||
{interiorStyle && (
|
||||
<directionalLight
|
||||
position={[0, 6, -halfD - 2]}
|
||||
intensity={Math.max(0.55, interiorStyle.ambient * 0.85) * Math.PI}
|
||||
intensity={Math.max(0.55, interiorStyle.ambient * 0.85) * Math.PI * hallLightScale}
|
||||
color={interiorStyle.sunLight}
|
||||
/>
|
||||
)}
|
||||
@@ -2431,7 +2432,8 @@ export default function VirtualGallery(props: Props) {
|
||||
const sceneFog = interiorStyle?.fog ?? '#0d0906';
|
||||
// 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 lightScale = interiorStyle?.lightScale ?? 1;
|
||||
const ambientIntensity = Math.max(0.85, interiorStyle?.ambient ?? 0.55) * Math.PI * lightScale;
|
||||
|
||||
const handleCanvasPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!active || showExitNav || e.button !== 0) return;
|
||||
@@ -2546,22 +2548,22 @@ export default function VirtualGallery(props: Props) {
|
||||
<hemisphereLight
|
||||
color={interiorStyle?.warmLight ?? '#fff8f0'}
|
||||
groundColor={sceneFog}
|
||||
intensity={0.55 * Math.PI}
|
||||
intensity={0.55 * Math.PI * lightScale}
|
||||
/>
|
||||
<directionalLight
|
||||
position={interiorStyle ? [2, 10, -4] : [3, 9, 4]}
|
||||
intensity={(interiorStyle ? 1.4 : 0.9) * Math.PI}
|
||||
intensity={(interiorStyle ? 1.4 : 0.9) * Math.PI * lightScale}
|
||||
color={interiorStyle?.sunLight ?? '#fff8f0'}
|
||||
/>
|
||||
<directionalLight
|
||||
position={[-4, 6, 5]}
|
||||
intensity={0.55 * Math.PI}
|
||||
intensity={0.55 * Math.PI * lightScale}
|
||||
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}
|
||||
intensity={2.2 * Math.PI * lightScale}
|
||||
distance={40}
|
||||
decay={0}
|
||||
color={interiorStyle?.warmLight ?? '#fff5e8'}
|
||||
@@ -2573,7 +2575,7 @@ export default function VirtualGallery(props: Props) {
|
||||
<Suspense fallback={null}>
|
||||
<Environment
|
||||
preset={interiorStyle?.details === 'modern' || interiorStyle?.details === 'industrial' ? 'city' : 'warehouse'}
|
||||
environmentIntensity={interiorStyle ? 0.55 : 0.25}
|
||||
environmentIntensity={(interiorStyle ? 0.55 : 0.25) * lightScale}
|
||||
/>
|
||||
</Suspense>
|
||||
</SceneErrorBoundary>
|
||||
|
||||
Reference in new issue
Block a user