Improve movement flow layout/animation and fix Byzantine hall doors, styles, and preload.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
23e1210e86
commit
8111cd0163
@@ -29,6 +29,8 @@ export interface MovementHallLayout {
|
||||
segments: WallSegment[];
|
||||
paintingCount: number;
|
||||
yearLabel: string;
|
||||
/** When true, far (−Z) wall has a center exit; paintings hang on flanks only. */
|
||||
endWallHasDoor: boolean;
|
||||
}
|
||||
|
||||
const WALL_HEIGHT = 4.2;
|
||||
@@ -165,36 +167,59 @@ function layoutSideSlots(
|
||||
});
|
||||
}
|
||||
|
||||
/** Far wall ahead of the entrance — split across door flanks (exit sits in the center). */
|
||||
/** Far wall ahead of the entrance — full span, or split across door flanks. */
|
||||
function layoutBackSlots(
|
||||
paintings: Painting[],
|
||||
width: number,
|
||||
halfD: number,
|
||||
inset: number
|
||||
inset: number,
|
||||
hasDoor: boolean
|
||||
): FrameSlot[] {
|
||||
if (paintings.length === 0) return [];
|
||||
const flankSpan = Math.max(MIN_FRAME_W + WALL_PADDING, (width - DOOR_CLEARANCE) / 2);
|
||||
const mid = Math.ceil(paintings.length / 2);
|
||||
const leftFlank = paintings.slice(0, mid);
|
||||
const rightFlank = paintings.slice(mid);
|
||||
const y = EYE_HEIGHT;
|
||||
const z = -halfD + inset + WALL_STANDOFF;
|
||||
const leftCenterX = -DOOR_CLEARANCE / 2 - flankSpan / 2;
|
||||
const rightCenterX = DOOR_CLEARANCE / 2 + flankSpan / 2;
|
||||
|
||||
const mapFlank = (group: Painting[], centerX: number): FrameSlot[] => {
|
||||
if (group.length === 0) return [];
|
||||
const { slots: rowSlots } = layoutRow(group, flankSpan);
|
||||
if (!hasDoor) {
|
||||
const { slots: rowSlots } = layoutRow(paintings, width);
|
||||
return rowSlots.map((s) => ({
|
||||
maxW: s.maxW,
|
||||
maxH: s.maxH,
|
||||
rotationY: 0,
|
||||
side: 'back' as const,
|
||||
position: [centerX + s.offset, y, z] as [number, number, number],
|
||||
position: [s.offset, y, z] as [number, number, number],
|
||||
}));
|
||||
}
|
||||
|
||||
const flankSpan = Math.max(MIN_FRAME_W + WALL_PADDING, (width - DOOR_CLEARANCE) / 2);
|
||||
const mid = Math.ceil(paintings.length / 2);
|
||||
const leftFlank = paintings.slice(0, mid);
|
||||
const rightFlank = paintings.slice(mid);
|
||||
const leftCenterX = -DOOR_CLEARANCE / 2 - flankSpan / 2;
|
||||
const rightCenterX = DOOR_CLEARANCE / 2 + flankSpan / 2;
|
||||
const doorEdge = DOOR_CLEARANCE / 2 + 0.08;
|
||||
|
||||
const mapFlank = (group: Painting[], centerX: number, side: 'left' | 'right'): FrameSlot[] => {
|
||||
if (group.length === 0) return [];
|
||||
const { slots: rowSlots } = layoutRow(group, flankSpan);
|
||||
return rowSlots.map((s) => {
|
||||
let x = centerX + s.offset;
|
||||
const halfOuter = frameOuterW(s.maxW, false) / 2;
|
||||
if (side === 'left' && x + halfOuter > -doorEdge) {
|
||||
x = -doorEdge - halfOuter;
|
||||
} else if (side === 'right' && x - halfOuter < doorEdge) {
|
||||
x = doorEdge + halfOuter;
|
||||
}
|
||||
return {
|
||||
maxW: s.maxW,
|
||||
maxH: s.maxH,
|
||||
rotationY: 0,
|
||||
side: 'back' as const,
|
||||
position: [x, y, z] as [number, number, number],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return [...mapFlank(leftFlank, leftCenterX), ...mapFlank(rightFlank, rightCenterX)];
|
||||
return [...mapFlank(leftFlank, leftCenterX, 'left'), ...mapFlank(rightFlank, rightCenterX, 'right')];
|
||||
}
|
||||
|
||||
export function buildMovementHallLayout(
|
||||
@@ -206,7 +231,21 @@ export function buildMovementHallLayout(
|
||||
const leftSpan = layoutRow(left, MIN_HALL_SIZE);
|
||||
const rightSpan = layoutRow(right, MIN_HALL_SIZE);
|
||||
const depth = Math.max(MIN_HALL_SIZE, leftSpan.spanNeeded, rightSpan.spanNeeded);
|
||||
const width = MIN_HALL_WIDTH;
|
||||
|
||||
// Single-wing halls put the exit on the entrance wall, so the far wall is solid
|
||||
// and can use its full width for paintings. Multi-wing halls keep a back exit.
|
||||
const endWallHasDoor = hallCount > 1;
|
||||
|
||||
let width: number;
|
||||
if (endWallHasDoor) {
|
||||
const mid = Math.ceil(back.length / 2);
|
||||
const leftFlankNeed = layoutRow(back.slice(0, mid), MIN_FRAME_W + WALL_PADDING).spanNeeded;
|
||||
const rightFlankNeed = layoutRow(back.slice(mid), MIN_FRAME_W + WALL_PADDING).spanNeeded;
|
||||
width = Math.max(MIN_HALL_WIDTH, leftFlankNeed + DOOR_CLEARANCE + rightFlankNeed);
|
||||
} else {
|
||||
width = Math.max(MIN_HALL_WIDTH, layoutRow(back, MIN_HALL_SIZE).spanNeeded);
|
||||
}
|
||||
|
||||
const halfW = width / 2;
|
||||
const halfD = depth / 2;
|
||||
const inset = WALL_THICKNESS / 2 + MOUNT_OFFSET;
|
||||
@@ -216,7 +255,7 @@ export function buildMovementHallLayout(
|
||||
side: 'back',
|
||||
label: back.length > 0 ? `Wing ${hallIndex + 1} · End wall` : '',
|
||||
paintings: back,
|
||||
slots: layoutBackSlots(back, width, halfD, inset),
|
||||
slots: layoutBackSlots(back, width, halfD, inset, endWallHasDoor),
|
||||
},
|
||||
{
|
||||
side: 'left',
|
||||
@@ -240,6 +279,7 @@ export function buildMovementHallLayout(
|
||||
segments,
|
||||
paintingCount: paintings.length,
|
||||
yearLabel: formatYearLabel(paintings),
|
||||
endWallHasDoor,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user