@agent-os-lab/agent-game-sdk 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/USAGE.md +8 -0
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/office/core/types.ts +20 -0
- package/src/office/layout/config.ts +234 -25
- package/src/office/layout/index.ts +1 -0
- package/src/office/layout/navigation.ts +168 -0
- package/src/office/layout/resolver.ts +645 -14
- package/src/office/mount.ts +15 -0
- package/src/office/react/AgentGameOfficeView.ts +20 -4
- package/src/office/renderers/three/agent-body-instancing.ts +38 -13
- package/src/office/renderers/three/agent-effect-instancing.ts +26 -12
- package/src/office/renderers/three/agent-label.ts +3 -1
- package/src/office/renderers/three/agent-mesh.ts +15 -2
- package/src/office/renderers/three/agent-route.ts +220 -0
- package/src/office/renderers/three/mount.ts +118 -21
- package/src/office/renderers/three/scene.ts +652 -18
- package/src/runtime-agent-list.ts +15 -0
|
@@ -2,8 +2,18 @@ import type { AgentGameOfficeAgent, AgentGameOfficeZoneId } from "../core/types"
|
|
|
2
2
|
import {
|
|
3
3
|
type AgentGameOfficeConfig,
|
|
4
4
|
type AgentGameOfficeRoomType,
|
|
5
|
+
type NormalizedOfficeConnectorConfig,
|
|
6
|
+
type NormalizedOfficeFloorConfig,
|
|
7
|
+
type NormalizedOfficeRoomConfig,
|
|
5
8
|
normalizeOfficeConfig,
|
|
6
9
|
} from "./config";
|
|
10
|
+
import type {
|
|
11
|
+
NavigationEdge,
|
|
12
|
+
NavigationNode,
|
|
13
|
+
ResolvedBuildingNavigationGraph,
|
|
14
|
+
} from "./navigation";
|
|
15
|
+
|
|
16
|
+
export const FLOOR_ELEVATION_STEP = 20;
|
|
7
17
|
|
|
8
18
|
const OFFICE_LAYOUT_SCALE = 1.2;
|
|
9
19
|
const OFFICE_FLOOR_CENTER_X = 16;
|
|
@@ -18,12 +28,25 @@ const LARGE_MEETING_CHAIR_DISTANCE_Z = 2.55 * OFFICE_LAYOUT_SCALE;
|
|
|
18
28
|
const ROOMS_PER_ROW = 3;
|
|
19
29
|
const ROOM_ROW_GAP = 0;
|
|
20
30
|
const NORTH_WALL_BOOKCASE_Z_OFFSET = 0.32;
|
|
31
|
+
const ELEVATOR_CORE_MARGIN_X = 3.2;
|
|
32
|
+
const ELEVATOR_CORE_MARGIN_Z = 3.2;
|
|
33
|
+
const ELEVATOR_ENTRY_OFFSET_Z = 1.6;
|
|
21
34
|
|
|
22
35
|
export type OfficeComponentKind =
|
|
23
36
|
| "desk"
|
|
24
37
|
| "officeChair"
|
|
25
38
|
| "monitor"
|
|
26
39
|
| "meetingTable"
|
|
40
|
+
| "diningTable"
|
|
41
|
+
| "cafeCounter"
|
|
42
|
+
| "cafeTable"
|
|
43
|
+
| "kitchenCounter"
|
|
44
|
+
| "kitchenStove"
|
|
45
|
+
| "kitchenFridge"
|
|
46
|
+
| "gardenPlanter"
|
|
47
|
+
| "gardenTree"
|
|
48
|
+
| "gardenBench"
|
|
49
|
+
| "gardenPath"
|
|
27
50
|
| "meetingGlassRoom"
|
|
28
51
|
| "largeMeetingTable"
|
|
29
52
|
| "tableCup"
|
|
@@ -46,11 +69,15 @@ export type OfficeComponentKind =
|
|
|
46
69
|
| "dumbbellRack"
|
|
47
70
|
| "weightBench"
|
|
48
71
|
| "yogaMat"
|
|
49
|
-
| "gymMirror"
|
|
72
|
+
| "gymMirror"
|
|
73
|
+
| "elevatorDoor"
|
|
74
|
+
| "elevatorFrame"
|
|
75
|
+
| "elevatorIndicator";
|
|
50
76
|
|
|
51
77
|
export type ResolvedOfficeComponentInstance = {
|
|
52
78
|
id: string;
|
|
53
79
|
kind: OfficeComponentKind;
|
|
80
|
+
floorId: string;
|
|
54
81
|
roomId: string | null;
|
|
55
82
|
position: { x: number; y?: number; z: number };
|
|
56
83
|
rotation?: number;
|
|
@@ -75,6 +102,7 @@ export type ResolvedOfficeComponentInstance = {
|
|
|
75
102
|
|
|
76
103
|
export type ResolvedOfficeRoom = {
|
|
77
104
|
id: string;
|
|
105
|
+
floorId: string;
|
|
78
106
|
type: AgentGameOfficeRoomType;
|
|
79
107
|
bounds: { x: number; z: number; width: number; depth: number };
|
|
80
108
|
capacity: number;
|
|
@@ -82,6 +110,7 @@ export type ResolvedOfficeRoom = {
|
|
|
82
110
|
|
|
83
111
|
export type ResolvedFloorTile = {
|
|
84
112
|
id: string;
|
|
113
|
+
floorId: string;
|
|
85
114
|
color: number;
|
|
86
115
|
x: number;
|
|
87
116
|
z: number;
|
|
@@ -93,6 +122,7 @@ export type ResolvedWallSide = "north" | "south" | "west" | "east";
|
|
|
93
122
|
|
|
94
123
|
export type ResolvedWall = {
|
|
95
124
|
id: string;
|
|
125
|
+
floorId: string;
|
|
96
126
|
roomId: string;
|
|
97
127
|
side: ResolvedWallSide;
|
|
98
128
|
x: number;
|
|
@@ -114,6 +144,7 @@ export type ResolvedLight = {
|
|
|
114
144
|
export type ResolvedSeatAnchor = {
|
|
115
145
|
id: string;
|
|
116
146
|
roomId: string;
|
|
147
|
+
floorId: string;
|
|
117
148
|
zoneId: AgentGameOfficeZoneId;
|
|
118
149
|
position: { x: number; z: number };
|
|
119
150
|
facing?: { x: number; z: number };
|
|
@@ -125,14 +156,56 @@ export type ResolvedZoneAnchor = ResolvedSeatAnchor;
|
|
|
125
156
|
export type ResolvedWallAnchor = {
|
|
126
157
|
id: string;
|
|
127
158
|
roomId: string;
|
|
159
|
+
floorId: string;
|
|
128
160
|
position: { x: number; z: number };
|
|
129
161
|
rotation?: number;
|
|
130
162
|
faceDirection: -1 | 1;
|
|
131
163
|
};
|
|
132
164
|
|
|
165
|
+
export type ResolvedOffstageAnchor = {
|
|
166
|
+
id: "offstage";
|
|
167
|
+
roomId: null;
|
|
168
|
+
floorId: null;
|
|
169
|
+
zoneId: "offstage";
|
|
170
|
+
position: { x: number; y: number; z: number };
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type ResolvedOfficeFloor = {
|
|
174
|
+
id: string;
|
|
175
|
+
name: string;
|
|
176
|
+
level: number;
|
|
177
|
+
elevation: number;
|
|
178
|
+
rooms: ResolvedOfficeRoom[];
|
|
179
|
+
floorTiles: ResolvedFloorTile[];
|
|
180
|
+
walls: ResolvedWall[];
|
|
181
|
+
components: ResolvedOfficeComponentInstance[];
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export type ResolvedElevatorStop = {
|
|
185
|
+
floorId: string;
|
|
186
|
+
entryNodeId: string;
|
|
187
|
+
cabinNodeId: string;
|
|
188
|
+
entryPosition: { x: number; y: number; z: number };
|
|
189
|
+
cabinPosition: { x: number; y: number; z: number };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export type ResolvedBuildingConnector = {
|
|
193
|
+
id: string;
|
|
194
|
+
type: "elevator";
|
|
195
|
+
name: string;
|
|
196
|
+
serves: string[];
|
|
197
|
+
stops: ResolvedElevatorStop[];
|
|
198
|
+
};
|
|
199
|
+
|
|
133
200
|
export type ResolvedOfficeLayout = {
|
|
201
|
+
building: {
|
|
202
|
+
floors: ResolvedOfficeFloor[];
|
|
203
|
+
connectors: ResolvedBuildingConnector[];
|
|
204
|
+
navigation: ResolvedBuildingNavigationGraph;
|
|
205
|
+
};
|
|
134
206
|
scene: {
|
|
135
207
|
width: number;
|
|
208
|
+
height: number;
|
|
136
209
|
depth: number;
|
|
137
210
|
center: { x: number; z: number };
|
|
138
211
|
floorTileSize: number;
|
|
@@ -146,7 +219,7 @@ export type ResolvedOfficeLayout = {
|
|
|
146
219
|
seats: ResolvedSeatAnchor[];
|
|
147
220
|
zones: ResolvedZoneAnchor[];
|
|
148
221
|
wallDecorations: ResolvedWallAnchor[];
|
|
149
|
-
offstage:
|
|
222
|
+
offstage: ResolvedOffstageAnchor;
|
|
150
223
|
};
|
|
151
224
|
};
|
|
152
225
|
|
|
@@ -159,6 +232,15 @@ type MutableResolvedLayout = ResolvedOfficeLayout & {
|
|
|
159
232
|
};
|
|
160
233
|
};
|
|
161
234
|
|
|
235
|
+
type FloorResolvedLayout = ResolvedOfficeFloor & {
|
|
236
|
+
scene: Pick<ResolvedOfficeLayout["scene"], "width" | "depth" | "center" | "floorTileSize">;
|
|
237
|
+
anchors: {
|
|
238
|
+
seats: ResolvedSeatAnchor[];
|
|
239
|
+
zones: ResolvedZoneAnchor[];
|
|
240
|
+
wallDecorations: ResolvedWallAnchor[];
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
|
|
162
244
|
const DESK_TEMPLATE = [
|
|
163
245
|
{ seat: { x: scaleOfficeX(-12), z: scaleOfficeZ(-6.2) }, rotation: Math.PI, desk: { x: scaleOfficeX(-12), z: scaleOfficeZ(-8) } },
|
|
164
246
|
{ seat: { x: scaleOfficeX(-7), z: scaleOfficeZ(-6.2) }, rotation: Math.PI, desk: { x: scaleOfficeX(-7), z: scaleOfficeZ(-8) } },
|
|
@@ -220,13 +302,30 @@ const LOUNGE_CENTER = { x: scaleOfficeX(4.8), z: scaleOfficeZ(8.2) } as const;
|
|
|
220
302
|
|
|
221
303
|
export function resolveOfficeLayout(config?: AgentGameOfficeConfig): ResolvedOfficeLayout {
|
|
222
304
|
const normalized = normalizeOfficeConfig(config);
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
305
|
+
const floors = normalized.building.floors.map((floor) =>
|
|
306
|
+
resolveOfficeFloor(floor, floor.level * FLOOR_ELEVATION_STEP, normalized.building.floors.length === 1)
|
|
307
|
+
);
|
|
308
|
+
const connectors = resolveBuildingConnectors(normalized.building.connectors, floors);
|
|
309
|
+
const offstage = createOffstageAnchor();
|
|
310
|
+
return assembleBuildingLayout(floors, connectors, offstage);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function resolveOfficeFloor(
|
|
314
|
+
floor: NormalizedOfficeFloorConfig,
|
|
315
|
+
elevation: number,
|
|
316
|
+
useLegacyRoomIds: boolean,
|
|
317
|
+
): FloorResolvedLayout {
|
|
318
|
+
const offstage = createOffstageAnchor();
|
|
319
|
+
const rooms = floor.rooms.map((room, index) => createRoom(resolveRoomId(room, useLegacyRoomIds), floor.id, room.type, room.capacity, index));
|
|
227
320
|
const layout: MutableResolvedLayout = {
|
|
321
|
+
building: {
|
|
322
|
+
floors: [],
|
|
323
|
+
connectors: [],
|
|
324
|
+
navigation: { nodes: [], edges: [] },
|
|
325
|
+
},
|
|
228
326
|
scene: {
|
|
229
327
|
width: OFFICE_FLOOR_WIDTH,
|
|
328
|
+
height: 0,
|
|
230
329
|
depth: OFFICE_FLOOR_DEPTH,
|
|
231
330
|
center: { x: OFFICE_FLOOR_CENTER_X, z: 0 },
|
|
232
331
|
floorTileSize: OFFICE_FLOOR_TILE_SIZE,
|
|
@@ -237,7 +336,7 @@ export function resolveOfficeLayout(config?: AgentGameOfficeConfig): ResolvedOff
|
|
|
237
336
|
{ id: "directional", kind: "directional", color: 0xffffff, intensity: 1.2, position: { x: 10, y: 18, z: 8 } },
|
|
238
337
|
],
|
|
239
338
|
},
|
|
240
|
-
rooms
|
|
339
|
+
rooms,
|
|
241
340
|
components: [],
|
|
242
341
|
anchors: {
|
|
243
342
|
seats: [],
|
|
@@ -252,21 +351,320 @@ export function resolveOfficeLayout(config?: AgentGameOfficeConfig): ResolvedOff
|
|
|
252
351
|
addOfficePreset(layout, room);
|
|
253
352
|
} else if (room.type === "auditorium") {
|
|
254
353
|
addAuditoriumPreset(layout, room);
|
|
255
|
-
} else {
|
|
354
|
+
} else if (room.type === "gym") {
|
|
256
355
|
addGymPreset(layout, room);
|
|
356
|
+
} else if (room.type === "dining") {
|
|
357
|
+
addDiningPreset(layout, room);
|
|
358
|
+
} else if (room.type === "cafe") {
|
|
359
|
+
addCafePreset(layout, room);
|
|
360
|
+
} else {
|
|
361
|
+
addSkyGardenPreset(layout, room);
|
|
257
362
|
}
|
|
258
363
|
}
|
|
259
364
|
|
|
260
365
|
applyAdaptiveSceneBounds(layout);
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
366
|
+
return {
|
|
367
|
+
id: floor.id,
|
|
368
|
+
name: floor.name,
|
|
369
|
+
level: floor.level,
|
|
370
|
+
elevation,
|
|
371
|
+
rooms: layout.rooms,
|
|
372
|
+
floorTiles: layout.scene.floorTiles,
|
|
373
|
+
walls: layout.scene.walls,
|
|
374
|
+
components: layout.components,
|
|
375
|
+
scene: {
|
|
376
|
+
width: layout.scene.width,
|
|
377
|
+
depth: layout.scene.depth,
|
|
378
|
+
center: layout.scene.center,
|
|
379
|
+
floorTileSize: layout.scene.floorTileSize,
|
|
380
|
+
},
|
|
381
|
+
anchors: {
|
|
382
|
+
seats: layout.anchors.seats,
|
|
383
|
+
zones: layout.anchors.zones,
|
|
384
|
+
wallDecorations: layout.anchors.wallDecorations,
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function resolveBuildingConnectors(
|
|
390
|
+
connectors: NormalizedOfficeConnectorConfig[],
|
|
391
|
+
floors: FloorResolvedLayout[],
|
|
392
|
+
): ResolvedBuildingConnector[] {
|
|
393
|
+
const floorsById = new Map(floors.map((floor) => [floor.id, floor]));
|
|
394
|
+
return connectors.map((connector) => {
|
|
395
|
+
const stops = connector.serves.map((floorId) => {
|
|
396
|
+
const floor = floorsById.get(floorId);
|
|
397
|
+
if (!floor) {
|
|
398
|
+
throw new Error(`Agent game office connector ${connector.id} serves unresolved floor: ${floorId}`);
|
|
399
|
+
}
|
|
400
|
+
const stop = resolveElevatorStop(connector.id, floor);
|
|
401
|
+
addElevatorStopComponents(floor, connector.id, stop);
|
|
402
|
+
return stop;
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
id: connector.id,
|
|
407
|
+
type: connector.type,
|
|
408
|
+
name: connector.name,
|
|
409
|
+
serves: connector.serves,
|
|
410
|
+
stops,
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function resolveElevatorStop(
|
|
416
|
+
connectorId: string,
|
|
417
|
+
floor: ResolvedOfficeFloor,
|
|
418
|
+
): ResolvedElevatorStop {
|
|
419
|
+
const bounds = getFloorBounds(floor);
|
|
420
|
+
const x = bounds.minX + ELEVATOR_CORE_MARGIN_X;
|
|
421
|
+
const z = bounds.minZ + ELEVATOR_CORE_MARGIN_Z;
|
|
422
|
+
return {
|
|
423
|
+
floorId: floor.id,
|
|
424
|
+
entryNodeId: `${connectorId}:${floor.id}:entry`,
|
|
425
|
+
cabinNodeId: `${connectorId}:${floor.id}:cabin`,
|
|
426
|
+
entryPosition: { x, y: floor.elevation, z: z + ELEVATOR_ENTRY_OFFSET_Z },
|
|
427
|
+
cabinPosition: { x, y: floor.elevation, z },
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function addElevatorStopComponents(
|
|
432
|
+
floor: FloorResolvedLayout,
|
|
433
|
+
connectorId: string,
|
|
434
|
+
stop: ResolvedElevatorStop,
|
|
435
|
+
) {
|
|
436
|
+
floor.components.push(
|
|
437
|
+
{
|
|
438
|
+
id: `${connectorId}:${floor.id}:door`,
|
|
439
|
+
kind: "elevatorDoor",
|
|
440
|
+
floorId: floor.id,
|
|
441
|
+
roomId: null,
|
|
442
|
+
position: { x: stop.cabinPosition.x, z: stop.cabinPosition.z },
|
|
443
|
+
props: { width: 1.5, depth: 0.18, height: 2.4 },
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: `${connectorId}:${floor.id}:frame`,
|
|
447
|
+
kind: "elevatorFrame",
|
|
448
|
+
floorId: floor.id,
|
|
449
|
+
roomId: null,
|
|
450
|
+
position: { x: stop.cabinPosition.x, z: stop.cabinPosition.z },
|
|
451
|
+
props: { width: 2.2, depth: 1.1, height: 2.8 },
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: `${connectorId}:${floor.id}:indicator`,
|
|
455
|
+
kind: "elevatorIndicator",
|
|
456
|
+
floorId: floor.id,
|
|
457
|
+
roomId: null,
|
|
458
|
+
position: { x: stop.cabinPosition.x - 1.35, y: 2.6, z: stop.cabinPosition.z },
|
|
459
|
+
props: { width: 0.45, depth: 0.08, height: 0.3 },
|
|
460
|
+
},
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function getFloorBounds(floor: ResolvedOfficeFloor): { minX: number; maxX: number; minZ: number; maxZ: number } {
|
|
465
|
+
const bounds = createEmptyBounds();
|
|
466
|
+
for (const room of floor.rooms) {
|
|
467
|
+
const edges = getRoomEdges(room);
|
|
468
|
+
includeBounds(bounds, edges.minX, edges.minZ);
|
|
469
|
+
includeBounds(bounds, edges.maxX, edges.maxZ);
|
|
470
|
+
}
|
|
471
|
+
if (!Number.isFinite(bounds.minX)) {
|
|
472
|
+
return {
|
|
473
|
+
minX: OFFICE_FLOOR_CENTER_X - OFFICE_FLOOR_WIDTH / 2,
|
|
474
|
+
maxX: OFFICE_FLOOR_CENTER_X + OFFICE_FLOOR_WIDTH / 2,
|
|
475
|
+
minZ: -OFFICE_FLOOR_DEPTH / 2,
|
|
476
|
+
maxZ: OFFICE_FLOOR_DEPTH / 2,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return bounds;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function buildNavigationGraph(
|
|
483
|
+
floors: FloorResolvedLayout[],
|
|
484
|
+
connectors: ResolvedBuildingConnector[],
|
|
485
|
+
offstage: ResolvedOffstageAnchor,
|
|
486
|
+
): ResolvedBuildingNavigationGraph {
|
|
487
|
+
const nodes: NavigationNode[] = [];
|
|
488
|
+
const edges: NavigationEdge[] = [];
|
|
489
|
+
|
|
490
|
+
for (const floor of floors) {
|
|
491
|
+
for (const anchor of floor.anchors.seats) {
|
|
492
|
+
nodes.push({
|
|
493
|
+
id: anchor.id,
|
|
494
|
+
floorId: anchor.floorId,
|
|
495
|
+
kind: "seat",
|
|
496
|
+
anchorId: anchor.id,
|
|
497
|
+
position: { x: anchor.position.x, y: floor.elevation, z: anchor.position.z },
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
for (const anchor of floor.anchors.zones) {
|
|
501
|
+
nodes.push({
|
|
502
|
+
id: `zone:${anchor.id}`,
|
|
503
|
+
floorId: anchor.floorId,
|
|
504
|
+
kind: "zone",
|
|
505
|
+
anchorId: anchor.id,
|
|
506
|
+
position: { x: anchor.position.x, y: floor.elevation, z: anchor.position.z },
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
nodes.push({
|
|
512
|
+
id: offstage.id,
|
|
513
|
+
floorId: null,
|
|
514
|
+
kind: "offstage",
|
|
515
|
+
anchorId: offstage.id,
|
|
516
|
+
position: offstage.position,
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
for (const connector of connectors) {
|
|
520
|
+
for (const stop of connector.stops) {
|
|
521
|
+
nodes.push(
|
|
522
|
+
{
|
|
523
|
+
id: stop.entryNodeId,
|
|
524
|
+
floorId: stop.floorId,
|
|
525
|
+
kind: "connector-entry",
|
|
526
|
+
connectorId: connector.id,
|
|
527
|
+
position: stop.entryPosition,
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
id: stop.cabinNodeId,
|
|
531
|
+
floorId: stop.floorId,
|
|
532
|
+
kind: "connector-cabin",
|
|
533
|
+
connectorId: connector.id,
|
|
534
|
+
position: stop.cabinPosition,
|
|
535
|
+
},
|
|
536
|
+
);
|
|
537
|
+
pushBidirectionalWalkEdge(edges, stop.entryNodeId, stop.cabinNodeId, stop.floorId);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
for (let leftIndex = 0; leftIndex < connector.stops.length; leftIndex += 1) {
|
|
541
|
+
for (let rightIndex = leftIndex + 1; rightIndex < connector.stops.length; rightIndex += 1) {
|
|
542
|
+
const left = connector.stops[leftIndex]!;
|
|
543
|
+
const right = connector.stops[rightIndex]!;
|
|
544
|
+
pushBidirectionalElevatorEdge(edges, connector.id, left, right);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
for (const floor of floors) {
|
|
550
|
+
const floorNodes = nodes.filter((node) =>
|
|
551
|
+
node.floorId === floor.id &&
|
|
552
|
+
(node.kind === "seat" || node.kind === "zone" || node.kind === "connector-entry")
|
|
553
|
+
);
|
|
554
|
+
for (let leftIndex = 0; leftIndex < floorNodes.length; leftIndex += 1) {
|
|
555
|
+
for (let rightIndex = leftIndex + 1; rightIndex < floorNodes.length; rightIndex += 1) {
|
|
556
|
+
pushBidirectionalWalkEdge(edges, floorNodes[leftIndex]!.id, floorNodes[rightIndex]!.id, floor.id);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return { nodes, edges };
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function pushBidirectionalWalkEdge(
|
|
565
|
+
edges: NavigationEdge[],
|
|
566
|
+
left: string,
|
|
567
|
+
right: string,
|
|
568
|
+
floorId: string,
|
|
569
|
+
) {
|
|
570
|
+
edges.push(
|
|
571
|
+
{ kind: "walk", from: left, to: right, floorId },
|
|
572
|
+
{ kind: "walk", from: right, to: left, floorId },
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function pushBidirectionalElevatorEdge(
|
|
577
|
+
edges: NavigationEdge[],
|
|
578
|
+
connectorId: string,
|
|
579
|
+
left: ResolvedElevatorStop,
|
|
580
|
+
right: ResolvedElevatorStop,
|
|
581
|
+
) {
|
|
582
|
+
edges.push(
|
|
583
|
+
{
|
|
584
|
+
kind: "elevator",
|
|
585
|
+
from: left.cabinNodeId,
|
|
586
|
+
to: right.cabinNodeId,
|
|
587
|
+
connectorId,
|
|
588
|
+
fromFloorId: left.floorId,
|
|
589
|
+
toFloorId: right.floorId,
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
kind: "elevator",
|
|
593
|
+
from: right.cabinNodeId,
|
|
594
|
+
to: left.cabinNodeId,
|
|
595
|
+
connectorId,
|
|
596
|
+
fromFloorId: right.floorId,
|
|
597
|
+
toFloorId: left.floorId,
|
|
598
|
+
},
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function assembleBuildingLayout(
|
|
603
|
+
floors: FloorResolvedLayout[],
|
|
604
|
+
connectors: ResolvedBuildingConnector[],
|
|
605
|
+
offstage: ResolvedOffstageAnchor,
|
|
606
|
+
): ResolvedOfficeLayout {
|
|
607
|
+
const rooms = floors.flatMap((floor) => floor.rooms);
|
|
608
|
+
const components = floors.flatMap((floor) => floor.components);
|
|
609
|
+
const floorTiles = floors.flatMap((floor) => floor.floorTiles);
|
|
610
|
+
const walls = floors.flatMap((floor) => floor.walls);
|
|
611
|
+
const aggregateOffstage = createAggregateOffstageAnchor(offstage);
|
|
612
|
+
const seats = [...floors.flatMap((floor) => floor.anchors.seats), aggregateOffstage];
|
|
613
|
+
const zones = [...floors.flatMap((floor) => floor.anchors.zones), aggregateOffstage];
|
|
614
|
+
const wallDecorations = floors.flatMap((floor) => floor.anchors.wallDecorations);
|
|
615
|
+
const bounds = createEmptyBounds();
|
|
616
|
+
|
|
617
|
+
for (const room of rooms) {
|
|
618
|
+
const edges = getRoomEdges(room);
|
|
619
|
+
includeBounds(bounds, edges.minX, edges.minZ);
|
|
620
|
+
includeBounds(bounds, edges.maxX, edges.maxZ);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const width = Number.isFinite(bounds.minX) ? bounds.maxX - bounds.minX : OFFICE_FLOOR_WIDTH;
|
|
624
|
+
const depth = Number.isFinite(bounds.minZ) ? bounds.maxZ - bounds.minZ : OFFICE_FLOOR_DEPTH;
|
|
625
|
+
const center = Number.isFinite(bounds.minX)
|
|
626
|
+
? { x: (bounds.minX + bounds.maxX) / 2, z: (bounds.minZ + bounds.maxZ) / 2 }
|
|
627
|
+
: { x: OFFICE_FLOOR_CENTER_X, z: 0 };
|
|
628
|
+
const maxElevation = Math.max(0, ...floors.map((floor) => floor.elevation));
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
building: {
|
|
632
|
+
floors,
|
|
633
|
+
connectors,
|
|
634
|
+
navigation: buildNavigationGraph(floors, connectors, offstage),
|
|
635
|
+
},
|
|
636
|
+
scene: {
|
|
637
|
+
width,
|
|
638
|
+
height: maxElevation + FLOOR_ELEVATION_STEP,
|
|
639
|
+
depth,
|
|
640
|
+
center,
|
|
641
|
+
floorTileSize: OFFICE_FLOOR_TILE_SIZE,
|
|
642
|
+
floorTiles,
|
|
643
|
+
walls,
|
|
644
|
+
lights: [
|
|
645
|
+
{ id: "ambient", kind: "ambient", color: 0xffffff, intensity: 0.7 },
|
|
646
|
+
{ id: "directional", kind: "directional", color: 0xffffff, intensity: 1.2, position: { x: 10, y: 18 + maxElevation, z: 8 } },
|
|
647
|
+
],
|
|
648
|
+
},
|
|
649
|
+
rooms,
|
|
650
|
+
components,
|
|
651
|
+
anchors: {
|
|
652
|
+
seats,
|
|
653
|
+
zones,
|
|
654
|
+
wallDecorations,
|
|
655
|
+
offstage,
|
|
656
|
+
},
|
|
657
|
+
};
|
|
264
658
|
}
|
|
265
659
|
|
|
266
660
|
export function resolveOfficeAgentAnchor(
|
|
267
661
|
layout: ResolvedOfficeLayout,
|
|
268
662
|
agent: AgentGameOfficeAgent,
|
|
269
663
|
): ResolvedSeatAnchor {
|
|
664
|
+
if (agent.zoneId === "offstage") {
|
|
665
|
+
return layout.anchors.offstage as unknown as ResolvedSeatAnchor;
|
|
666
|
+
}
|
|
667
|
+
|
|
270
668
|
const candidates = sortPreferredAnchors(layout, layout.anchors.seats
|
|
271
669
|
.filter((anchor) => anchor.zoneId === agent.zoneId)
|
|
272
670
|
.sort(compareAnchorId), agent.zoneId);
|
|
@@ -281,7 +679,7 @@ export function resolveOfficeAgentAnchor(
|
|
|
281
679
|
return available[stableHash(agent.id) % available.length]!;
|
|
282
680
|
}
|
|
283
681
|
|
|
284
|
-
return layout.anchors.offstage;
|
|
682
|
+
return layout.anchors.offstage as unknown as ResolvedSeatAnchor;
|
|
285
683
|
}
|
|
286
684
|
|
|
287
685
|
function sortPreferredAnchors(
|
|
@@ -307,7 +705,7 @@ function addOfficePreset(layout: MutableResolvedLayout, room: ResolvedOfficeRoom
|
|
|
307
705
|
addComponent(layout, room.id, "desk", `desk-${index + 1}`, slot.desk.x, slot.desk.z);
|
|
308
706
|
addComponent(layout, room.id, "officeChair", `desk-chair-${index + 1}`, slot.seat.x, slot.seat.z, { rotation: slot.rotation });
|
|
309
707
|
addComponent(layout, room.id, "monitor", `monitor-${index + 1}`, slot.desk.x, slot.desk.z - 0.4);
|
|
310
|
-
addSeat(layout, room.id, `desk-${index + 1}`, "desk", slot.seat.x, slot.seat.z, { x:
|
|
708
|
+
addSeat(layout, room.id, `desk-${index + 1}`, "desk", slot.seat.x, slot.seat.z, { x: slot.desk.x, z: slot.desk.z - 0.4 });
|
|
311
709
|
});
|
|
312
710
|
|
|
313
711
|
MEETING_ROOMS.forEach((meetingRoom, roomIndex) => {
|
|
@@ -462,8 +860,164 @@ function addGymPreset(layout: MutableResolvedLayout, room: ResolvedOfficeRoom) {
|
|
|
462
860
|
}
|
|
463
861
|
}
|
|
464
862
|
|
|
863
|
+
function addDiningPreset(layout: MutableResolvedLayout, room: ResolvedOfficeRoom) {
|
|
864
|
+
const tableCenters = [
|
|
865
|
+
{ x: scaleOfficeX(42), z: scaleOfficeZ(-5.2) },
|
|
866
|
+
{ x: scaleOfficeX(50), z: scaleOfficeZ(-5.2) },
|
|
867
|
+
{ x: scaleOfficeX(42), z: scaleOfficeZ(2.8) },
|
|
868
|
+
{ x: scaleOfficeX(50), z: scaleOfficeZ(2.8) },
|
|
869
|
+
];
|
|
870
|
+
const seatsPerTable = 4;
|
|
871
|
+
const maxSeats = Math.min(room.capacity, tableCenters.length * seatsPerTable);
|
|
872
|
+
|
|
873
|
+
tableCenters.forEach((center, tableIndex) => {
|
|
874
|
+
addComponent(layout, room.id, "diningTable", `dining-table-${tableIndex + 1}`, center.x, center.z);
|
|
875
|
+
addComponent(layout, room.id, "tableCup", `dining-cup-${tableIndex + 1}-1`, center.x - 0.45, center.z - 0.28);
|
|
876
|
+
addComponent(layout, room.id, "tableCup", `dining-cup-${tableIndex + 1}-2`, center.x + 0.45, center.z + 0.28);
|
|
877
|
+
|
|
878
|
+
[
|
|
879
|
+
{ x: center.x - 1.55, z: center.z, rotation: -Math.PI / 2 },
|
|
880
|
+
{ x: center.x + 1.55, z: center.z, rotation: Math.PI / 2 },
|
|
881
|
+
{ x: center.x, z: center.z - 1.25, rotation: 0 },
|
|
882
|
+
{ x: center.x, z: center.z + 1.25, rotation: Math.PI },
|
|
883
|
+
].forEach((seat, seatIndex) => {
|
|
884
|
+
const seatNumber = tableIndex * seatsPerTable + seatIndex + 1;
|
|
885
|
+
if (seatNumber > maxSeats) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
addComponent(layout, room.id, "officeChair", `dining-chair-${seatNumber}`, seat.x, seat.z, {
|
|
889
|
+
rotation: seat.rotation,
|
|
890
|
+
});
|
|
891
|
+
addSeat(layout, room.id, `dining-${seatNumber}`, seatNumber % 3 === 0 ? "lounge" : "pantry", seat.x, seat.z, center);
|
|
892
|
+
});
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
addComponent(layout, room.id, "teaCounter", "serving-counter", scaleOfficeX(58.5), scaleOfficeZ(-8.8));
|
|
896
|
+
addComponent(layout, room.id, "waterDispenser", "dining-water-dispenser", scaleOfficeX(61.2), scaleOfficeZ(-8.7));
|
|
897
|
+
addComponent(layout, room.id, "kitchenCounter", "kitchen-counter", scaleOfficeX(57.8), scaleOfficeZ(8.8));
|
|
898
|
+
addComponent(layout, room.id, "kitchenStove", "kitchen-stove", scaleOfficeX(61.0), scaleOfficeZ(8.7));
|
|
899
|
+
addComponent(layout, room.id, "kitchenFridge", "kitchen-fridge", scaleOfficeX(53.9), scaleOfficeZ(8.8));
|
|
900
|
+
addSeat(layout, room.id, "serving-queue-1", "pantry", scaleOfficeX(57.5), scaleOfficeZ(-6.6), {
|
|
901
|
+
x: scaleOfficeX(58.5),
|
|
902
|
+
z: scaleOfficeZ(-8.8),
|
|
903
|
+
});
|
|
904
|
+
addSeat(layout, room.id, "serving-queue-2", "pantry", scaleOfficeX(60.2), scaleOfficeZ(-6.6), {
|
|
905
|
+
x: scaleOfficeX(58.5),
|
|
906
|
+
z: scaleOfficeZ(-8.8),
|
|
907
|
+
});
|
|
908
|
+
addSeat(layout, room.id, "kitchen-prep-1", "pantry", scaleOfficeX(57.2), scaleOfficeZ(6.8), {
|
|
909
|
+
x: scaleOfficeX(57.8),
|
|
910
|
+
z: scaleOfficeZ(8.8),
|
|
911
|
+
});
|
|
912
|
+
addSeat(layout, room.id, "kitchen-prep-2", "pantry", scaleOfficeX(60.4), scaleOfficeZ(6.8), {
|
|
913
|
+
x: scaleOfficeX(61.0),
|
|
914
|
+
z: scaleOfficeZ(8.7),
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function addCafePreset(layout: MutableResolvedLayout, room: ResolvedOfficeRoom) {
|
|
919
|
+
const counterX = scaleOfficeX(55.7);
|
|
920
|
+
const counterZ = scaleOfficeZ(-8.8);
|
|
921
|
+
const tableCenters = [
|
|
922
|
+
{ x: scaleOfficeX(40.5), z: scaleOfficeZ(-4.9) },
|
|
923
|
+
{ x: scaleOfficeX(47.5), z: scaleOfficeZ(-4.9) },
|
|
924
|
+
{ x: scaleOfficeX(40.5), z: scaleOfficeZ(3.1) },
|
|
925
|
+
{ x: scaleOfficeX(47.5), z: scaleOfficeZ(3.1) },
|
|
926
|
+
];
|
|
927
|
+
const seatsPerTable = 3;
|
|
928
|
+
const maxTableSeats = Math.min(room.capacity, tableCenters.length * seatsPerTable);
|
|
929
|
+
|
|
930
|
+
addComponent(layout, room.id, "cafeCounter", "coffee-counter", counterX, counterZ);
|
|
931
|
+
addComponent(layout, room.id, "waterDispenser", "cafe-water-dispenser", scaleOfficeX(60.2), scaleOfficeZ(-8.7));
|
|
932
|
+
addComponent(layout, room.id, "tableCup", "counter-cup-1", counterX - 0.8, counterZ - 0.15);
|
|
933
|
+
addComponent(layout, room.id, "tableCup", "counter-cup-2", counterX - 0.35, counterZ + 0.1);
|
|
934
|
+
addSeat(layout, room.id, "barista-1", "pantry", counterX - 0.4, scaleOfficeZ(-7.35), { x: counterX, z: counterZ });
|
|
935
|
+
addSeat(layout, room.id, "pickup-1", "pantry", counterX + 1.25, scaleOfficeZ(-6.7), { x: counterX, z: counterZ });
|
|
936
|
+
|
|
937
|
+
tableCenters.forEach((center, tableIndex) => {
|
|
938
|
+
addComponent(layout, room.id, "cafeTable", `cafe-table-${tableIndex + 1}`, center.x, center.z);
|
|
939
|
+
addComponent(layout, room.id, "tableCup", `cafe-cup-${tableIndex + 1}-1`, center.x - 0.28, center.z - 0.18);
|
|
940
|
+
addComponent(layout, room.id, "tableCup", `cafe-cup-${tableIndex + 1}-2`, center.x + 0.25, center.z + 0.2);
|
|
941
|
+
|
|
942
|
+
[
|
|
943
|
+
{ x: center.x - 1.25, z: center.z, rotation: -Math.PI / 2 },
|
|
944
|
+
{ x: center.x + 1.25, z: center.z, rotation: Math.PI / 2 },
|
|
945
|
+
{ x: center.x, z: center.z + 1.08, rotation: Math.PI },
|
|
946
|
+
].forEach((seat, seatIndex) => {
|
|
947
|
+
const seatNumber = tableIndex * seatsPerTable + seatIndex + 1;
|
|
948
|
+
if (seatNumber > maxTableSeats) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
addComponent(layout, room.id, "officeChair", `cafe-chair-${seatNumber}`, seat.x, seat.z, {
|
|
952
|
+
rotation: seat.rotation,
|
|
953
|
+
});
|
|
954
|
+
addSeat(layout, room.id, `cafe-seat-${seatNumber}`, "lounge", seat.x, seat.z, center);
|
|
955
|
+
});
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function addSkyGardenPreset(layout: MutableResolvedLayout, room: ResolvedOfficeRoom) {
|
|
960
|
+
const centerX = scaleOfficeX(16);
|
|
961
|
+
const centerZ = scaleOfficeZ(0);
|
|
962
|
+
addComponent(layout, room.id, "gardenPath", "main-path", centerX, centerZ, {
|
|
963
|
+
props: { width: 31, depth: 3.2 },
|
|
964
|
+
});
|
|
965
|
+
addComponent(layout, room.id, "gardenPath", "cross-path", centerX, centerZ, {
|
|
966
|
+
rotation: Math.PI / 2,
|
|
967
|
+
props: { width: 14, depth: 2.6 },
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
[
|
|
971
|
+
{ id: "north-west", x: scaleOfficeX(-7), z: scaleOfficeZ(-7.6) },
|
|
972
|
+
{ id: "north-east", x: scaleOfficeX(39), z: scaleOfficeZ(-7.6) },
|
|
973
|
+
{ id: "south-west", x: scaleOfficeX(-7), z: scaleOfficeZ(7.6) },
|
|
974
|
+
{ id: "south-east", x: scaleOfficeX(39), z: scaleOfficeZ(7.6) },
|
|
975
|
+
].forEach((planter) => {
|
|
976
|
+
addComponent(layout, room.id, "gardenPlanter", `planter-${planter.id}`, planter.x, planter.z);
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
[
|
|
980
|
+
{ id: "tree-1", x: scaleOfficeX(2), z: scaleOfficeZ(-5.4) },
|
|
981
|
+
{ id: "tree-2", x: scaleOfficeX(30), z: scaleOfficeZ(-5.4) },
|
|
982
|
+
{ id: "tree-3", x: scaleOfficeX(2), z: scaleOfficeZ(5.4) },
|
|
983
|
+
{ id: "tree-4", x: scaleOfficeX(30), z: scaleOfficeZ(5.4) },
|
|
984
|
+
].forEach((tree) => {
|
|
985
|
+
addComponent(layout, room.id, "gardenTree", tree.id, tree.x, tree.z);
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
[
|
|
989
|
+
{ id: "bench-north", x: centerX, z: scaleOfficeZ(-5.7), rotation: 0 },
|
|
990
|
+
{ id: "bench-south", x: centerX, z: scaleOfficeZ(5.7), rotation: Math.PI },
|
|
991
|
+
{ id: "bench-west", x: scaleOfficeX(5.5), z: centerZ, rotation: -Math.PI / 2 },
|
|
992
|
+
{ id: "bench-east", x: scaleOfficeX(26.5), z: centerZ, rotation: Math.PI / 2 },
|
|
993
|
+
].forEach((bench, index) => {
|
|
994
|
+
addComponent(layout, room.id, "gardenBench", bench.id, bench.x, bench.z, {
|
|
995
|
+
rotation: bench.rotation,
|
|
996
|
+
});
|
|
997
|
+
addSeat(layout, room.id, `garden-seat-${index + 1}`, index < 2 ? "reading" : "lounge", bench.x, bench.z, {
|
|
998
|
+
x: centerX,
|
|
999
|
+
z: centerZ,
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
const extraCount = Math.max(0, Math.min(room.capacity, 12) - 4);
|
|
1004
|
+
for (let index = 0; index < extraCount; index += 1) {
|
|
1005
|
+
const angle = (index / Math.max(1, extraCount)) * Math.PI * 2;
|
|
1006
|
+
addSeat(
|
|
1007
|
+
layout,
|
|
1008
|
+
room.id,
|
|
1009
|
+
`garden-open-${index + 1}`,
|
|
1010
|
+
index % 2 === 0 ? "lounge" : "reading",
|
|
1011
|
+
centerX + Math.cos(angle) * 4.2,
|
|
1012
|
+
centerZ + Math.sin(angle) * 3.6,
|
|
1013
|
+
{ x: centerX, z: centerZ },
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
465
1018
|
function createRoom(
|
|
466
1019
|
id: string,
|
|
1020
|
+
floorId: string,
|
|
467
1021
|
type: AgentGameOfficeRoomType,
|
|
468
1022
|
capacity: number,
|
|
469
1023
|
index: number,
|
|
@@ -474,6 +1028,7 @@ function createRoom(
|
|
|
474
1028
|
const isOffice = type === "office";
|
|
475
1029
|
return {
|
|
476
1030
|
id,
|
|
1031
|
+
floorId,
|
|
477
1032
|
type,
|
|
478
1033
|
capacity,
|
|
479
1034
|
bounds: {
|
|
@@ -519,6 +1074,7 @@ function applyAdaptiveSceneBounds(layout: MutableResolvedLayout) {
|
|
|
519
1074
|
if (!bounds) {
|
|
520
1075
|
return {
|
|
521
1076
|
id: `floor-${room.id}`,
|
|
1077
|
+
floorId: room.floorId,
|
|
522
1078
|
color: getRoomFloorColor(room.type),
|
|
523
1079
|
x: center.x,
|
|
524
1080
|
z: center.z,
|
|
@@ -539,6 +1095,7 @@ function applyAdaptiveSceneBounds(layout: MutableResolvedLayout) {
|
|
|
539
1095
|
};
|
|
540
1096
|
return {
|
|
541
1097
|
id: `floor-${room.id}`,
|
|
1098
|
+
floorId: room.floorId,
|
|
542
1099
|
color: getRoomFloorColor(room.type),
|
|
543
1100
|
x: room.bounds.x,
|
|
544
1101
|
z: room.bounds.z,
|
|
@@ -563,6 +1120,12 @@ function getRoomFloorColor(type: AgentGameOfficeRoomType): number {
|
|
|
563
1120
|
return 0xdbe4ec;
|
|
564
1121
|
case "gym":
|
|
565
1122
|
return 0xd7e3d2;
|
|
1123
|
+
case "dining":
|
|
1124
|
+
return 0xeadfc8;
|
|
1125
|
+
case "cafe":
|
|
1126
|
+
return 0xe8ddcf;
|
|
1127
|
+
case "skyGarden":
|
|
1128
|
+
return 0xd9ead2;
|
|
566
1129
|
}
|
|
567
1130
|
}
|
|
568
1131
|
|
|
@@ -808,6 +1371,7 @@ function createRoomWall(
|
|
|
808
1371
|
const isHorizontal = side === "north" || side === "south";
|
|
809
1372
|
return {
|
|
810
1373
|
id: `${room.id}:${side}-wall`,
|
|
1374
|
+
floorId: room.floorId,
|
|
811
1375
|
roomId: room.id,
|
|
812
1376
|
side,
|
|
813
1377
|
color: 0xe8e0d0,
|
|
@@ -883,6 +1447,26 @@ function getComponentFootprint(component: ResolvedOfficeComponentInstance): { wi
|
|
|
883
1447
|
return { width: 1.4, depth: 0.4 };
|
|
884
1448
|
case "meetingTable":
|
|
885
1449
|
return { width: 5, depth: component.props?.depth ?? 3 };
|
|
1450
|
+
case "diningTable":
|
|
1451
|
+
return { width: 2.6, depth: 1.8 };
|
|
1452
|
+
case "cafeCounter":
|
|
1453
|
+
return { width: 4.2, depth: 0.9 };
|
|
1454
|
+
case "cafeTable":
|
|
1455
|
+
return { width: 1.7, depth: 1.7 };
|
|
1456
|
+
case "kitchenCounter":
|
|
1457
|
+
return { width: 4.8, depth: 0.9 };
|
|
1458
|
+
case "kitchenStove":
|
|
1459
|
+
return { width: 1.5, depth: 0.9 };
|
|
1460
|
+
case "kitchenFridge":
|
|
1461
|
+
return { width: 1.1, depth: 0.9 };
|
|
1462
|
+
case "gardenPlanter":
|
|
1463
|
+
return { width: 4.4, depth: 1.2 };
|
|
1464
|
+
case "gardenTree":
|
|
1465
|
+
return { width: 1.8, depth: 1.8 };
|
|
1466
|
+
case "gardenBench":
|
|
1467
|
+
return { width: 3.2, depth: 1 };
|
|
1468
|
+
case "gardenPath":
|
|
1469
|
+
return { width: component.props?.width ?? 4, depth: component.props?.depth ?? 1 };
|
|
886
1470
|
case "meetingGlassRoom":
|
|
887
1471
|
return { width: 11.7 * OFFICE_LAYOUT_SCALE, depth: component.props?.depth ?? 8 * OFFICE_LAYOUT_SCALE };
|
|
888
1472
|
case "largeMeetingTable":
|
|
@@ -925,6 +1509,12 @@ function getComponentFootprint(component: ResolvedOfficeComponentInstance): { wi
|
|
|
925
1509
|
return { width: 1.4, depth: 3.2 };
|
|
926
1510
|
case "gymMirror":
|
|
927
1511
|
return { width: component.props?.width ?? 5.2, depth: 0.2 };
|
|
1512
|
+
case "elevatorDoor":
|
|
1513
|
+
return { width: component.props?.width ?? 1.5, depth: component.props?.depth ?? 0.18 };
|
|
1514
|
+
case "elevatorFrame":
|
|
1515
|
+
return { width: component.props?.width ?? 2.2, depth: component.props?.depth ?? 1.1 };
|
|
1516
|
+
case "elevatorIndicator":
|
|
1517
|
+
return { width: component.props?.width ?? 0.45, depth: component.props?.depth ?? 0.08 };
|
|
928
1518
|
case "glassWall":
|
|
929
1519
|
case "glassDoor":
|
|
930
1520
|
return { width: component.props?.width ?? 1, depth: component.props?.depth ?? 1 };
|
|
@@ -948,6 +1538,7 @@ function addComponent(
|
|
|
948
1538
|
layout.components.push({
|
|
949
1539
|
id: `${roomId}:${id}`,
|
|
950
1540
|
kind,
|
|
1541
|
+
floorId: resolveRoomFloorId(layout, roomId),
|
|
951
1542
|
roomId,
|
|
952
1543
|
position: { x, y: options.y, z },
|
|
953
1544
|
rotation: options.rotation,
|
|
@@ -970,6 +1561,7 @@ function addWallDecoration(
|
|
|
970
1561
|
layout.anchors.wallDecorations.push({
|
|
971
1562
|
id: `${room.id}:${id}`,
|
|
972
1563
|
roomId: room.id,
|
|
1564
|
+
floorId: room.floorId,
|
|
973
1565
|
position: { x, z },
|
|
974
1566
|
rotation,
|
|
975
1567
|
faceDirection,
|
|
@@ -985,12 +1577,13 @@ function addSeat(
|
|
|
985
1577
|
z: number,
|
|
986
1578
|
facing: { x: number; z: number },
|
|
987
1579
|
) {
|
|
988
|
-
const anchor = createAnchor(roomId, id, zoneId, roomId, x, z, facing);
|
|
1580
|
+
const anchor = createAnchor(resolveRoomFloorId(layout, roomId), roomId, id, zoneId, roomId, x, z, facing);
|
|
989
1581
|
layout.anchors.seats.push(anchor);
|
|
990
1582
|
layout.anchors.zones.push(anchor);
|
|
991
1583
|
}
|
|
992
1584
|
|
|
993
1585
|
function createAnchor(
|
|
1586
|
+
floorId: string,
|
|
994
1587
|
roomId: string,
|
|
995
1588
|
id: string,
|
|
996
1589
|
zoneId: AgentGameOfficeZoneId,
|
|
@@ -1002,12 +1595,50 @@ function createAnchor(
|
|
|
1002
1595
|
return {
|
|
1003
1596
|
id: `${roomId}:${id}`,
|
|
1004
1597
|
roomId: anchorRoomId,
|
|
1598
|
+
floorId,
|
|
1005
1599
|
zoneId,
|
|
1006
1600
|
position: { x, z },
|
|
1007
1601
|
facing,
|
|
1008
1602
|
};
|
|
1009
1603
|
}
|
|
1010
1604
|
|
|
1605
|
+
function createOffstageAnchor(): ResolvedOffstageAnchor {
|
|
1606
|
+
return {
|
|
1607
|
+
id: "offstage",
|
|
1608
|
+
roomId: null,
|
|
1609
|
+
floorId: null,
|
|
1610
|
+
zoneId: "offstage",
|
|
1611
|
+
position: { x: scaleOfficeX(-13), y: 0, z: scaleOfficeZ(8) },
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
function createAggregateOffstageAnchor(offstage: ResolvedOffstageAnchor): ResolvedSeatAnchor {
|
|
1616
|
+
return {
|
|
1617
|
+
id: offstage.id,
|
|
1618
|
+
roomId: "offstage",
|
|
1619
|
+
floorId: "offstage",
|
|
1620
|
+
zoneId: offstage.zoneId,
|
|
1621
|
+
position: { x: offstage.position.x, z: offstage.position.z },
|
|
1622
|
+
facing: { x: offstage.position.x, z: offstage.position.z },
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function resolveRoomId(room: NormalizedOfficeRoomConfig, useLegacyRoomIds: boolean): string {
|
|
1627
|
+
if (!useLegacyRoomIds) {
|
|
1628
|
+
return room.id;
|
|
1629
|
+
}
|
|
1630
|
+
const separatorIndex = room.id.indexOf(":");
|
|
1631
|
+
return separatorIndex === -1 ? room.id : room.id.slice(separatorIndex + 1);
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function resolveRoomFloorId(layout: MutableResolvedLayout, roomId: string): string {
|
|
1635
|
+
const floorId = layout.rooms.find((room) => room.id === roomId)?.floorId;
|
|
1636
|
+
if (!floorId) {
|
|
1637
|
+
throw new Error(`Unknown office room for floor-owned layout content: ${roomId}`);
|
|
1638
|
+
}
|
|
1639
|
+
return floorId;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1011
1642
|
function compareAnchorId(left: ResolvedSeatAnchor, right: ResolvedSeatAnchor): number {
|
|
1012
1643
|
return left.id.localeCompare(right.id);
|
|
1013
1644
|
}
|