@agent-os-lab/agent-game-sdk 0.1.10 → 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/office/core/types.ts +2 -0
- package/src/office/layout/resolver.ts +1 -1
- package/src/office/mount.ts +3 -0
- package/src/office/renderers/three/agent-body-instancing.ts +23 -6
- 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/mount.ts +12 -2
package/README.md
CHANGED
|
@@ -33,6 +33,7 @@ const view = await mountAgentGameOffice(container, {
|
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
view.focusAgent("agent-1");
|
|
36
|
+
view.resetCamera();
|
|
36
37
|
view.refreshAgents();
|
|
37
38
|
view.destroy();
|
|
38
39
|
```
|
|
@@ -57,6 +58,9 @@ export function Office() {
|
|
|
57
58
|
<button type="button" onClick={() => view?.refreshAgents()}>
|
|
58
59
|
Refresh agents
|
|
59
60
|
</button>
|
|
61
|
+
<button type="button" onClick={() => view?.resetCamera()}>
|
|
62
|
+
Reset view
|
|
63
|
+
</button>
|
|
60
64
|
<AgentGameOfficeView
|
|
61
65
|
renderer="three"
|
|
62
66
|
office={{
|
package/USAGE.md
CHANGED
|
@@ -203,6 +203,7 @@ const view = await mountAgentGameOffice(container, {
|
|
|
203
203
|
});
|
|
204
204
|
|
|
205
205
|
view.focusAgent("agent-1");
|
|
206
|
+
view.resetCamera();
|
|
206
207
|
view.refreshAgents();
|
|
207
208
|
view.destroy();
|
|
208
209
|
```
|
|
@@ -221,6 +222,8 @@ The layout packs up to three rooms per row, then starts another row. `capacity`
|
|
|
221
222
|
|
|
222
223
|
The SDK keeps runtime statuses simple. `idle` and `resting` agents are locally distributed across ambient anchors for lounge, pantry, gym, and reading areas. `entertaining` stays biased toward the review area. If a configured room does not provide the selected ambient zone, the SDK falls back to another available non-offstage anchor.
|
|
223
224
|
|
|
225
|
+
`resetCamera` returns the office view to its initial camera position, rotation, target, zoom, and follow state. It is intended for UI controls such as a reset-view button after a user pans, rotates, zooms, or follows an Agent.
|
|
226
|
+
|
|
224
227
|
`refreshAgents` asks Agent Game Runtime to refresh the tenant roster through the existing bootstrap flow. It is intended for UI controls such as a refresh button after an Agent was created or deleted in another surface.
|
|
225
228
|
|
|
226
229
|
## React Office View
|
|
@@ -245,6 +248,9 @@ export function Office() {
|
|
|
245
248
|
<button type="button" onClick={() => view?.refreshAgents()}>
|
|
246
249
|
Refresh agents
|
|
247
250
|
</button>
|
|
251
|
+
<button type="button" onClick={() => view?.resetCamera()}>
|
|
252
|
+
Reset view
|
|
253
|
+
</button>
|
|
248
254
|
<AgentGameOfficeView
|
|
249
255
|
renderer="three"
|
|
250
256
|
office={{
|
|
@@ -308,6 +314,8 @@ view.updateAgents([
|
|
|
308
314
|
|
|
309
315
|
`updateAgents` only mutates SDK-managed snapshot sources. Runtime and custom sources own their own updates.
|
|
310
316
|
|
|
317
|
+
`resetCamera` is source-independent and can be called for snapshot, runtime, and custom sources.
|
|
318
|
+
|
|
311
319
|
## Custom Source
|
|
312
320
|
|
|
313
321
|
Use `source.type: "custom"` when another state manager already projects runtime state into office snapshots.
|
package/package.json
CHANGED
package/src/office/core/types.ts
CHANGED
|
@@ -87,6 +87,7 @@ export type AgentGameOfficeSourceInput =
|
|
|
87
87
|
|
|
88
88
|
export type AgentGameOfficeRendererController = {
|
|
89
89
|
update(snapshot: AgentGameOfficeSnapshot): void;
|
|
90
|
+
resetCamera?(): void;
|
|
90
91
|
focusAgent?(agentId: string | null): void;
|
|
91
92
|
focusFloor?(floorId: string | null): void;
|
|
92
93
|
getFocusedFloor?(): string | null;
|
|
@@ -140,6 +141,7 @@ export type AgentGameOfficeResolvedOptions = Omit<AgentGameOfficeMountOptions, "
|
|
|
140
141
|
|
|
141
142
|
export type AgentGameOfficeController = {
|
|
142
143
|
updateAgents(agents: AgentPresence[]): void;
|
|
144
|
+
resetCamera(): void;
|
|
143
145
|
focusAgent(agentId: string | null): void;
|
|
144
146
|
focusFloor(floorId: string | null): void;
|
|
145
147
|
getFocusedFloor(): string | null;
|
|
@@ -705,7 +705,7 @@ function addOfficePreset(layout: MutableResolvedLayout, room: ResolvedOfficeRoom
|
|
|
705
705
|
addComponent(layout, room.id, "desk", `desk-${index + 1}`, slot.desk.x, slot.desk.z);
|
|
706
706
|
addComponent(layout, room.id, "officeChair", `desk-chair-${index + 1}`, slot.seat.x, slot.seat.z, { rotation: slot.rotation });
|
|
707
707
|
addComponent(layout, room.id, "monitor", `monitor-${index + 1}`, slot.desk.x, slot.desk.z - 0.4);
|
|
708
|
-
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 });
|
|
709
709
|
});
|
|
710
710
|
|
|
711
711
|
MEETING_ROOMS.forEach((meetingRoom, roomIndex) => {
|
package/src/office/mount.ts
CHANGED
|
@@ -18,7 +18,7 @@ type AgentBodyBatch = {
|
|
|
18
18
|
count: number;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
const geometryCache = new Map<string, THREE.
|
|
21
|
+
const geometryCache = new Map<string, THREE.BufferGeometry>();
|
|
22
22
|
const materialCache = new Map<string, THREE.MeshLambertMaterial>();
|
|
23
23
|
const matrixHelper = new THREE.Matrix4();
|
|
24
24
|
|
|
@@ -70,7 +70,8 @@ export class AgentBodyInstancedLayer {
|
|
|
70
70
|
requiredCapacity: number,
|
|
71
71
|
opacity: number,
|
|
72
72
|
): AgentBodyBatch {
|
|
73
|
-
const
|
|
73
|
+
const shape = part.shape ?? "box";
|
|
74
|
+
const key = `${shape}:${part.width}:${part.height}:${part.depth}:${part.color}:${opacity}`;
|
|
74
75
|
const existing = this.batches.get(key);
|
|
75
76
|
if (existing && existing.capacity >= requiredCapacity) {
|
|
76
77
|
return existing;
|
|
@@ -80,7 +81,7 @@ export class AgentBodyInstancedLayer {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
const mesh = new THREE.InstancedMesh(
|
|
83
|
-
getGeometry(part
|
|
84
|
+
getGeometry(part),
|
|
84
85
|
getMaterial(part.color, opacity),
|
|
85
86
|
Math.max(requiredCapacity, 1),
|
|
86
87
|
);
|
|
@@ -100,17 +101,33 @@ export function createAgentBodyInstancedLayer(scene: THREE.Scene): AgentBodyInst
|
|
|
100
101
|
return new AgentBodyInstancedLayer(scene);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
function getGeometry(
|
|
104
|
-
const
|
|
104
|
+
function getGeometry(part: ReturnType<typeof resolveAgentBodyPartRenderSpecs>[number]): THREE.BufferGeometry {
|
|
105
|
+
const shape = part.shape ?? "box";
|
|
106
|
+
const key = `${shape}:${part.width}:${part.height}:${part.depth}`;
|
|
105
107
|
const cached = geometryCache.get(key);
|
|
106
108
|
if (cached) {
|
|
107
109
|
return cached;
|
|
108
110
|
}
|
|
109
|
-
const geometry =
|
|
111
|
+
const geometry = createGeometry(part);
|
|
110
112
|
geometryCache.set(key, geometry);
|
|
111
113
|
return geometry;
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
function createGeometry(part: ReturnType<typeof resolveAgentBodyPartRenderSpecs>[number]): THREE.BufferGeometry {
|
|
117
|
+
switch (part.shape) {
|
|
118
|
+
case "frontPlane":
|
|
119
|
+
return new THREE.PlaneGeometry(part.width, part.height);
|
|
120
|
+
case "backPlane": {
|
|
121
|
+
const geometry = new THREE.PlaneGeometry(part.width, part.height);
|
|
122
|
+
geometry.rotateY(Math.PI);
|
|
123
|
+
return geometry;
|
|
124
|
+
}
|
|
125
|
+
case "box":
|
|
126
|
+
case undefined:
|
|
127
|
+
return new THREE.BoxGeometry(part.width, part.height, part.depth);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
114
131
|
function getMaterial(color: number, opacity: number): THREE.MeshLambertMaterial {
|
|
115
132
|
const key = `${color}:${opacity}`;
|
|
116
133
|
const cached = materialCache.get(key);
|
|
@@ -2,6 +2,8 @@ import * as THREE from "three";
|
|
|
2
2
|
|
|
3
3
|
import type { AgentGameOfficeAgent } from "../../core/types";
|
|
4
4
|
|
|
5
|
+
export const AGENT_LABEL_WORLD_Y_OFFSET = 3.05;
|
|
6
|
+
|
|
5
7
|
export type AgentLabelRecord = {
|
|
6
8
|
root: HTMLDivElement;
|
|
7
9
|
title: HTMLDivElement;
|
|
@@ -69,7 +71,7 @@ export function updateAgentLabelPosition(
|
|
|
69
71
|
viewport: HTMLElement,
|
|
70
72
|
): void {
|
|
71
73
|
const projected = position.clone();
|
|
72
|
-
projected.y +=
|
|
74
|
+
projected.y += AGENT_LABEL_WORLD_Y_OFFSET;
|
|
73
75
|
projected.project(camera);
|
|
74
76
|
const x = (projected.x * 0.5 + 0.5) * viewport.clientWidth;
|
|
75
77
|
const y = (-projected.y * 0.5 + 0.5) * viewport.clientHeight;
|
|
@@ -13,6 +13,9 @@ export type AgentMeshParts = {
|
|
|
13
13
|
head: THREE.Object3D;
|
|
14
14
|
leftEye: THREE.Object3D;
|
|
15
15
|
rightEye: THREE.Object3D;
|
|
16
|
+
mouth: THREE.Object3D;
|
|
17
|
+
chestFront: THREE.Object3D;
|
|
18
|
+
backPanel: THREE.Object3D;
|
|
16
19
|
hairTop: THREE.Object3D;
|
|
17
20
|
hairBack: THREE.Object3D;
|
|
18
21
|
leftArm: THREE.Object3D;
|
|
@@ -38,6 +41,9 @@ export type AgentBodyPartKey =
|
|
|
38
41
|
| "head"
|
|
39
42
|
| "leftEye"
|
|
40
43
|
| "rightEye"
|
|
44
|
+
| "mouth"
|
|
45
|
+
| "chestFront"
|
|
46
|
+
| "backPanel"
|
|
41
47
|
| "hairTop"
|
|
42
48
|
| "hairBack"
|
|
43
49
|
| "leftArm"
|
|
@@ -45,6 +51,7 @@ export type AgentBodyPartKey =
|
|
|
45
51
|
|
|
46
52
|
export type AgentBodyPartRenderSpec = {
|
|
47
53
|
key: AgentBodyPartKey;
|
|
54
|
+
shape?: "box" | "frontPlane" | "backPlane";
|
|
48
55
|
width: number;
|
|
49
56
|
height: number;
|
|
50
57
|
depth: number;
|
|
@@ -66,8 +73,11 @@ export function resolveAgentBodyPartRenderSpecs(index: number): AgentBodyPartRen
|
|
|
66
73
|
{ key: "leftFoot", width: 0.24, height: 0.12, depth: 0.34, color: 0x111827, x: -0.14, y: 0.06, z: 0.08 },
|
|
67
74
|
{ key: "rightFoot", width: 0.24, height: 0.12, depth: 0.34, color: 0x111827, x: 0.14, y: 0.06, z: 0.08 },
|
|
68
75
|
{ key: "head", width: 0.5, height: 0.48, depth: 0.42, color: skin, x: 0, y: 1.48, z: 0 },
|
|
69
|
-
{ key: "leftEye", width: 0.06, height: 0.07, depth: 0
|
|
70
|
-
{ key: "rightEye", width: 0.06, height: 0.07, depth: 0
|
|
76
|
+
{ key: "leftEye", shape: "frontPlane", width: 0.06, height: 0.07, depth: 0, color: 0x111827, x: -0.11, y: 1.5, z: 0.235 },
|
|
77
|
+
{ key: "rightEye", shape: "frontPlane", width: 0.06, height: 0.07, depth: 0, color: 0x111827, x: 0.11, y: 1.5, z: 0.235 },
|
|
78
|
+
{ key: "mouth", shape: "frontPlane", width: 0.16, height: 0.035, depth: 0, color: 0x7f1d1d, x: 0, y: 1.38, z: 0.245 },
|
|
79
|
+
{ key: "chestFront", shape: "frontPlane", width: 0.28, height: 0.12, depth: 0, color: 0xe0f2fe, x: 0, y: 1.02, z: 0.185 },
|
|
80
|
+
{ key: "backPanel", shape: "backPlane", width: 0.36, height: 0.44, depth: 0, color: 0x0f172a, x: 0, y: 0.92, z: -0.185 },
|
|
71
81
|
{ key: "hairTop", width: 0.54, height: 0.14, depth: 0.46, color: hair, x: 0, y: 1.78, z: -0.01 },
|
|
72
82
|
{ key: "hairBack", width: 0.52, height: 0.28, depth: 0.09, color: hair, x: 0, y: 1.61, z: -0.23 },
|
|
73
83
|
{ key: "leftArm", width: 0.15, height: 0.56, depth: 0.17, color: shirt, x: -0.43, y: 0.88, z: 0 },
|
|
@@ -99,6 +109,9 @@ export function createAgentMesh(agent: AgentGameOfficeAgent, index: number): Age
|
|
|
99
109
|
head: parts.head,
|
|
100
110
|
leftEye: parts.leftEye,
|
|
101
111
|
rightEye: parts.rightEye,
|
|
112
|
+
mouth: parts.mouth,
|
|
113
|
+
chestFront: parts.chestFront,
|
|
114
|
+
backPanel: parts.backPanel,
|
|
102
115
|
hairTop: parts.hairTop,
|
|
103
116
|
hairBack: parts.hairBack,
|
|
104
117
|
leftArm: parts.leftArm,
|
|
@@ -58,7 +58,7 @@ const THREE_RENDERER_CAMERA_PADDING = 1.16;
|
|
|
58
58
|
const OFFICE_CAMERA_MIN_BOUNDS_HEIGHT = 3.2;
|
|
59
59
|
const WORKING_FOLLOW_RESET_DELAY_MS = 30_000;
|
|
60
60
|
const WORK_EXIT_WAIT_MS = 30_000;
|
|
61
|
-
const WORKING_FOLLOW_CAMERA_OFFSET = new THREE.Vector3(
|
|
61
|
+
const WORKING_FOLLOW_CAMERA_OFFSET = new THREE.Vector3(14, 18, 20);
|
|
62
62
|
const OFFICE_CAMERA_TRANSITION_DURATION_MS = 600;
|
|
63
63
|
const OFFICE_CAMERA_UP = new THREE.Vector3(0, 1, 0);
|
|
64
64
|
|
|
@@ -332,7 +332,7 @@ export function mountThreeAgentGameOffice(
|
|
|
332
332
|
mesh: record.mesh,
|
|
333
333
|
agent: record.agent,
|
|
334
334
|
target: record.target,
|
|
335
|
-
facingTarget:
|
|
335
|
+
facingTarget: resolveAgentFacingTarget(record.agent, _options.officeLayout),
|
|
336
336
|
elapsedSeconds,
|
|
337
337
|
deltaSeconds,
|
|
338
338
|
});
|
|
@@ -387,6 +387,16 @@ export function mountThreeAgentGameOffice(
|
|
|
387
387
|
|
|
388
388
|
return {
|
|
389
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
|
+
},
|
|
390
400
|
focusAgent(agentId) {
|
|
391
401
|
if (!agentId) {
|
|
392
402
|
transitionOfficeCameraTo(createInitialOfficeCameraView(_options.officeLayout, camera.aspect, camera.fov));
|