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:
co-authored by
Claude Opus 5
parent
1853222e01
commit
1cb9eef935
@@ -335,12 +335,12 @@ Enter from the home page by clicking a **movement name** on the movement flow. F
|
|||||||
| Paintings on walls | **Left, end, and right** — single-wing halls keep the far wall solid (full span); multi-wing halls hang end-wall works on panels beside the back exit |
|
| Paintings on walls | **Left, end, and right** — single-wing halls keep the far wall solid (full span); multi-wing halls hang end-wall works on panels beside the back exit |
|
||||||
| Wall order | Same U-shaped hang as artist halls (left → end → right) |
|
| Wall order | Same U-shaped hang as artist halls (left → end → right) |
|
||||||
| Frame captions | **Year · artist** label below each frame |
|
| Frame captions | **Year · artist** label below each frame |
|
||||||
| Period interior | Each of the 26 seeded movements maps to a unique style in `movement-interior-styles.ts` (Italian palazzo, Baroque palace, NYC loft, white cube, etc.). Style keys use a legacy id map (`DB_ID_TO_STYLE_KEY`: gallery ids **1–26** → authored keys **27–52**, with Northern/High Renaissance swapped) so Byzantine gets the chapel, not Gothic stone |
|
| Period interior | Each of the 26 seeded movements maps to a unique style in `movement-interior-styles.ts` (Italian palazzo, Baroque palace, Byzantine basilica, NYC loft, white cube, etc.). Style keys use a legacy id map (`DB_ID_TO_STYLE_KEY`: gallery ids **1–26** → authored keys **27–52**, with Northern/High Renaissance swapped) so Byzantine gets the basilica, not Gothic stone |
|
||||||
| Wall materials | Hi-res **procedural textures** with normal maps (`galleryProceduralTextures.ts`): real-world stone, marble, wood panelling, brick, velvet, stucco — plus **single-colour painted walls** (`painted-lime`, `painted-oil-matte`, `painted-oil-satin`, `painted-emulsion`, `painted-flat`) tinted per movement for Renaissance salons through modern white cubes |
|
| Wall materials | Hi-res **procedural textures** with normal maps (`galleryProceduralTextures.ts`): real-world stone, marble, wood panelling, brick, velvet, stucco, **marble revetment** (framed book-matched panels), **Cosmatesque paving** (`marble-opus-sectile`), **coffered timber** ceilings — plus **single-colour painted walls** (`painted-lime`, `painted-oil-matte`, `painted-oil-satin`, `painted-emulsion`, `painted-flat`) tinted per movement for Renaissance salons through modern white cubes |
|
||||||
| Textures | Wall/floor/ceiling maps applied via `useTexturedMaterial`; movement **tints** drive painted-wall hue |
|
| Textures | Wall/floor/ceiling maps applied via `useTexturedMaterial`; movement **tints** drive painted-wall hue |
|
||||||
| Windows | **Side walls only** — placed in gaps between frames when possible; if a wall is packed, high **clerestory** windows are still added so the hall keeps daylight (`computeSideWallWindows`) |
|
| Windows | **Side walls only** — placed in gaps between frames when possible; if a wall is packed, high **clerestory** windows are still added so the hall keeps daylight (`computeSideWallWindows`) |
|
||||||
| Lighting | Shared hall lights only (ambient / hemisphere / directional + capped ceiling track spots + one fill per window). See **light budget** below |
|
| Lighting | Shared hall lights only (ambient / hemisphere / directional + capped ceiling track spots + one fill per window). See **light budget** below |
|
||||||
| Period details | `MovementHallDetails.tsx` — classical / neoclassical use **shallow engaged corner pilasters** (never freestanding mid-hall or proud corner shafts that cover frames) |
|
| Period details | `MovementHallDetails.tsx` — classical / neoclassical use **shallow engaged corner pilasters** (never freestanding mid-hall or proud corner shafts that cover frames); `byzantine` adds engaged porphyry colonnettes with basket capitals, a marble revetment dado, and hanging brass polycandela |
|
||||||
| Back wall | Single wing: solid display wall. Multi-wing: **Exit double doors** → **Wing navigator** or **Exit to Timeline** |
|
| Back wall | Single wing: solid display wall. Multi-wing: **Exit double doors** → **Wing navigator** or **Exit to Timeline** |
|
||||||
| Front / entrance wall | Single wing: **Exit double doors** (leave the way you entered). Multi-wing: **“Next wing →”** archway when a later wing exists |
|
| Front / entrance wall | Single wing: **Exit double doors** (leave the way you entered). Multi-wing: **“Next wing →”** archway when a later wing exists |
|
||||||
| Influence lamps | Same golden upside-down emissive fixtures as artist halls when `has_influence_links` is true |
|
| Influence lamps | Same golden upside-down emissive fixtures as artist halls when `has_influence_links` is true |
|
||||||
@@ -376,6 +376,8 @@ Full guide: [tours.md](tours.md).
|
|||||||
|
|
||||||
**Light budget (critical):** Three.js forward shading cannot compile `MeshStandardMaterial` with dozens of dynamic lights. Large wings used to add **one `spotLight` per painting** (plus influence lamps), which pushed the scene to ~100–200 lights — walls, floor, and ceiling then rendered as a black void while painting canvases (`MeshBasicMaterial`) still showed. Halls therefore use **shared lighting only**: ambient / hemisphere / directional fill, a small set of ceiling track spots (`GalleryTrackLights`, capped), and **one** window spot each. Painting canvases stay unlit (`MeshBasicMaterial`); frames and influence fixtures use **emissive** accents instead of per-frame lights. Intensities are scaled for three.js physical lights (post-r155).
|
**Light budget (critical):** Three.js forward shading cannot compile `MeshStandardMaterial` with dozens of dynamic lights. Large wings used to add **one `spotLight` per painting** (plus influence lamps), which pushed the scene to ~100–200 lights — walls, floor, and ceiling then rendered as a black void while painting canvases (`MeshBasicMaterial`) still showed. Halls therefore use **shared lighting only**: ambient / hemisphere / directional fill, a small set of ceiling track spots (`GalleryTrackLights`, capped), and **one** window spot each. Painting canvases stay unlit (`MeshBasicMaterial`); frames and influence fixtures use **emissive** accents instead of per-frame lights. Intensities are scaled for three.js physical lights (post-r155).
|
||||||
|
|
||||||
|
Shared lights carry a **brightness floor** so dark period styles never render as a black void. A style that is *meant* to be dim (a basilica lit by window shafts, not flooded) sets **`lightScale`** in `movement-interior-styles.ts` — it multiplies the scene ambient/hemisphere/directional/fill lights, the hall point lights, the ceiling track spots, and the HDR `environmentIntensity`. It defaults to **1**, so only styles that opt in are affected. Window fill lights are deliberately **not** scaled, so the daylight shafts still read against the darker room.
|
||||||
|
|
||||||
**Period architecture:** Freestanding columns must not sit in the walkway or in the corner pocket in front of frames (hang margin is only ~0.7 m from each wall end). Classical / neoclassical details use **engaged corner pilasters** flush to the walls (`MovementHallDetails.tsx`).
|
**Period architecture:** Freestanding columns must not sit in the walkway or in the corner pocket in front of frames (hang margin is only ~0.7 m from each wall end). Classical / neoclassical details use **engaged corner pilasters** flush to the walls (`MovementHallDetails.tsx`).
|
||||||
|
|
||||||
**Surface textures:** Procedural colour maps use sRGB; **normal maps use `NoColorSpace`** (`galleryProceduralTextures.ts`). Wrong colour space on normals breaks wall shading.
|
**Surface textures:** Procedural colour maps use sRGB; **normal maps use `NoColorSpace`** (`galleryProceduralTextures.ts`). Wrong colour space on normals breaks wall shading.
|
||||||
|
|||||||
@@ -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 }) {
|
function NeoclassicalDetails({ halfW, halfD, trim }: Pick<Props, 'halfW' | 'halfD'> & { trim: string }) {
|
||||||
// Shallow engaged corner pilasters only. Freestanding columns (~0.5m into the
|
// 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.
|
// 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} />;
|
return <BaroqueDetails style={style} width={width} depth={depth} halfW={halfW} halfD={halfD} trim={trim} />;
|
||||||
case 'medieval':
|
case 'medieval':
|
||||||
return <MedievalDetails halfW={halfW} halfD={halfD} />;
|
return <MedievalDetails halfW={halfW} halfD={halfD} />;
|
||||||
|
case 'byzantine':
|
||||||
|
return <ByzantineDetails halfW={halfW} halfD={halfD} trim={trim} />;
|
||||||
case 'neoclassical':
|
case 'neoclassical':
|
||||||
return <NeoclassicalDetails halfW={halfW} halfD={halfD} trim={trim} />;
|
return <NeoclassicalDetails halfW={halfW} halfD={halfD} trim={trim} />;
|
||||||
case 'classical':
|
case 'classical':
|
||||||
|
|||||||
@@ -1484,6 +1484,7 @@ function ArtistHall({
|
|||||||
);
|
);
|
||||||
const titleColor = interiorStyle?.titleColor ?? '#4a3020';
|
const titleColor = interiorStyle?.titleColor ?? '#4a3020';
|
||||||
const warmLight = interiorStyle?.warmLight ?? '#fff5e8';
|
const warmLight = interiorStyle?.warmLight ?? '#fff5e8';
|
||||||
|
const hallLightScale = interiorStyle?.lightScale ?? 1;
|
||||||
const wallRoughness = interiorStyle ? 0.75 : 0.92;
|
const wallRoughness = interiorStyle ? 0.75 : 0.92;
|
||||||
const wallMetalness = interiorStyle ? 0.08 : 0.06;
|
const wallMetalness = interiorStyle ? 0.08 : 0.06;
|
||||||
|
|
||||||
@@ -1656,7 +1657,7 @@ function ArtistHall({
|
|||||||
<GalleryTrackLights
|
<GalleryTrackLights
|
||||||
width={width}
|
width={width}
|
||||||
depth={depth}
|
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}
|
color={interiorStyle.warmLight}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -1736,14 +1737,14 @@ function ArtistHall({
|
|||||||
|
|
||||||
<pointLight
|
<pointLight
|
||||||
position={[0, WALL_HEIGHT - 0.4, 0]}
|
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}
|
distance={width + depth}
|
||||||
decay={0}
|
decay={0}
|
||||||
color={warmLight}
|
color={warmLight}
|
||||||
/>
|
/>
|
||||||
<pointLight
|
<pointLight
|
||||||
position={[0, WALL_HEIGHT - 0.4, -halfD / 2]}
|
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)}
|
distance={Math.max(12, depth * 0.75)}
|
||||||
decay={0}
|
decay={0}
|
||||||
color={warmLight}
|
color={warmLight}
|
||||||
@@ -1751,7 +1752,7 @@ function ArtistHall({
|
|||||||
{interiorStyle && (
|
{interiorStyle && (
|
||||||
<directionalLight
|
<directionalLight
|
||||||
position={[0, 6, -halfD - 2]}
|
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}
|
color={interiorStyle.sunLight}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -2431,7 +2432,8 @@ export default function VirtualGallery(props: Props) {
|
|||||||
const sceneFog = interiorStyle?.fog ?? '#0d0906';
|
const sceneFog = interiorStyle?.fog ?? '#0d0906';
|
||||||
// Floor ambient so dark period styles (Gothic, Byzantine) stay readable without HDR IBL.
|
// Floor ambient so dark period styles (Gothic, Byzantine) stay readable without HDR IBL.
|
||||||
// three r155+ physical lights need ~π× legacy intensity for similar brightness.
|
// 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>) => {
|
const handleCanvasPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
|
||||||
if (!active || showExitNav || e.button !== 0) return;
|
if (!active || showExitNav || e.button !== 0) return;
|
||||||
@@ -2546,22 +2548,22 @@ export default function VirtualGallery(props: Props) {
|
|||||||
<hemisphereLight
|
<hemisphereLight
|
||||||
color={interiorStyle?.warmLight ?? '#fff8f0'}
|
color={interiorStyle?.warmLight ?? '#fff8f0'}
|
||||||
groundColor={sceneFog}
|
groundColor={sceneFog}
|
||||||
intensity={0.55 * Math.PI}
|
intensity={0.55 * Math.PI * lightScale}
|
||||||
/>
|
/>
|
||||||
<directionalLight
|
<directionalLight
|
||||||
position={interiorStyle ? [2, 10, -4] : [3, 9, 4]}
|
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'}
|
color={interiorStyle?.sunLight ?? '#fff8f0'}
|
||||||
/>
|
/>
|
||||||
<directionalLight
|
<directionalLight
|
||||||
position={[-4, 6, 5]}
|
position={[-4, 6, 5]}
|
||||||
intensity={0.55 * Math.PI}
|
intensity={0.55 * Math.PI * lightScale}
|
||||||
color={interiorStyle?.warmLight ?? '#fff5e8'}
|
color={interiorStyle?.warmLight ?? '#fff5e8'}
|
||||||
/>
|
/>
|
||||||
{/* Guaranteed fill so walls never disappear if HDR Environment fails. */}
|
{/* Guaranteed fill so walls never disappear if HDR Environment fails. */}
|
||||||
<pointLight
|
<pointLight
|
||||||
position={[0, 3.2, 0]}
|
position={[0, 3.2, 0]}
|
||||||
intensity={2.2 * Math.PI}
|
intensity={2.2 * Math.PI * lightScale}
|
||||||
distance={40}
|
distance={40}
|
||||||
decay={0}
|
decay={0}
|
||||||
color={interiorStyle?.warmLight ?? '#fff5e8'}
|
color={interiorStyle?.warmLight ?? '#fff5e8'}
|
||||||
@@ -2573,7 +2575,7 @@ export default function VirtualGallery(props: Props) {
|
|||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Environment
|
<Environment
|
||||||
preset={interiorStyle?.details === 'modern' || interiorStyle?.details === 'industrial' ? 'city' : 'warehouse'}
|
preset={interiorStyle?.details === 'modern' || interiorStyle?.details === 'industrial' ? 'city' : 'warehouse'}
|
||||||
environmentIntensity={interiorStyle ? 0.55 : 0.25}
|
environmentIntensity={(interiorStyle ? 0.55 : 0.25) * lightScale}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</SceneErrorBoundary>
|
</SceneErrorBoundary>
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ export interface MovementInteriorStyle {
|
|||||||
doorWood: [string, string, string];
|
doorWood: [string, string, string];
|
||||||
windows: GalleryWindowSpec[];
|
windows: GalleryWindowSpec[];
|
||||||
trackLights: number;
|
trackLights: number;
|
||||||
details: 'palazzo' | 'baroque' | 'medieval' | 'neoclassical' | 'salon' | 'modern' | 'classical' | 'museum' | 'industrial' | 'atelier';
|
/** Scales the shared hall/scene lights. <1 for deliberately dim period interiors. */
|
||||||
|
lightScale: number;
|
||||||
|
details: 'palazzo' | 'baroque' | 'medieval' | 'byzantine' | 'neoclassical' | 'salon' | 'modern' | 'classical' | 'museum' | 'industrial' | 'atelier';
|
||||||
}
|
}
|
||||||
|
|
||||||
function windows(...specs: GalleryWindowSpec[]): GalleryWindowSpec[] {
|
function windows(...specs: GalleryWindowSpec[]): GalleryWindowSpec[] {
|
||||||
@@ -85,6 +87,7 @@ function mk(
|
|||||||
doorWood: opts.doorWood ?? ['#3d2818', '#4e3624', '#261a10'],
|
doorWood: opts.doorWood ?? ['#3d2818', '#4e3624', '#261a10'],
|
||||||
windows: opts.windows,
|
windows: opts.windows,
|
||||||
trackLights: opts.trackLights ?? 0.8,
|
trackLights: opts.trackLights ?? 0.8,
|
||||||
|
lightScale: opts.lightScale ?? 1,
|
||||||
details: opts.details,
|
details: opts.details,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -114,25 +117,29 @@ const BY_MOVEMENT_ID: Record<number, MovementInteriorStyle> = {
|
|||||||
),
|
),
|
||||||
28: mk(
|
28: mk(
|
||||||
'byzantine-sanctuary',
|
'byzantine-sanctuary',
|
||||||
'Byzantine chapel',
|
'Byzantine basilica',
|
||||||
'Gold mosaic walls · amber light through arched windows',
|
'Marble revetment · coffered timber ceiling · Cosmatesque stone floor',
|
||||||
{ wall: 'mosaic-byzantine', ceiling: 'gilded-stucco', floor: 'marble-checker' },
|
{ wall: 'marble-revetment', ceiling: 'coffered-wood', floor: 'marble-opus-sectile' },
|
||||||
{ wall: '#d4af37', ceiling: '#c8a840', floor: '#ddd8cc', trim: '#b8860b' },
|
{ wall: '#a89880', ceiling: '#6a4526', floor: '#a09079', trim: '#8a6440' },
|
||||||
{
|
{
|
||||||
details: 'medieval',
|
details: 'byzantine',
|
||||||
titleColor: '#f0e8c0',
|
titleColor: '#e8d8b0',
|
||||||
ambient: 0.48,
|
ambient: 0.44,
|
||||||
warmLight: '#ffd898',
|
warmLight: '#ffdca8',
|
||||||
sunLight: '#ffe8b0',
|
sunLight: '#ffdca0',
|
||||||
fog: '#0a0806',
|
fog: '#100b07',
|
||||||
background: '#060504',
|
background: '#0a0705',
|
||||||
windows: windows(
|
windows: windows(
|
||||||
{ wall: 'back', x: 0, y: 2.8, width: 1.0, height: 2.2, style: 'roman-arch', lightColor: '#ffb860', lightIntensity: 2.4 },
|
{ wall: 'back', x: 0, y: 3.1, width: 0.9, height: 1.9, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.4 },
|
||||||
{ wall: 'left', x: -1, y: 2.6, width: 0.7, height: 1.8, style: 'roman-arch', lightColor: '#ffb860', lightIntensity: 1.8 },
|
{ wall: 'left', x: -3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 },
|
||||||
{ wall: 'right', x: 1, y: 2.6, width: 0.7, height: 1.8, style: 'roman-arch', lightColor: '#ffb860', lightIntensity: 1.8 }
|
{ wall: 'left', x: 3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 },
|
||||||
|
{ wall: 'right', x: -3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 },
|
||||||
|
{ wall: 'right', x: 3.5, y: 3.2, width: 0.7, height: 1.6, style: 'roman-arch', lightColor: '#ffd28a', lightIntensity: 3.0 }
|
||||||
),
|
),
|
||||||
trackLights: 0.5,
|
trackLights: 0.35,
|
||||||
doorWood: ['#3a3020', '#4a3830', '#2a2018'],
|
// Basilica interiors are lit by shafts from high windows, not evenly flooded.
|
||||||
|
lightScale: 0.3,
|
||||||
|
doorWood: ['#3a2416', '#4c3220', '#241608'],
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
29: mk(
|
29: mk(
|
||||||
|
|||||||
@@ -8,6 +8,9 @@ export type SurfaceTextureKind =
|
|||||||
| 'marble-veined-carrara'
|
| 'marble-veined-carrara'
|
||||||
| 'marble-veined-emerald'
|
| 'marble-veined-emerald'
|
||||||
| 'marble-checker'
|
| 'marble-checker'
|
||||||
|
| 'marble-opus-sectile'
|
||||||
|
| 'marble-revetment'
|
||||||
|
| 'coffered-wood'
|
||||||
| 'limestone'
|
| 'limestone'
|
||||||
| 'sandstone'
|
| 'sandstone'
|
||||||
| 'rough-stone'
|
| 'rough-stone'
|
||||||
@@ -244,6 +247,43 @@ function marbleVeined(ctx: CanvasRenderingContext2D, size: number, base: string,
|
|||||||
noiseOverlay(ctx, size, 0.035, seed + 1);
|
noiseOverlay(ctx, size, 0.035, seed + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Veined marble confined to one slab rect — for revetment panels and inlay. */
|
||||||
|
function marbleSlab(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
w: number,
|
||||||
|
h: number,
|
||||||
|
base: string,
|
||||||
|
vein: string,
|
||||||
|
seed: number
|
||||||
|
) {
|
||||||
|
const rand = seeded(seed);
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.rect(x, y, w, h);
|
||||||
|
ctx.clip();
|
||||||
|
ctx.fillStyle = base;
|
||||||
|
ctx.fillRect(x, y, w, h);
|
||||||
|
const [vr, vg, vb] = hexToRgb(vein);
|
||||||
|
const strands = 10 + Math.floor(w / 24);
|
||||||
|
for (let i = 0; i < strands; i++) {
|
||||||
|
ctx.strokeStyle = `rgba(${vr},${vg},${vb},${0.1 + rand() * 0.3})`;
|
||||||
|
ctx.lineWidth = 0.6 + rand() * 3.4;
|
||||||
|
ctx.beginPath();
|
||||||
|
let px = x + rand() * w;
|
||||||
|
let py = y + rand() * h;
|
||||||
|
ctx.moveTo(px, py);
|
||||||
|
for (let s = 0; s < 9; s++) {
|
||||||
|
px += (rand() - 0.35) * w * 0.28;
|
||||||
|
py += (rand() - 0.5) * h * 0.22;
|
||||||
|
ctx.quadraticCurveTo(px - w * 0.05, py + h * 0.04, px, py);
|
||||||
|
}
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
function woodPanelSurface(
|
function woodPanelSurface(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
size: number,
|
size: number,
|
||||||
@@ -346,6 +386,161 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData {
|
|||||||
noiseOverlay(ctx, size, 0.03, 103);
|
noiseOverlay(ctx, size, 0.03, 103);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'marble-opus-sectile': {
|
||||||
|
// Cosmatesque paving: cut-stone squares with diagonally-set inlays and
|
||||||
|
// corner triangles in porphyry, verd-antique, and giallo over travertine.
|
||||||
|
const stones = [
|
||||||
|
['#6a3c42', '#4c2830'],
|
||||||
|
['#47554b', '#333f38'],
|
||||||
|
['#8a7752', '#66573c'],
|
||||||
|
['#4e4a45', '#363330'],
|
||||||
|
];
|
||||||
|
fill(ctx, size, '#7d6e58');
|
||||||
|
const grid = 2;
|
||||||
|
const cell = size / grid;
|
||||||
|
for (let row = 0; row < grid; row++) {
|
||||||
|
for (let col = 0; col < grid; col++) {
|
||||||
|
const x = col * cell;
|
||||||
|
const y = row * cell;
|
||||||
|
const rand = seeded(610 + row * grid + col);
|
||||||
|
// Travertine ground slab for this bay
|
||||||
|
marbleSlab(ctx, x, y, cell, cell, '#8f7f66', '#6d5f4c', 620 + row * grid + col);
|
||||||
|
|
||||||
|
const pick = stones[(row * 3 + col * 5 + Math.floor(rand() * 2)) % stones.length];
|
||||||
|
const inset = cell * 0.045;
|
||||||
|
// Corner triangles
|
||||||
|
ctx.fillStyle = pick[1];
|
||||||
|
const corners: [number, number, number, number, number, number][] = [
|
||||||
|
[x + inset, y + inset, x + cell * 0.5, y + inset, x + inset, y + cell * 0.5],
|
||||||
|
[x + cell - inset, y + inset, x + cell * 0.5, y + inset, x + cell - inset, y + cell * 0.5],
|
||||||
|
[x + inset, y + cell - inset, x + cell * 0.5, y + cell - inset, x + inset, y + cell * 0.5],
|
||||||
|
[x + cell - inset, y + cell - inset, x + cell * 0.5, y + cell - inset, x + cell - inset, y + cell * 0.5],
|
||||||
|
];
|
||||||
|
for (const [ax, ay, bx, by, cx2, cy2] of corners) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(ax, ay);
|
||||||
|
ctx.lineTo(bx, by);
|
||||||
|
ctx.lineTo(cx2, cy2);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
// Diamond inlay set on the diagonal
|
||||||
|
const cx = x + cell / 2;
|
||||||
|
const cy = y + cell / 2;
|
||||||
|
const r = cell * 0.34;
|
||||||
|
ctx.save();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(cx, cy - r);
|
||||||
|
ctx.lineTo(cx + r, cy);
|
||||||
|
ctx.lineTo(cx, cy + r);
|
||||||
|
ctx.lineTo(cx - r, cy);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.clip();
|
||||||
|
marbleSlab(ctx, cx - r, cy - r, r * 2, r * 2, pick[0], pick[1], 640 + row * grid + col);
|
||||||
|
ctx.restore();
|
||||||
|
ctx.strokeStyle = 'rgba(40,30,22,0.45)';
|
||||||
|
ctx.lineWidth = 1.4;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(cx, cy - r);
|
||||||
|
ctx.lineTo(cx + r, cy);
|
||||||
|
ctx.lineTo(cx, cy + r);
|
||||||
|
ctx.lineTo(cx - r, cy);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
// Cut joint around the bay
|
||||||
|
ctx.strokeStyle = 'rgba(40,30,22,0.5)';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeRect(x + 1, y + 1, cell - 2, cell - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noiseOverlay(ctx, size, 0.03, 601);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'marble-revetment': {
|
||||||
|
// Basilica wall cladding: book-matched marble panels in carved stone
|
||||||
|
// surrounds — Santa Sabina / San Vitale, no mosaic.
|
||||||
|
fill(ctx, size, '#a8977c');
|
||||||
|
const cols = 2;
|
||||||
|
const rows = 2;
|
||||||
|
const pw = size / cols;
|
||||||
|
const ph = size / rows;
|
||||||
|
const fields = [
|
||||||
|
['#5e6d64', '#33423a'],
|
||||||
|
['#8a7a5e', '#5a4c36'],
|
||||||
|
['#5e6d64', '#33423a'],
|
||||||
|
['#6f5f56', '#453a34'],
|
||||||
|
];
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const x = col * pw;
|
||||||
|
const y = row * ph;
|
||||||
|
// Travertine surround
|
||||||
|
marbleSlab(ctx, x, y, pw, ph, '#b0a084', '#8a7658', 660 + row * cols + col);
|
||||||
|
const m = pw * 0.09;
|
||||||
|
const iw = pw - m * 2;
|
||||||
|
const ih = ph - m * 2;
|
||||||
|
const field = fields[(row * cols + col) % fields.length];
|
||||||
|
// Book-matched pair: the same slab mirrored about the panel centre
|
||||||
|
const half = iw / 2;
|
||||||
|
marbleSlab(ctx, x + m, y + m, half, ih, field[0], field[1], 670 + row * cols + col);
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x + m + iw, y + m);
|
||||||
|
ctx.scale(-1, 1);
|
||||||
|
ctx.drawImage(ctx.canvas, x + m, y + m, half, ih, 0, 0, half, ih);
|
||||||
|
ctx.restore();
|
||||||
|
// Moulded frame around the panel
|
||||||
|
ctx.strokeStyle = 'rgba(60,44,30,0.55)';
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.strokeRect(x + m, y + m, iw, ih);
|
||||||
|
ctx.strokeStyle = 'rgba(255,244,222,0.35)';
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeRect(x + m - 4, y + m - 4, iw + 8, ih + 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Horizontal string course between panel registers
|
||||||
|
ctx.fillStyle = 'rgba(90,68,44,0.35)';
|
||||||
|
ctx.fillRect(0, ph - 3, size, 6);
|
||||||
|
noiseOverlay(ctx, size, 0.04, 680);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'coffered-wood': {
|
||||||
|
// Open-timber coffered basilica ceiling: recessed dark walnut boxes with
|
||||||
|
// lit chamfers so the grid reads as depth, not a flat pattern.
|
||||||
|
fill(ctx, size, '#2e1e12');
|
||||||
|
const n = 6;
|
||||||
|
const cell = size / n;
|
||||||
|
for (let row = 0; row < n; row++) {
|
||||||
|
for (let col = 0; col < n; col++) {
|
||||||
|
const x = col * cell;
|
||||||
|
const y = row * cell;
|
||||||
|
const beam = cell * 0.14;
|
||||||
|
// Beam faces catch light from below
|
||||||
|
ctx.fillStyle = '#6a4a2c';
|
||||||
|
ctx.fillRect(x, y, cell, beam);
|
||||||
|
ctx.fillRect(x, y, beam, cell);
|
||||||
|
ctx.fillStyle = '#4a3018';
|
||||||
|
ctx.fillRect(x + cell - beam, y, beam, cell);
|
||||||
|
ctx.fillRect(x, y + cell - beam, cell, beam);
|
||||||
|
// Recessed coffer pan
|
||||||
|
const px = x + beam;
|
||||||
|
const py = y + beam;
|
||||||
|
const pwid = cell - beam * 2;
|
||||||
|
const phei = cell - beam * 2;
|
||||||
|
const grad = ctx.createLinearGradient(px, py, px, py + phei);
|
||||||
|
grad.addColorStop(0, '#22150c');
|
||||||
|
grad.addColorStop(1, '#3c2616');
|
||||||
|
ctx.fillStyle = grad;
|
||||||
|
ctx.fillRect(px, py, pwid, phei);
|
||||||
|
// Central gilt rosette boss
|
||||||
|
ctx.fillStyle = '#8a6a2a';
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(px + pwid / 2, py + phei / 2, Math.min(pwid, phei) * 0.13, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
noiseOverlay(ctx, size, 0.05, 690);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'limestone':
|
case 'limestone':
|
||||||
fill(ctx, size, '#ddd4c4');
|
fill(ctx, size, '#ddd4c4');
|
||||||
for (let y = 0; y < size; y += 4) {
|
for (let y = 0; y < size; y += 4) {
|
||||||
@@ -460,15 +655,59 @@ function paintSurface(kind: SurfaceTextureKind, size: number): ImageData {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'mosaic-byzantine': {
|
case 'mosaic-byzantine': {
|
||||||
fill(ctx, size, '#1a1814');
|
// Gold-ground tessera field (gold-leaf glass, individually jittered) with
|
||||||
const cell = size / 32;
|
// sparse inlaid accent tesserae and a meander border course — Hagia Sophia /
|
||||||
const colors = ['#d4af37', '#8a3020', '#2060a0', '#f0ece0', '#408040'];
|
// Ravenna style, not a flat multicolor checker.
|
||||||
for (let y = 0; y < size; y += cell) {
|
fill(ctx, size, '#1a1408');
|
||||||
for (let x = 0; x < size; x += cell) {
|
const grid = 32;
|
||||||
ctx.fillStyle = colors[(x / cell + y / cell) % colors.length];
|
const cell = size / grid;
|
||||||
ctx.fillRect(x + 1, y + 1, cell - 2, cell - 2);
|
const rand = seeded(777);
|
||||||
|
const accents: [number, number, number][] = [
|
||||||
|
[32, 52, 98],
|
||||||
|
[126, 30, 44],
|
||||||
|
[28, 78, 58],
|
||||||
|
[18, 16, 22],
|
||||||
|
];
|
||||||
|
for (let gy = 0; gy < grid; gy++) {
|
||||||
|
for (let gx = 0; gx < grid; gx++) {
|
||||||
|
const x = gx * cell;
|
||||||
|
const y = gy * cell;
|
||||||
|
const roll = rand();
|
||||||
|
let r: number, g: number, b: number;
|
||||||
|
if (roll < 0.05) {
|
||||||
|
[r, g, b] = accents[Math.floor(rand() * accents.length)];
|
||||||
|
} else {
|
||||||
|
const j = (rand() - 0.5) * 50;
|
||||||
|
r = 214 + j;
|
||||||
|
g = 170 + j * 0.72;
|
||||||
|
b = 58 + j * 0.35;
|
||||||
|
}
|
||||||
|
ctx.fillStyle = rgb(r, g, b);
|
||||||
|
const pad = 1 + rand() * 1.4;
|
||||||
|
ctx.fillRect(x + pad, y + pad, cell - pad * 2, cell - pad * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Dark grout between tesserae reads as individual glass/gold tiles.
|
||||||
|
ctx.strokeStyle = 'rgba(16,10,4,0.4)';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
for (let i = 0; i <= grid; i++) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(i * cell, 0);
|
||||||
|
ctx.lineTo(i * cell, size);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, i * cell);
|
||||||
|
ctx.lineTo(size, i * cell);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
// Meander (key-pattern) register band dividing the panel, in navy and gold.
|
||||||
|
const bandRow = Math.floor(grid * 0.47);
|
||||||
|
for (let gx = 0; gx < grid; gx++) {
|
||||||
|
const on = Math.floor(gx / 2) % 2 === 0;
|
||||||
|
ctx.fillStyle = on ? '#0f2a52' : '#e0b840';
|
||||||
|
ctx.fillRect(gx * cell, bandRow * cell, cell, cell * 0.5);
|
||||||
|
}
|
||||||
|
noiseOverlay(ctx, size, 0.045, 778);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'mosaic-roman': {
|
case 'mosaic-roman': {
|
||||||
@@ -611,6 +850,9 @@ const ROUGHNESS: Partial<Record<SurfaceTextureKind, number>> = {
|
|||||||
'marble-veined-carrara': 0.16,
|
'marble-veined-carrara': 0.16,
|
||||||
'marble-veined-emerald': 0.18,
|
'marble-veined-emerald': 0.18,
|
||||||
'marble-checker': 0.14,
|
'marble-checker': 0.14,
|
||||||
|
'marble-opus-sectile': 0.2,
|
||||||
|
'marble-revetment': 0.34,
|
||||||
|
'coffered-wood': 0.72,
|
||||||
'gilded-stucco': 0.22,
|
'gilded-stucco': 0.22,
|
||||||
'velvet-crimson': 0.92,
|
'velvet-crimson': 0.92,
|
||||||
'velvet-navy': 0.92,
|
'velvet-navy': 0.92,
|
||||||
@@ -638,6 +880,8 @@ const ROUGHNESS: Partial<Record<SurfaceTextureKind, number>> = {
|
|||||||
const METALNESS: Partial<Record<SurfaceTextureKind, number>> = {
|
const METALNESS: Partial<Record<SurfaceTextureKind, number>> = {
|
||||||
'marble-white': 0.08,
|
'marble-white': 0.08,
|
||||||
'marble-veined-carrara': 0.1,
|
'marble-veined-carrara': 0.1,
|
||||||
|
'marble-opus-sectile': 0.14,
|
||||||
|
'marble-revetment': 0.1,
|
||||||
'gilded-stucco': 0.65,
|
'gilded-stucco': 0.65,
|
||||||
'concrete-polished': 0.12,
|
'concrete-polished': 0.12,
|
||||||
'terrazzo': 0.15,
|
'terrazzo': 0.15,
|
||||||
@@ -647,6 +891,9 @@ const METALNESS: Partial<Record<SurfaceTextureKind, number>> = {
|
|||||||
|
|
||||||
const METERS: Partial<Record<SurfaceTextureKind, number>> = {
|
const METERS: Partial<Record<SurfaceTextureKind, number>> = {
|
||||||
'marble-checker': 2.4,
|
'marble-checker': 2.4,
|
||||||
|
'marble-opus-sectile': 5.0,
|
||||||
|
'marble-revetment': 4.4,
|
||||||
|
'coffered-wood': 4.2,
|
||||||
flagstone: 3.0,
|
flagstone: 3.0,
|
||||||
'mosaic-byzantine': 1.8,
|
'mosaic-byzantine': 1.8,
|
||||||
'mosaic-roman': 2.0,
|
'mosaic-roman': 2.0,
|
||||||
@@ -669,6 +916,9 @@ const NORMAL_SCALE: Partial<Record<SurfaceTextureKind, number>> = {
|
|||||||
'painted-lime': 0.62,
|
'painted-lime': 0.62,
|
||||||
'marble-white': 0.75,
|
'marble-white': 0.75,
|
||||||
'marble-veined-carrara': 0.8,
|
'marble-veined-carrara': 0.8,
|
||||||
|
'marble-opus-sectile': 0.55,
|
||||||
|
'marble-revetment': 0.6,
|
||||||
|
'coffered-wood': 1.0,
|
||||||
'velvet-crimson': 0.5,
|
'velvet-crimson': 0.5,
|
||||||
'velvet-navy': 0.5,
|
'velvet-navy': 0.5,
|
||||||
'velvet-emerald': 0.5,
|
'velvet-emerald': 0.5,
|
||||||
|
|||||||
Reference in New Issue
Block a user