@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
|
@@ -8,12 +8,20 @@ import type {
|
|
|
8
8
|
AgentGameOfficeResolvedOptions,
|
|
9
9
|
AgentGameOfficeSceneState,
|
|
10
10
|
AgentGameOfficeSnapshot,
|
|
11
|
+
AgentNavigationError,
|
|
11
12
|
} from "../../core/types";
|
|
12
13
|
import type { ResolvedOfficeLayout } from "../../layout";
|
|
13
14
|
import { applyAgentPose, updateAgentMotion } from "./agent-animation";
|
|
14
15
|
import { createAgentBodyInstancedLayer } from "./agent-body-instancing";
|
|
15
16
|
import { createAgentEffectInstancedLayer } from "./agent-effect-instancing";
|
|
16
|
-
import { resolveAgentFacingTarget
|
|
17
|
+
import { resolveAgentFacingTarget } from "./agent-layout";
|
|
18
|
+
import {
|
|
19
|
+
advanceAgentRouteState,
|
|
20
|
+
createAgentRouteState,
|
|
21
|
+
resolveAgentRouteFloorId,
|
|
22
|
+
resolveRouteTargetPosition,
|
|
23
|
+
type RenderAgentLocationState,
|
|
24
|
+
} from "./agent-route";
|
|
17
25
|
import {
|
|
18
26
|
type AgentLabelRecord,
|
|
19
27
|
createAgentLabel,
|
|
@@ -22,7 +30,13 @@ import {
|
|
|
22
30
|
updateAgentLabelPosition,
|
|
23
31
|
} from "./agent-label";
|
|
24
32
|
import { type AgentMeshParts, createAgentMesh } from "./agent-mesh";
|
|
25
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
OFFICE_FLOOR_FOCUS_DIM_OPACITY,
|
|
35
|
+
addLights,
|
|
36
|
+
applyOfficeFloorFocus,
|
|
37
|
+
buildOfficeScene,
|
|
38
|
+
disposeObject,
|
|
39
|
+
} from "./scene";
|
|
26
40
|
|
|
27
41
|
type AgentMeshRecord = {
|
|
28
42
|
id: string;
|
|
@@ -31,18 +45,20 @@ type AgentMeshRecord = {
|
|
|
31
45
|
mesh: AgentMeshParts;
|
|
32
46
|
label: AgentLabelRecord;
|
|
33
47
|
target: THREE.Vector3;
|
|
48
|
+
routeState: RenderAgentLocationState;
|
|
34
49
|
};
|
|
35
50
|
|
|
36
51
|
export const THREE_RENDERER_PIXEL_RATIO_LIMIT = 1.5;
|
|
37
52
|
export const THREE_RENDERER_TARGET_FPS = 30;
|
|
38
53
|
export const THREE_RENDERER_MIN_CAMERA_DISTANCE = 8;
|
|
39
|
-
export const THREE_RENDERER_MAX_CAMERA_DISTANCE =
|
|
40
|
-
export const THREE_RENDERER_INITIAL_CAMERA_ROTATION = { x: -0.
|
|
54
|
+
export const THREE_RENDERER_MAX_CAMERA_DISTANCE = 180;
|
|
55
|
+
export const THREE_RENDERER_INITIAL_CAMERA_ROTATION = { x: -0.57, y: 0.52, z: 0.31 } as const;
|
|
56
|
+
export const THREE_RENDERER_INITIAL_CAMERA_POSITION = { x: 130.17, y: 100.75, z: 123.18 } as const;
|
|
41
57
|
const THREE_RENDERER_CAMERA_PADDING = 1.16;
|
|
42
|
-
const
|
|
58
|
+
const OFFICE_CAMERA_MIN_BOUNDS_HEIGHT = 3.2;
|
|
43
59
|
const WORKING_FOLLOW_RESET_DELAY_MS = 30_000;
|
|
44
60
|
const WORK_EXIT_WAIT_MS = 30_000;
|
|
45
|
-
const WORKING_FOLLOW_CAMERA_OFFSET = new THREE.Vector3(
|
|
61
|
+
const WORKING_FOLLOW_CAMERA_OFFSET = new THREE.Vector3(14, 18, 20);
|
|
46
62
|
const OFFICE_CAMERA_TRANSITION_DURATION_MS = 600;
|
|
47
63
|
const OFFICE_CAMERA_UP = new THREE.Vector3(0, 1, 0);
|
|
48
64
|
|
|
@@ -238,6 +254,8 @@ export function mountThreeAgentGameOffice(
|
|
|
238
254
|
let previousFrameTime = performance.now();
|
|
239
255
|
let previousRenderTime = previousFrameTime;
|
|
240
256
|
let cameraTransition: OfficeCameraTransition | null = null;
|
|
257
|
+
let focusedFloorId = normalizeFocusedFloorId(_options.officeLayout, _options.focusedFloorId ?? null);
|
|
258
|
+
setFocusedFloor(focusedFloorId, false);
|
|
241
259
|
|
|
242
260
|
function update(snapshot: AgentGameOfficeSnapshot) {
|
|
243
261
|
latestSourceAgents = snapshot.agents;
|
|
@@ -262,11 +280,14 @@ export function mountThreeAgentGameOffice(
|
|
|
262
280
|
latestAgents.forEach((agent, index) => {
|
|
263
281
|
const record = agents.get(agent.id) ?? createAgentRecord(agent, index);
|
|
264
282
|
agents.set(agent.id, record);
|
|
265
|
-
|
|
283
|
+
record.routeState = createAgentRouteState(_options.officeLayout, agent, record.routeState);
|
|
284
|
+
const target = vectorFromLike(resolveRouteTargetPosition(record.routeState));
|
|
266
285
|
record.agent = agent;
|
|
267
286
|
record.renderIndex = index;
|
|
268
287
|
record.target.copy(target);
|
|
269
288
|
record.mesh.group.visible = agent.sceneState !== "offline";
|
|
289
|
+
record.mesh.group.userData.officeFloorId = resolveAgentRouteFloorId(_options.officeLayout, record.routeState);
|
|
290
|
+
record.label.root.style.opacity = String(resolveFocusedAgentOpacity(record));
|
|
270
291
|
updateAgentLabel(record.label, agent);
|
|
271
292
|
updateAgentLabelPosition(record.label, record.mesh.group.position, camera, renderer.domElement);
|
|
272
293
|
});
|
|
@@ -274,13 +295,16 @@ export function mountThreeAgentGameOffice(
|
|
|
274
295
|
|
|
275
296
|
function createAgentRecord(agent: AgentGameOfficeAgent, index: number): AgentMeshRecord {
|
|
276
297
|
const mesh = createAgentMesh(agent, index);
|
|
277
|
-
|
|
298
|
+
void index;
|
|
299
|
+
const routeState = createAgentRouteState(_options.officeLayout, agent, undefined);
|
|
300
|
+
const position = vectorFromLike(routeState.currentPosition);
|
|
278
301
|
mesh.group.position.copy(position);
|
|
279
302
|
scene.add(mesh.group);
|
|
280
303
|
|
|
281
304
|
const label = createAgentLabel(overlay);
|
|
305
|
+
label.root.style.opacity = String(resolveFocusedAgentOpacity({ routeState } as AgentMeshRecord));
|
|
282
306
|
|
|
283
|
-
return { id: agent.id, agent, renderIndex: index, mesh, label, target: position.clone() };
|
|
307
|
+
return { id: agent.id, agent, renderIndex: index, mesh, label, target: position.clone(), routeState };
|
|
284
308
|
}
|
|
285
309
|
|
|
286
310
|
function animate() {
|
|
@@ -312,7 +336,17 @@ export function mountThreeAgentGameOffice(
|
|
|
312
336
|
elapsedSeconds,
|
|
313
337
|
deltaSeconds,
|
|
314
338
|
});
|
|
339
|
+
record.routeState = {
|
|
340
|
+
...record.routeState,
|
|
341
|
+
currentPosition: vectorToLike(record.mesh.group.position),
|
|
342
|
+
};
|
|
343
|
+
if (!moving && record.routeState.activeRoute) {
|
|
344
|
+
record.routeState = advanceAgentRouteState(record.routeState, vectorToLike(record.mesh.group.position));
|
|
345
|
+
record.target.copy(vectorFromLike(resolveRouteTargetPosition(record.routeState)));
|
|
346
|
+
}
|
|
315
347
|
record.mesh.group.visible = record.agent.sceneState !== "offline";
|
|
348
|
+
record.mesh.group.userData.officeFloorId = resolveAgentRouteFloorId(_options.officeLayout, record.routeState);
|
|
349
|
+
record.label.root.style.opacity = String(resolveFocusedAgentOpacity(record));
|
|
316
350
|
applyAgentPose(record.mesh, record.agent, moving, elapsedSeconds);
|
|
317
351
|
if (moving || shouldUpdateLabels) {
|
|
318
352
|
updateAgentLabelPosition(record.label, record.mesh.group.position, camera, renderer.domElement);
|
|
@@ -324,10 +358,12 @@ export function mountThreeAgentGameOffice(
|
|
|
324
358
|
mesh: record.mesh,
|
|
325
359
|
renderIndex: record.renderIndex,
|
|
326
360
|
visible: record.agent.sceneState !== "offline",
|
|
361
|
+
opacity: resolveFocusedAgentOpacity(record),
|
|
327
362
|
})));
|
|
328
363
|
agentEffectLayer.update(Array.from(agents.values()).map((record) => ({
|
|
329
364
|
mesh: record.mesh,
|
|
330
365
|
visible: record.agent.sceneState !== "offline",
|
|
366
|
+
opacity: resolveFocusedAgentOpacity(record),
|
|
331
367
|
})));
|
|
332
368
|
renderer.render(scene, camera);
|
|
333
369
|
labelsDirty = false;
|
|
@@ -351,6 +387,16 @@ export function mountThreeAgentGameOffice(
|
|
|
351
387
|
|
|
352
388
|
return {
|
|
353
389
|
update,
|
|
390
|
+
resetCamera() {
|
|
391
|
+
workingFollowState = {
|
|
392
|
+
...workingFollowState,
|
|
393
|
+
selectedAgentId: null,
|
|
394
|
+
resetAtMs: null,
|
|
395
|
+
};
|
|
396
|
+
camera.zoom = 1;
|
|
397
|
+
camera.updateProjectionMatrix();
|
|
398
|
+
transitionOfficeCameraTo(createInitialOfficeCameraView(_options.officeLayout, camera.aspect, camera.fov));
|
|
399
|
+
},
|
|
354
400
|
focusAgent(agentId) {
|
|
355
401
|
if (!agentId) {
|
|
356
402
|
transitionOfficeCameraTo(createInitialOfficeCameraView(_options.officeLayout, camera.aspect, camera.fov));
|
|
@@ -361,6 +407,16 @@ export function mountThreeAgentGameOffice(
|
|
|
361
407
|
return;
|
|
362
408
|
}
|
|
363
409
|
transitionOfficeCameraTo(createFollowOfficeCameraView(record.mesh.group.position));
|
|
410
|
+
setFocusedFloor(resolveAgentRouteFloorId(_options.officeLayout, record.routeState));
|
|
411
|
+
},
|
|
412
|
+
focusFloor(floorId) {
|
|
413
|
+
setFocusedFloor(floorId);
|
|
414
|
+
},
|
|
415
|
+
getFocusedFloor() {
|
|
416
|
+
return focusedFloorId;
|
|
417
|
+
},
|
|
418
|
+
getNavigationErrors() {
|
|
419
|
+
return getCurrentNavigationErrors();
|
|
364
420
|
},
|
|
365
421
|
destroy() {
|
|
366
422
|
disposed = true;
|
|
@@ -387,6 +443,30 @@ export function mountThreeAgentGameOffice(
|
|
|
387
443
|
_options.onCameraChange?.(createOfficeCameraState(camera, controls));
|
|
388
444
|
}
|
|
389
445
|
|
|
446
|
+
function getCurrentNavigationErrors(): AgentNavigationError[] {
|
|
447
|
+
return Array.from(agents.values())
|
|
448
|
+
.map((record) => record.routeState.error)
|
|
449
|
+
.filter((error): error is AgentNavigationError => Boolean(error));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function resolveFocusedAgentOpacity(record: Pick<AgentMeshRecord, "routeState">): number {
|
|
453
|
+
if (!focusedFloorId) {
|
|
454
|
+
return 1;
|
|
455
|
+
}
|
|
456
|
+
const agentFloorId = resolveAgentRouteFloorId(_options.officeLayout, record.routeState);
|
|
457
|
+
return agentFloorId === focusedFloorId ? 1 : OFFICE_FLOOR_FOCUS_DIM_OPACITY;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function setFocusedFloor(floorId: string | null, emit = true) {
|
|
461
|
+
const nextFloorId = normalizeFocusedFloorId(_options.officeLayout, floorId);
|
|
462
|
+
focusedFloorId = nextFloorId;
|
|
463
|
+
applyOfficeFloorFocus(scene, _options.officeLayout, focusedFloorId);
|
|
464
|
+
labelsDirty = true;
|
|
465
|
+
if (emit) {
|
|
466
|
+
_options.onFocusedFloorChange?.(focusedFloorId);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
390
470
|
function syncWorkingFollowState(nowMs: number) {
|
|
391
471
|
const previousSelectedAgentId = workingFollowState.selectedAgentId;
|
|
392
472
|
workingFollowState = updateWorkingAgentFollowState(workingFollowState, latestAgents, nowMs);
|
|
@@ -432,6 +512,21 @@ export function mountThreeAgentGameOffice(
|
|
|
432
512
|
}
|
|
433
513
|
}
|
|
434
514
|
|
|
515
|
+
function vectorFromLike(value: { x: number; y: number; z: number }): THREE.Vector3 {
|
|
516
|
+
return new THREE.Vector3(value.x, value.y, value.z);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function vectorToLike(value: THREE.Vector3): { x: number; y: number; z: number } {
|
|
520
|
+
return { x: value.x, y: value.y, z: value.z };
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function normalizeFocusedFloorId(layout: ResolvedOfficeLayout, floorId: string | null): string | null {
|
|
524
|
+
if (!floorId) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
return layout.building.floors.some((floor) => floor.id === floorId) ? floorId : null;
|
|
528
|
+
}
|
|
529
|
+
|
|
435
530
|
export type OfficeCameraView = {
|
|
436
531
|
position: THREE.Vector3;
|
|
437
532
|
rotation: THREE.Euler;
|
|
@@ -496,21 +591,19 @@ export function createInitialOfficeCameraView(
|
|
|
496
591
|
aspect: number,
|
|
497
592
|
fovDegrees: number,
|
|
498
593
|
): OfficeCameraView {
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
594
|
+
const boundsHeight = resolveOfficeCameraBoundsHeight(layout);
|
|
595
|
+
const target = new THREE.Vector3(layout.scene.center.x, boundsHeight / 2, layout.scene.center.z);
|
|
596
|
+
const position = new THREE.Vector3(
|
|
597
|
+
THREE_RENDERER_INITIAL_CAMERA_POSITION.x,
|
|
598
|
+
THREE_RENDERER_INITIAL_CAMERA_POSITION.y,
|
|
599
|
+
THREE_RENDERER_INITIAL_CAMERA_POSITION.z,
|
|
503
600
|
);
|
|
504
|
-
const quaternion = new THREE.Quaternion().setFromEuler(rotation);
|
|
505
|
-
const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(quaternion).normalize();
|
|
506
|
-
const target = new THREE.Vector3(layout.scene.center.x, OFFICE_CAMERA_BOUNDS_HEIGHT / 2, layout.scene.center.z);
|
|
507
|
-
const preliminaryPosition = target.clone().sub(forward.clone());
|
|
508
601
|
const orbitRotation = new THREE.Euler();
|
|
509
|
-
const matrix = new THREE.Matrix4().lookAt(
|
|
602
|
+
const matrix = new THREE.Matrix4().lookAt(position, target, OFFICE_CAMERA_UP);
|
|
510
603
|
orbitRotation.setFromRotationMatrix(matrix);
|
|
511
604
|
const orbitQuaternion = new THREE.Quaternion().setFromEuler(orbitRotation);
|
|
512
|
-
const
|
|
513
|
-
const
|
|
605
|
+
const fittedDistance = resolveOfficeCameraDistance(layout, target, orbitQuaternion, aspect, fovDegrees);
|
|
606
|
+
const distance = Math.max(position.distanceTo(target), fittedDistance);
|
|
514
607
|
|
|
515
608
|
return {
|
|
516
609
|
position,
|
|
@@ -587,7 +680,7 @@ function resolveOfficeCameraDistance(
|
|
|
587
680
|
const corners: THREE.Vector3[] = [];
|
|
588
681
|
|
|
589
682
|
for (const x of [layout.scene.center.x - halfWidth, layout.scene.center.x + halfWidth]) {
|
|
590
|
-
for (const y of [0,
|
|
683
|
+
for (const y of [0, resolveOfficeCameraBoundsHeight(layout)]) {
|
|
591
684
|
for (const z of [layout.scene.center.z - halfDepth, layout.scene.center.z + halfDepth]) {
|
|
592
685
|
corners.push(new THREE.Vector3(x, y, z));
|
|
593
686
|
}
|
|
@@ -610,6 +703,10 @@ function resolveOfficeCameraDistance(
|
|
|
610
703
|
);
|
|
611
704
|
}
|
|
612
705
|
|
|
706
|
+
function resolveOfficeCameraBoundsHeight(layout: ResolvedOfficeLayout): number {
|
|
707
|
+
return Math.max(OFFICE_CAMERA_MIN_BOUNDS_HEIGHT, layout.scene.height);
|
|
708
|
+
}
|
|
709
|
+
|
|
613
710
|
export function createOfficeCameraState(
|
|
614
711
|
camera: THREE.PerspectiveCamera,
|
|
615
712
|
controls: OrbitControls,
|