Compare commits
2
Commits
cfee69c9a6
...
4088d7d57b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4088d7d57b | ||
|
|
f20f811f21 |
@@ -494,7 +494,7 @@ Each painting includes:
|
|||||||
|
|
||||||
| Field | Meaning |
|
| Field | Meaning |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| `has_influence_links` | `true` when the work appears in any influence row — 3D gallery shows a golden lamp above the frame |
|
| `has_influence_links` | `true` when the work appears in any influence row — 3D gallery shows a golden lamp on a shared rail (40 cm above the tallest frame in the hall) |
|
||||||
| `checkup_checked` | Reviewed in checkup / debug workflow (gold frame in 3D when true) |
|
| `checkup_checked` | Reviewed in checkup / debug workflow (gold frame in 3D when true) |
|
||||||
| `checkup_fixed` | Image replaced via **Fix it** |
|
| `checkup_fixed` | Image replaced via **Fix it** |
|
||||||
|
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ Each artist has **exactly one hall**. The hall is a rectangular room sized to fi
|
|||||||
| Floor | Herringbone **parquet** procedural texture (`parquetFloorTexture.ts`) |
|
| Floor | Herringbone **parquet** procedural texture (`parquetFloorTexture.ts`) |
|
||||||
| Wall tint | Gallery walls blend the artist’s **movement colour** into cream plaster tones |
|
| Wall tint | Gallery walls blend the artist’s **movement colour** into cream plaster tones |
|
||||||
| Frame finish | **Unchecked** works: black moulding; **Reviewed** (`checkup_checked`): bright gold moulding at **double width** |
|
| Frame finish | **Unchecked** works: black moulding; **Reviewed** (`checkup_checked`): bright gold moulding at **double width** |
|
||||||
| Influence lamps | A golden picture light appears **above frames** whose work has any influence-graph edge (`has_influence_links` from the API); fixture is mounted **upside down** and is **emissive-only** (no per-frame lights — see light budget below) |
|
| Influence lamps | A golden picture light marks works with any influence-graph edge (`has_influence_links` from the API). All lamps in a hall share one rail height: **40 cm above the tallest allocated frame** in that hall (so they line up regardless of canvas size). Fixture is mounted **upside down** and is **emissive-only** (no per-frame lights — see light budget below) |
|
||||||
| Curator-note plates | A small brass plate hangs **beneath frames** that have non-empty `curator_notes` |
|
| Curator-note plates | A small brass plate hangs **beneath frames** that have non-empty `curator_notes` |
|
||||||
| Eye-level viewing | Frame centres sit at **eye height (~1.65 m)**; the camera stays **level with the floor** (no pitch up/down) |
|
| Eye-level viewing | Frame centres sit at **eye height (~1.65 m)**; the camera stays **level with the floor** (no pitch up/down) |
|
||||||
| Open centre | Floor and ceiling only — no freestanding columns or pedestals in the walkway |
|
| Open centre | Floor and ceiling only — no freestanding columns or pedestals in the walkway |
|
||||||
|
|||||||
@@ -191,8 +191,8 @@ function galleryWallColors(movementColor?: string) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Height above frame top edge — keeps fixture out of the viewer's line of sight. */
|
/** Clearance above the tallest frame in the hall for influence lamps (metres). */
|
||||||
const INFLUENCE_LAMP_ABOVE_FRAME = 0.46;
|
const INFLUENCE_LAMP_ABOVE_HIGHEST_M = 0.4;
|
||||||
|
|
||||||
type WallSide = 'back' | 'left' | 'right';
|
type WallSide = 'back' | 'left' | 'right';
|
||||||
|
|
||||||
@@ -304,6 +304,20 @@ function frameOuterH(canvasH: number, reviewed: boolean): number {
|
|||||||
return canvasH + matBorder * 2 + rail;
|
return canvasH + matBorder * 2 + rail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** World Y of the top edge of the tallest allocated frame in the hall. */
|
||||||
|
function highestPaintingTopY(segments: WallSegment[]): number {
|
||||||
|
let maxTop = EYE_HEIGHT;
|
||||||
|
for (const seg of segments) {
|
||||||
|
for (let i = 0; i < seg.paintings.length; i++) {
|
||||||
|
const slot = seg.slots[i];
|
||||||
|
if (!slot) continue;
|
||||||
|
const top = slot.position[1] + frameOuterH(slot.maxH, paintingIsReviewed(seg.paintings[i])) / 2;
|
||||||
|
if (top > maxTop) maxTop = top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxTop;
|
||||||
|
}
|
||||||
|
|
||||||
function layoutRow(paintings: Painting[], span: number) {
|
function layoutRow(paintings: Painting[], span: number) {
|
||||||
const count = paintings.length;
|
const count = paintings.length;
|
||||||
if (count === 0) return { slots: [], spanNeeded: span };
|
if (count === 0) return { slots: [], spanNeeded: span };
|
||||||
@@ -803,17 +817,19 @@ function usePaintingTexture(urls: string[] | string | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InfluencePictureLamp({
|
function InfluencePictureLamp({
|
||||||
frameHeight,
|
frameWorldY,
|
||||||
matBorder,
|
lampWorldY,
|
||||||
frameDepth,
|
frameDepth,
|
||||||
highlighted,
|
highlighted,
|
||||||
}: {
|
}: {
|
||||||
frameHeight: number;
|
/** Painting group world Y — local lamp offset = lampWorldY − frameWorldY. */
|
||||||
matBorder: number;
|
frameWorldY: number;
|
||||||
|
/** Shared hall rail: 40 cm above the tallest frame. */
|
||||||
|
lampWorldY: number;
|
||||||
frameDepth: number;
|
frameDepth: number;
|
||||||
highlighted: boolean;
|
highlighted: boolean;
|
||||||
}) {
|
}) {
|
||||||
const mountY = frameHeight / 2 + matBorder + INFLUENCE_LAMP_ABOVE_FRAME;
|
const mountY = lampWorldY - frameWorldY;
|
||||||
const glow = highlighted ? 1.6 : 1.15;
|
const glow = highlighted ? 1.6 : 1.15;
|
||||||
const scale = 1.35;
|
const scale = 1.35;
|
||||||
|
|
||||||
@@ -918,6 +934,7 @@ function PaintingFrame({
|
|||||||
wallSide,
|
wallSide,
|
||||||
imageRevision,
|
imageRevision,
|
||||||
caption,
|
caption,
|
||||||
|
influenceLampWorldY,
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
painting: Painting;
|
painting: Painting;
|
||||||
@@ -928,6 +945,7 @@ function PaintingFrame({
|
|||||||
wallSide: WallSide;
|
wallSide: WallSide;
|
||||||
imageRevision?: number;
|
imageRevision?: number;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
|
influenceLampWorldY: number;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
@@ -1018,8 +1036,8 @@ function PaintingFrame({
|
|||||||
|
|
||||||
{hasInfluenceLinks && (
|
{hasInfluenceLinks && (
|
||||||
<InfluencePictureLamp
|
<InfluencePictureLamp
|
||||||
frameHeight={height}
|
frameWorldY={position[1]}
|
||||||
matBorder={matBorder}
|
lampWorldY={influenceLampWorldY}
|
||||||
frameDepth={frameDepth}
|
frameDepth={frameDepth}
|
||||||
highlighted={hovered}
|
highlighted={hovered}
|
||||||
/>
|
/>
|
||||||
@@ -1505,6 +1523,10 @@ function ArtistHall({
|
|||||||
const hallLightScale = interiorStyle?.lightScale ?? 1;
|
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;
|
||||||
|
const influenceLampWorldY = useMemo(
|
||||||
|
() => highestPaintingTopY(segments) + INFLUENCE_LAMP_ABOVE_HIGHEST_M,
|
||||||
|
[segments]
|
||||||
|
);
|
||||||
|
|
||||||
const wallMaterial = (color: string) => (
|
const wallMaterial = (color: string) => (
|
||||||
<meshStandardMaterial color={color} roughness={wallRoughness} metalness={wallMetalness} />
|
<meshStandardMaterial color={color} roughness={wallRoughness} metalness={wallMetalness} />
|
||||||
@@ -1752,6 +1774,7 @@ function ArtistHall({
|
|||||||
wallSide={seg.slots[i].side}
|
wallSide={seg.slots[i].side}
|
||||||
imageRevision={imageRevisions?.[painting.id]}
|
imageRevision={imageRevisions?.[painting.id]}
|
||||||
caption={showCaptions ? paintingWallCaption(painting) : undefined}
|
caption={showCaptions ? paintingWallCaption(painting) : undefined}
|
||||||
|
influenceLampWorldY={influenceLampWorldY}
|
||||||
onClick={() => onPaintingClick(painting.id)}
|
onClick={() => onPaintingClick(painting.id)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user