Align influence lamps 40cm above the tallest hall frame.

Compute the highest allocated frame top per hall and place every influence lamp on that shared rail so markers line up across different canvas sizes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-08-09 22:42:23 +03:00
co-authored by Cursor
parent cfee69c9a6
commit f20f811f21
2 changed files with 33 additions and 10 deletions
+32 -9
View File
@@ -191,8 +191,8 @@ function galleryWallColors(movementColor?: string) {
};
}
/** Height above frame top edge — keeps fixture out of the viewer's line of sight. */
const INFLUENCE_LAMP_ABOVE_FRAME = 0.46;
/** Clearance above the tallest frame in the hall for influence lamps (metres). */
const INFLUENCE_LAMP_ABOVE_HIGHEST_M = 0.4;
type WallSide = 'back' | 'left' | 'right';
@@ -304,6 +304,20 @@ function frameOuterH(canvasH: number, reviewed: boolean): number {
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) {
const count = paintings.length;
if (count === 0) return { slots: [], spanNeeded: span };
@@ -803,17 +817,19 @@ function usePaintingTexture(urls: string[] | string | null) {
}
function InfluencePictureLamp({
frameHeight,
matBorder,
frameWorldY,
lampWorldY,
frameDepth,
highlighted,
}: {
frameHeight: number;
matBorder: number;
/** Painting group world Y — local lamp offset = lampWorldY frameWorldY. */
frameWorldY: number;
/** Shared hall rail: 40 cm above the tallest frame. */
lampWorldY: number;
frameDepth: number;
highlighted: boolean;
}) {
const mountY = frameHeight / 2 + matBorder + INFLUENCE_LAMP_ABOVE_FRAME;
const mountY = lampWorldY - frameWorldY;
const glow = highlighted ? 1.6 : 1.15;
const scale = 1.35;
@@ -918,6 +934,7 @@ function PaintingFrame({
wallSide,
imageRevision,
caption,
influenceLampWorldY,
onClick,
}: {
painting: Painting;
@@ -928,6 +945,7 @@ function PaintingFrame({
wallSide: WallSide;
imageRevision?: number;
caption?: string;
influenceLampWorldY: number;
onClick: () => void;
}) {
const [hovered, setHovered] = useState(false);
@@ -1018,8 +1036,8 @@ function PaintingFrame({
{hasInfluenceLinks && (
<InfluencePictureLamp
frameHeight={height}
matBorder={matBorder}
frameWorldY={position[1]}
lampWorldY={influenceLampWorldY}
frameDepth={frameDepth}
highlighted={hovered}
/>
@@ -1505,6 +1523,10 @@ function ArtistHall({
const hallLightScale = interiorStyle?.lightScale ?? 1;
const wallRoughness = interiorStyle ? 0.75 : 0.92;
const wallMetalness = interiorStyle ? 0.08 : 0.06;
const influenceLampWorldY = useMemo(
() => highestPaintingTopY(segments) + INFLUENCE_LAMP_ABOVE_HIGHEST_M,
[segments]
);
const wallMaterial = (color: string) => (
<meshStandardMaterial color={color} roughness={wallRoughness} metalness={wallMetalness} />
@@ -1752,6 +1774,7 @@ function ArtistHall({
wallSide={seg.slots[i].side}
imageRevision={imageRevisions?.[painting.id]}
caption={showCaptions ? paintingWallCaption(painting) : undefined}
influenceLampWorldY={influenceLampWorldY}
onClick={() => onPaintingClick(painting.id)}
/>
))}