opencroc 1.8.0 → 1.8.2
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/dist/cli/index.js +1107 -49
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +128 -1
- package/dist/index.js +548 -0
- package/dist/index.js.map +1 -1
- package/dist/web/dist/assets/main-Ccg3eDNK.js +1 -0
- package/dist/web/dist/assets/office-runtime-B3iNctxE.css +1 -0
- package/dist/web/dist/assets/office-runtime-BsCh82Pj.js +183 -0
- package/dist/web/dist/assets/pixel-page-3BYGm7dH.js +470 -0
- package/dist/web/dist/assets/react-vendor-C8RhVn0h.js +49 -0
- package/dist/web/dist/assets/studio-page-BInoyoV2.css +1 -0
- package/dist/web/dist/assets/studio-page-o3SCvE_v.js +351 -0
- package/dist/web/dist/assets/three-addons-BdrPp04O.js +470 -0
- package/dist/web/dist/assets/three-core-CsxM1PCY.js +4057 -0
- package/dist/web/dist/index.html +15 -0
- package/dist/web/index.html +11 -572
- package/dist/web/public/botreview/char_0.png +0 -0
- package/dist/web/public/botreview/char_1.png +0 -0
- package/dist/web/public/botreview/char_2.png +0 -0
- package/dist/web/public/botreview/coffee-machine.gif +0 -0
- package/dist/web/public/botreview/server.gif +0 -0
- package/dist/web/public/botreview/walls.png +0 -0
- package/dist/web/public/star/desk-v3.webp +0 -0
- package/dist/web/public/star/office_bg_small.webp +0 -0
- package/dist/web/public/star/star-idle-v5.png +0 -0
- package/dist/web/public/star/star-working-spritesheet-grid.webp +0 -0
- package/dist/web/src/app/AppLayout.tsx +34 -0
- package/dist/web/src/app/AppRouter.tsx +46 -0
- package/dist/web/src/app/bootstrap.tsx +22 -0
- package/dist/web/src/app/routes.tsx +52 -0
- package/dist/web/src/features/office/runtime/index.ts +1 -0
- package/dist/web/src/features/office/runtime/mount.ts +809 -0
- package/dist/web/src/features/pixel/runtime/index.ts +1 -0
- package/dist/web/src/features/pixel/runtime/mount.ts +728 -0
- package/dist/web/src/features/studio/runtime/index.ts +1 -0
- package/dist/web/src/features/studio/runtime/mount.ts +664 -0
- package/dist/web/src/features/three/engine/index.ts +1 -0
- package/dist/web/src/main.tsx +7 -0
- package/dist/web/src/pages/office/index.ts +1 -0
- package/dist/web/src/pages/office/page.tsx +283 -0
- package/dist/web/src/pages/pixel/index.ts +1 -0
- package/dist/web/src/pages/pixel/page.tsx +564 -0
- package/dist/web/src/pages/studio/index.ts +1 -0
- package/dist/web/src/pages/studio/page.tsx +446 -0
- package/dist/web/{js/agents.js → src/runtime/agents.ts} +304 -31
- package/dist/web/{js/camera.js → src/runtime/camera.ts} +12 -5
- package/dist/web/{js/dataviz.js → src/runtime/dataviz.ts} +38 -14
- package/dist/web/{js/effects.js → src/runtime/effects.ts} +139 -2
- package/dist/web/{js/engine.js → src/runtime/engine.ts} +45 -6
- package/dist/web/{js/office.js → src/runtime/office.ts} +136 -20
- package/dist/web/{js/ui.js → src/runtime/ui.ts} +11 -7
- package/dist/web/src/shared/assets.ts +4 -0
- package/dist/web/src/shared/navigation.ts +47 -0
- package/dist/web/src/styles/app-layout.css +19 -0
- package/dist/web/src/styles/office.css +268 -0
- package/dist/web/tsconfig.json +28 -0
- package/dist/web/vite.config.ts +93 -0
- package/package.json +11 -2
- package/dist/web/index-studio.html +0 -804
- package/dist/web/index-v2-pixel.html +0 -1571
- /package/dist/web/{assets → dist}/botreview/char_0.png +0 -0
- /package/dist/web/{assets → dist}/botreview/char_1.png +0 -0
- /package/dist/web/{assets → dist}/botreview/char_2.png +0 -0
- /package/dist/web/{assets → dist}/botreview/coffee-machine.gif +0 -0
- /package/dist/web/{assets → dist}/botreview/server.gif +0 -0
- /package/dist/web/{assets → dist}/botreview/walls.png +0 -0
- /package/dist/web/{assets → dist}/star/desk-v3.webp +0 -0
- /package/dist/web/{assets → dist}/star/office_bg_small.webp +0 -0
- /package/dist/web/{assets → dist}/star/star-idle-v5.png +0 -0
- /package/dist/web/{assets → dist}/star/star-working-spritesheet-grid.webp +0 -0
- /package/dist/web/{js/state.js → src/runtime/state.ts} +0 -0
|
@@ -6,12 +6,24 @@
|
|
|
6
6
|
|
|
7
7
|
import * as THREE from 'three';
|
|
8
8
|
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
9
|
-
import { DESK_POSITIONS } from './office
|
|
9
|
+
import { DESK_POSITIONS, POND_POSITIONS, setDeskOccupied } from './office';
|
|
10
10
|
|
|
11
11
|
/* ─── Module state ─────────────────────────────────────────────────────────── */
|
|
12
12
|
const agents = new Map(); // name → { group, parts, label, bubble, anim }
|
|
13
13
|
let scene = null;
|
|
14
|
-
let css2dRenderer = null;
|
|
14
|
+
let css2dRenderer = null;
|
|
15
|
+
let css2dResizeHandler = null;
|
|
16
|
+
|
|
17
|
+
function disposeObject3D(object) {
|
|
18
|
+
object?.traverse?.((child) => {
|
|
19
|
+
child.geometry?.dispose?.();
|
|
20
|
+
if (Array.isArray(child.material)) {
|
|
21
|
+
child.material.forEach((material) => material?.dispose?.());
|
|
22
|
+
} else {
|
|
23
|
+
child.material?.dispose?.();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
15
27
|
|
|
16
28
|
/* ─── Robot Colors per Role ────────────────────────────────────────────────── */
|
|
17
29
|
const ROLE_COLORS = {
|
|
@@ -37,6 +49,17 @@ const STATUS_ANIM = {
|
|
|
37
49
|
passed: { speed: 1.0, bobAmp: 0.05, rotSpeed: 0.2 },
|
|
38
50
|
};
|
|
39
51
|
|
|
52
|
+
const ACTIVE_STATUSES = new Set([
|
|
53
|
+
'working',
|
|
54
|
+
'testing',
|
|
55
|
+
'thinking',
|
|
56
|
+
'scanning',
|
|
57
|
+
'navigating',
|
|
58
|
+
'interacting',
|
|
59
|
+
'asserting',
|
|
60
|
+
'reporting',
|
|
61
|
+
]);
|
|
62
|
+
|
|
40
63
|
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
41
64
|
AgentManager — Creates, updates, removes 3D robot agents
|
|
42
65
|
═══════════════════════════════════════════════════════════════════════════════ */
|
|
@@ -45,12 +68,17 @@ export class AgentManager {
|
|
|
45
68
|
scene = sceneRef;
|
|
46
69
|
this._time = 0;
|
|
47
70
|
this._bubbleTimers = new Map();
|
|
71
|
+
this._deskAssignments = new Map();
|
|
72
|
+
this._eventAssignments = new Map();
|
|
48
73
|
this._initCSS2D();
|
|
49
74
|
}
|
|
50
75
|
|
|
51
|
-
/** Initialize CSS2D renderer for labels and bubbles */
|
|
52
|
-
_initCSS2D() {
|
|
53
|
-
|
|
76
|
+
/** Initialize CSS2D renderer for labels and bubbles */
|
|
77
|
+
_initCSS2D() {
|
|
78
|
+
if (css2dRenderer?.domElement?.parentNode) {
|
|
79
|
+
css2dRenderer.domElement.parentNode.removeChild(css2dRenderer.domElement);
|
|
80
|
+
}
|
|
81
|
+
css2dRenderer = new CSS2DRenderer();
|
|
54
82
|
css2dRenderer.setSize(window.innerWidth, window.innerHeight);
|
|
55
83
|
css2dRenderer.domElement.style.position = 'fixed';
|
|
56
84
|
css2dRenderer.domElement.style.top = '0';
|
|
@@ -59,14 +87,16 @@ export class AgentManager {
|
|
|
59
87
|
css2dRenderer.domElement.style.zIndex = '5';
|
|
60
88
|
document.body.appendChild(css2dRenderer.domElement);
|
|
61
89
|
|
|
62
|
-
|
|
63
|
-
css2dRenderer.setSize(window.innerWidth, window.innerHeight);
|
|
64
|
-
}
|
|
65
|
-
|
|
90
|
+
css2dResizeHandler = () => {
|
|
91
|
+
css2dRenderer.setSize(window.innerWidth, window.innerHeight);
|
|
92
|
+
};
|
|
93
|
+
window.addEventListener('resize', css2dResizeHandler);
|
|
94
|
+
}
|
|
66
95
|
|
|
67
96
|
/** Sync agents from backend data */
|
|
68
97
|
sync(agentData) {
|
|
69
98
|
const current = new Set();
|
|
99
|
+
const active = new Set();
|
|
70
100
|
|
|
71
101
|
agentData.forEach((a, i) => {
|
|
72
102
|
current.add(a.name);
|
|
@@ -74,11 +104,51 @@ export class AgentManager {
|
|
|
74
104
|
this._createRobot(a, i, agentData.length);
|
|
75
105
|
}
|
|
76
106
|
this._updateStatus(a.name, a.status);
|
|
107
|
+
const eventActive = this._eventAssignments.get(a.name);
|
|
108
|
+
const isActive = typeof eventActive === 'boolean' ? eventActive : this._isActiveStatus(a.status);
|
|
109
|
+
if (isActive) active.add(a.name);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Drop stale event-assignment flags for removed agents.
|
|
113
|
+
for (const name of this._eventAssignments.keys()) {
|
|
114
|
+
if (!current.has(name)) this._eventAssignments.delete(name);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Release desks for idle/done agents.
|
|
118
|
+
for (const [name, deskIdx] of this._deskAssignments) {
|
|
119
|
+
if (!current.has(name) || !active.has(name)) {
|
|
120
|
+
this._deskAssignments.delete(name);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Assign desks to active agents that don't have one yet.
|
|
125
|
+
active.forEach((name) => {
|
|
126
|
+
if (!this._deskAssignments.has(name)) {
|
|
127
|
+
const deskIdx = this._nextFreeDesk();
|
|
128
|
+
if (deskIdx >= 0) this._deskAssignments.set(name, deskIdx);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
this._syncDeskOccupancy();
|
|
133
|
+
|
|
134
|
+
// Update movement targets by current allocation.
|
|
135
|
+
current.forEach((name) => {
|
|
136
|
+
const agent = agents.get(name);
|
|
137
|
+
if (!agent) return;
|
|
138
|
+
const deskIdx = this._deskAssignments.get(name);
|
|
139
|
+
if (typeof deskIdx === 'number' && DESK_POSITIONS[deskIdx]) {
|
|
140
|
+
const desk = DESK_POSITIONS[deskIdx];
|
|
141
|
+
this._setTarget(agent, desk.x, desk.z + 1.2, 'desk', desk);
|
|
142
|
+
} else {
|
|
143
|
+
const pond = POND_POSITIONS[agent.pondSlot % Math.max(1, POND_POSITIONS.length)] || { x: -9, z: 6.2 };
|
|
144
|
+
this._setTarget(agent, pond.x, pond.z, 'pond');
|
|
145
|
+
}
|
|
77
146
|
});
|
|
78
147
|
|
|
79
148
|
// Remove stale agents
|
|
80
149
|
for (const [name] of agents) {
|
|
81
150
|
if (!current.has(name)) {
|
|
151
|
+
this._deskAssignments.delete(name);
|
|
82
152
|
this._removeRobot(name);
|
|
83
153
|
}
|
|
84
154
|
}
|
|
@@ -87,6 +157,89 @@ export class AgentManager {
|
|
|
87
157
|
this._scheduleBubbles(agentData);
|
|
88
158
|
}
|
|
89
159
|
|
|
160
|
+
applyAssignmentEvent(payload) {
|
|
161
|
+
const name = payload?.name;
|
|
162
|
+
if (!name || !agents.has(name)) return null;
|
|
163
|
+
|
|
164
|
+
this._eventAssignments.set(name, true);
|
|
165
|
+
if (!this._deskAssignments.has(name)) {
|
|
166
|
+
const deskIdx = this._nextFreeDesk();
|
|
167
|
+
if (deskIdx >= 0) this._deskAssignments.set(name, deskIdx);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const agent = agents.get(name);
|
|
171
|
+
const deskIdx = this._deskAssignments.get(name);
|
|
172
|
+
if (!agent || typeof deskIdx !== 'number' || !DESK_POSITIONS[deskIdx]) return null;
|
|
173
|
+
|
|
174
|
+
const desk = DESK_POSITIONS[deskIdx];
|
|
175
|
+
const from = { x: agent.baseX, z: agent.baseZ };
|
|
176
|
+
this._setTarget(agent, desk.x, desk.z + 1.2, 'desk', desk);
|
|
177
|
+
this._syncDeskOccupancy();
|
|
178
|
+
this._flashSummon(name);
|
|
179
|
+
|
|
180
|
+
return { from, to: { x: desk.x, z: desk.z + 1.2 }, kind: 'assigned' };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
applyReleaseEvent(payload) {
|
|
184
|
+
const name = payload?.name;
|
|
185
|
+
if (!name || !agents.has(name)) return null;
|
|
186
|
+
|
|
187
|
+
this._eventAssignments.set(name, false);
|
|
188
|
+
this._deskAssignments.delete(name);
|
|
189
|
+
|
|
190
|
+
const agent = agents.get(name);
|
|
191
|
+
if (!agent) return null;
|
|
192
|
+
|
|
193
|
+
const pond = POND_POSITIONS[agent.pondSlot % Math.max(1, POND_POSITIONS.length)] || { x: -9, z: 6.2 };
|
|
194
|
+
const from = { x: agent.baseX, z: agent.baseZ };
|
|
195
|
+
this._setTarget(agent, pond.x, pond.z, 'pond');
|
|
196
|
+
this._syncDeskOccupancy();
|
|
197
|
+
|
|
198
|
+
return { from, to: { x: pond.x, z: pond.z }, kind: 'released' };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_flashSummon(name) {
|
|
202
|
+
const agent = agents.get(name);
|
|
203
|
+
if (!agent) return;
|
|
204
|
+
|
|
205
|
+
// Brief glow spike
|
|
206
|
+
if (agent.parts.glow) {
|
|
207
|
+
const prev = agent.parts.glow.intensity;
|
|
208
|
+
agent.parts.glow.intensity = 1.9;
|
|
209
|
+
setTimeout(() => {
|
|
210
|
+
const a = agents.get(name);
|
|
211
|
+
if (a && a.parts.glow) a.parts.glow.intensity = prev;
|
|
212
|
+
}, 700);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Expanding ring at robot feet (blue for assignment)
|
|
216
|
+
const ringGeo = new THREE.TorusGeometry(0.3, 0.04, 8, 20);
|
|
217
|
+
const ringMat = new THREE.MeshBasicMaterial({
|
|
218
|
+
color: 0x60a5fa, transparent: true, opacity: 0.88, depthWrite: false,
|
|
219
|
+
});
|
|
220
|
+
const ring = new THREE.Mesh(ringGeo, ringMat);
|
|
221
|
+
ring.rotation.x = -Math.PI / 2;
|
|
222
|
+
ring.position.set(agent.baseX, 0.24, agent.baseZ);
|
|
223
|
+
scene.add(ring);
|
|
224
|
+
|
|
225
|
+
let life = 0;
|
|
226
|
+
const ttl = 0.78;
|
|
227
|
+
const tick = () => {
|
|
228
|
+
life += 0.016;
|
|
229
|
+
ring.position.set(agent.baseX, 0.24, agent.baseZ);
|
|
230
|
+
ring.scale.setScalar(1 + (life / ttl) * 4.5);
|
|
231
|
+
ring.material.opacity = Math.max(0, 0.88 * (1 - life / ttl));
|
|
232
|
+
if (life < ttl) {
|
|
233
|
+
requestAnimationFrame(tick);
|
|
234
|
+
} else {
|
|
235
|
+
scene.remove(ring);
|
|
236
|
+
ringGeo.dispose();
|
|
237
|
+
ringMat.dispose();
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
requestAnimationFrame(tick);
|
|
241
|
+
}
|
|
242
|
+
|
|
90
243
|
/** Update all agents each frame */
|
|
91
244
|
update(dt) {
|
|
92
245
|
this._time += dt;
|
|
@@ -94,6 +247,31 @@ export class AgentManager {
|
|
|
94
247
|
for (const [name, agent] of agents) {
|
|
95
248
|
const anim = STATUS_ANIM[agent.status] || STATUS_ANIM.idle;
|
|
96
249
|
|
|
250
|
+
let moveTargetX = agent.targetX;
|
|
251
|
+
let moveTargetZ = agent.targetZ;
|
|
252
|
+
if (agent.path.length) {
|
|
253
|
+
moveTargetX = agent.path[0].x;
|
|
254
|
+
moveTargetZ = agent.path[0].z;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Move toward target zone.
|
|
258
|
+
const dx = moveTargetX - agent.baseX;
|
|
259
|
+
const dz = moveTargetZ - agent.baseZ;
|
|
260
|
+
const dist = Math.hypot(dx, dz);
|
|
261
|
+
if (dist > 0.01) {
|
|
262
|
+
const speed = agent.zone === 'desk' ? 4.2 : 2.6;
|
|
263
|
+
const step = Math.min(1, (dt * speed) / dist);
|
|
264
|
+
agent.baseX += dx * step;
|
|
265
|
+
agent.baseZ += dz * step;
|
|
266
|
+
agent.group.lookAt(new THREE.Vector3(moveTargetX, 0.2, moveTargetZ));
|
|
267
|
+
} else if (agent.path.length) {
|
|
268
|
+
agent.path.shift();
|
|
269
|
+
} else if (agent.zone === 'desk' && agent.deskPos) {
|
|
270
|
+
agent.group.lookAt(new THREE.Vector3(agent.deskPos.x, 0.2, agent.deskPos.z));
|
|
271
|
+
} else if (agent.zone === 'pond') {
|
|
272
|
+
agent.group.lookAt(new THREE.Vector3(-9, 0.2, 6.2));
|
|
273
|
+
}
|
|
274
|
+
|
|
97
275
|
// Bobbing
|
|
98
276
|
const bobY = Math.sin(this._time * anim.speed * 2) * anim.bobAmp;
|
|
99
277
|
agent.group.position.y = agent.baseY + bobY;
|
|
@@ -147,7 +325,15 @@ export class AgentManager {
|
|
|
147
325
|
═════════════════════════════════════════════════════════════════════════ */
|
|
148
326
|
_createRobot(agentData, index, total) {
|
|
149
327
|
const role = agentData.role || 'parser';
|
|
150
|
-
|
|
328
|
+
// Support dynamic color from server (hex string like '#60a5fa')
|
|
329
|
+
let colors = ROLE_COLORS[role] || DEFAULT_COLORS;
|
|
330
|
+
if (agentData.color && !ROLE_COLORS[role]) {
|
|
331
|
+
const hex = parseInt(agentData.color.replace('#', ''), 16);
|
|
332
|
+
if (!isNaN(hex)) {
|
|
333
|
+
const lighter = new THREE.Color(hex).lerp(new THREE.Color(0xffffff), 0.35).getHex();
|
|
334
|
+
colors = { body: hex, accent: hex, eye: lighter, glow: hex };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
151
337
|
const group = new THREE.Group();
|
|
152
338
|
group.name = `agent-${agentData.name}`;
|
|
153
339
|
|
|
@@ -311,14 +497,13 @@ export class AgentManager {
|
|
|
311
497
|
group.add(shadow);
|
|
312
498
|
|
|
313
499
|
/* ── Position ────────────────────────────────────────────────────────── */
|
|
314
|
-
const
|
|
315
|
-
const
|
|
316
|
-
const
|
|
500
|
+
const pondSlot = index % Math.max(1, POND_POSITIONS.length);
|
|
501
|
+
const pond = POND_POSITIONS[pondSlot] || { x: -9, z: 6.2 };
|
|
502
|
+
const x = pond.x;
|
|
503
|
+
const z = pond.z;
|
|
317
504
|
|
|
318
505
|
group.position.set(x, 0.2, z);
|
|
319
|
-
|
|
320
|
-
// Point toward desk
|
|
321
|
-
group.lookAt(new THREE.Vector3(desk.x, 0.2, desk.z));
|
|
506
|
+
group.lookAt(new THREE.Vector3(-9, 0.2, 6.2));
|
|
322
507
|
|
|
323
508
|
scene.add(group);
|
|
324
509
|
|
|
@@ -346,7 +531,12 @@ export class AgentManager {
|
|
|
346
531
|
baseX: x,
|
|
347
532
|
baseY: 0.2,
|
|
348
533
|
baseZ: z,
|
|
349
|
-
deskPos:
|
|
534
|
+
deskPos: null,
|
|
535
|
+
pondSlot,
|
|
536
|
+
zone: 'pond',
|
|
537
|
+
targetX: x,
|
|
538
|
+
targetZ: z,
|
|
539
|
+
path: [],
|
|
350
540
|
});
|
|
351
541
|
}
|
|
352
542
|
|
|
@@ -369,14 +559,72 @@ export class AgentManager {
|
|
|
369
559
|
}
|
|
370
560
|
}
|
|
371
561
|
|
|
562
|
+
_isActiveStatus(status) {
|
|
563
|
+
return ACTIVE_STATUSES.has(status || 'idle');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
_nextFreeDesk() {
|
|
567
|
+
const used = new Set(this._deskAssignments.values());
|
|
568
|
+
for (let i = 0; i < DESK_POSITIONS.length; i++) {
|
|
569
|
+
if (!used.has(i)) return i;
|
|
570
|
+
}
|
|
571
|
+
return -1;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
_setTarget(agent, x, z, zone, deskPos = null) {
|
|
575
|
+
const changed = zone !== agent.zone || Math.hypot(agent.targetX - x, agent.targetZ - z) > 0.06;
|
|
576
|
+
if (!changed) {
|
|
577
|
+
agent.deskPos = deskPos;
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
agent.targetX = x;
|
|
582
|
+
agent.targetZ = z;
|
|
583
|
+
agent.zone = zone;
|
|
584
|
+
agent.deskPos = deskPos;
|
|
585
|
+
agent.path = this._buildPath(agent, x, z, zone);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
_buildPath(agent, targetX, targetZ, zone) {
|
|
589
|
+
const path = [];
|
|
590
|
+
const corridorZ = 2.6;
|
|
591
|
+
const pondGateX = -6.4;
|
|
592
|
+
|
|
593
|
+
const nearCorridor = Math.abs(agent.baseZ - corridorZ) < 0.6;
|
|
594
|
+
if (!nearCorridor) {
|
|
595
|
+
path.push({ x: agent.baseX, z: corridorZ });
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (zone === 'desk') {
|
|
599
|
+
path.push({ x: targetX, z: corridorZ });
|
|
600
|
+
path.push({ x: targetX, z: targetZ });
|
|
601
|
+
return path;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
path.push({ x: pondGateX, z: corridorZ + 1.1 });
|
|
605
|
+
path.push({ x: targetX, z: targetZ });
|
|
606
|
+
return path;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
_syncDeskOccupancy() {
|
|
610
|
+
for (let i = 0; i < DESK_POSITIONS.length; i++) {
|
|
611
|
+
setDeskOccupied(i, false);
|
|
612
|
+
}
|
|
613
|
+
for (const deskIdx of this._deskAssignments.values()) {
|
|
614
|
+
setDeskOccupied(deskIdx, true);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
372
618
|
/* ═════════════════════════════════════════════════════════════════════════
|
|
373
619
|
Remove Robot
|
|
374
620
|
═════════════════════════════════════════════════════════════════════════ */
|
|
375
|
-
_removeRobot(name) {
|
|
376
|
-
const agent = agents.get(name);
|
|
377
|
-
if (!agent) return;
|
|
378
|
-
scene.remove(agent.group);
|
|
379
|
-
|
|
621
|
+
_removeRobot(name) {
|
|
622
|
+
const agent = agents.get(name);
|
|
623
|
+
if (!agent) return;
|
|
624
|
+
scene.remove(agent.group);
|
|
625
|
+
disposeObject3D(agent.group);
|
|
626
|
+
agents.delete(name);
|
|
627
|
+
this._syncDeskOccupancy();
|
|
380
628
|
|
|
381
629
|
// Clean bubble timer
|
|
382
630
|
const bt = this._bubbleTimers.get(name);
|
|
@@ -427,7 +675,7 @@ export class AgentManager {
|
|
|
427
675
|
}
|
|
428
676
|
}
|
|
429
677
|
|
|
430
|
-
_showBubble(name, text) {
|
|
678
|
+
_showBubble(name, text) {
|
|
431
679
|
const agent = agents.get(name);
|
|
432
680
|
if (!agent) return;
|
|
433
681
|
|
|
@@ -446,14 +694,39 @@ export class AgentManager {
|
|
|
446
694
|
agent.bubbleObj = bubble;
|
|
447
695
|
|
|
448
696
|
// Remove after 3 seconds
|
|
449
|
-
setTimeout(() => {
|
|
450
|
-
if (agent.bubbleObj === bubble) {
|
|
451
|
-
agent.group.remove(bubble);
|
|
452
|
-
agent.bubbleObj = null;
|
|
453
|
-
}
|
|
454
|
-
}, 3000);
|
|
455
|
-
}
|
|
456
|
-
|
|
697
|
+
setTimeout(() => {
|
|
698
|
+
if (agent.bubbleObj === bubble) {
|
|
699
|
+
agent.group.remove(bubble);
|
|
700
|
+
agent.bubbleObj = null;
|
|
701
|
+
}
|
|
702
|
+
}, 3000);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
dispose() {
|
|
706
|
+
for (const timer of this._bubbleTimers.values()) {
|
|
707
|
+
clearTimeout(timer);
|
|
708
|
+
}
|
|
709
|
+
this._bubbleTimers.clear();
|
|
710
|
+
this._deskAssignments.clear();
|
|
711
|
+
this._eventAssignments.clear();
|
|
712
|
+
|
|
713
|
+
for (const name of [...agents.keys()]) {
|
|
714
|
+
this._removeRobot(name);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
if (css2dResizeHandler) {
|
|
718
|
+
window.removeEventListener('resize', css2dResizeHandler);
|
|
719
|
+
css2dResizeHandler = null;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
if (css2dRenderer?.domElement?.parentNode) {
|
|
723
|
+
css2dRenderer.domElement.parentNode.removeChild(css2dRenderer.domElement);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
css2dRenderer = null;
|
|
727
|
+
scene = null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
457
730
|
|
|
458
731
|
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
459
732
|
Helper exports
|
|
@@ -118,8 +118,15 @@ export class CameraController {
|
|
|
118
118
|
this.controls.autoRotate = false;
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
/** Get the raw controls for external use */
|
|
122
|
-
getControls() {
|
|
123
|
-
return this.controls;
|
|
124
|
-
}
|
|
125
|
-
|
|
121
|
+
/** Get the raw controls for external use */
|
|
122
|
+
getControls() {
|
|
123
|
+
return this.controls;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
dispose() {
|
|
127
|
+
this.controls?.dispose?.();
|
|
128
|
+
if (this.scene?.userData?.camera === this.camera) {
|
|
129
|
+
delete this.scene.userData.camera;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -4,7 +4,18 @@
|
|
|
4
4
|
~2000 lines
|
|
5
5
|
═══════════════════════════════════════════════════════════════════════════════ */
|
|
6
6
|
|
|
7
|
-
import * as THREE from 'three';
|
|
7
|
+
import * as THREE from 'three';
|
|
8
|
+
|
|
9
|
+
function disposeObject3D(object) {
|
|
10
|
+
object?.traverse?.((child) => {
|
|
11
|
+
child.geometry?.dispose?.();
|
|
12
|
+
if (Array.isArray(child.material)) {
|
|
13
|
+
child.material.forEach((material) => material?.dispose?.());
|
|
14
|
+
} else {
|
|
15
|
+
child.material?.dispose?.();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
8
19
|
|
|
9
20
|
/* ─── Color Map per module ─────────────────────────────────────────────────── */
|
|
10
21
|
const MOD_COLORS = [
|
|
@@ -125,10 +136,18 @@ export class GraphViz {
|
|
|
125
136
|
}
|
|
126
137
|
}
|
|
127
138
|
|
|
128
|
-
/** Show/hide the graph */
|
|
129
|
-
show() { this.group.visible = true; }
|
|
130
|
-
hide() { this.group.visible = false; }
|
|
131
|
-
|
|
139
|
+
/** Show/hide the graph */
|
|
140
|
+
show() { this.group.visible = true; }
|
|
141
|
+
hide() { this.group.visible = false; }
|
|
142
|
+
|
|
143
|
+
dispose() {
|
|
144
|
+
this.scene.remove(this.group);
|
|
145
|
+
disposeObject3D(this.group);
|
|
146
|
+
this._nodes.clear();
|
|
147
|
+
this._edges = [];
|
|
148
|
+
this._modules.clear();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
132
151
|
|
|
133
152
|
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
134
153
|
HologramDisplay — Central floating data display
|
|
@@ -250,7 +269,7 @@ export class HologramDisplay {
|
|
|
250
269
|
}
|
|
251
270
|
|
|
252
271
|
/** Update each frame */
|
|
253
|
-
update(dt, graphData) {
|
|
272
|
+
update(dt, graphData) {
|
|
254
273
|
this._time += dt;
|
|
255
274
|
|
|
256
275
|
// Rotate elements
|
|
@@ -278,11 +297,16 @@ export class HologramDisplay {
|
|
|
278
297
|
this.holoLight.intensity = 0.6 + 0.4 * Math.sin(this._time * 2);
|
|
279
298
|
|
|
280
299
|
// Update sphere opacity based on data amount
|
|
281
|
-
if (graphData && graphData.nodes) {
|
|
282
|
-
const nodeCount = graphData.nodes.length;
|
|
283
|
-
const t = Math.min(nodeCount / 100, 1);
|
|
284
|
-
this.sphere.material.opacity = 0.1 + t * 0.2;
|
|
285
|
-
this.innerSphere.material.opacity = 0.08 + t * 0.15;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
300
|
+
if (graphData && graphData.nodes) {
|
|
301
|
+
const nodeCount = graphData.nodes.length;
|
|
302
|
+
const t = Math.min(nodeCount / 100, 1);
|
|
303
|
+
this.sphere.material.opacity = 0.1 + t * 0.2;
|
|
304
|
+
this.innerSphere.material.opacity = 0.08 + t * 0.15;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
dispose() {
|
|
309
|
+
this.scene.remove(this.group);
|
|
310
|
+
disposeObject3D(this.group);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -3,7 +3,18 @@
|
|
|
3
3
|
~2000 lines
|
|
4
4
|
═══════════════════════════════════════════════════════════════════════════════ */
|
|
5
5
|
|
|
6
|
-
import * as THREE from 'three';
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
|
|
8
|
+
function disposeObject3D(object) {
|
|
9
|
+
object?.traverse?.((child) => {
|
|
10
|
+
child.geometry?.dispose?.();
|
|
11
|
+
if (Array.isArray(child.material)) {
|
|
12
|
+
child.material.forEach((material) => material?.dispose?.());
|
|
13
|
+
} else {
|
|
14
|
+
child.material?.dispose?.();
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
7
18
|
|
|
8
19
|
/* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
20
|
ParticleManager
|
|
@@ -342,4 +353,130 @@ export class ParticleManager {
|
|
|
342
353
|
|
|
343
354
|
this.systems.push(sys);
|
|
344
355
|
}
|
|
345
|
-
|
|
356
|
+
|
|
357
|
+
triggerAgentTransfer(from, to, kind = 'assigned') {
|
|
358
|
+
if (!from || !to) return;
|
|
359
|
+
|
|
360
|
+
const startColor = kind === 'released' ? 0xfbbf24 : 0x60a5fa;
|
|
361
|
+
const endColor = kind === 'released' ? 0x34d399 : 0x22d3ee;
|
|
362
|
+
|
|
363
|
+
this._triggerPulse(from.x, 0.34, from.z, startColor, kind === 'released' ? 0.8 : 1.1);
|
|
364
|
+
this._triggerPulse(to.x, 0.34, to.z, endColor, kind === 'released' ? 1.2 : 0.9, 180);
|
|
365
|
+
|
|
366
|
+
// Expanding ripple at the pond side (departure or arrival).
|
|
367
|
+
const pondSide = kind === 'assigned' ? from : to;
|
|
368
|
+
if (pondSide.x != null) this.triggerPondRipple(pondSide.x, pondSide.z ?? 6.2);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
triggerPondRipple(x, z) {
|
|
372
|
+
const geo = new THREE.RingGeometry(0.18, 0.32, 24);
|
|
373
|
+
const mat = new THREE.MeshBasicMaterial({
|
|
374
|
+
color: 0x0ea5e9,
|
|
375
|
+
transparent: true,
|
|
376
|
+
opacity: 0.65,
|
|
377
|
+
side: THREE.DoubleSide,
|
|
378
|
+
depthWrite: false,
|
|
379
|
+
});
|
|
380
|
+
const ring = new THREE.Mesh(geo, mat);
|
|
381
|
+
ring.rotation.x = -Math.PI / 2;
|
|
382
|
+
ring.position.set(x, 0.31, z);
|
|
383
|
+
this.scene.add(ring);
|
|
384
|
+
|
|
385
|
+
let life = 0;
|
|
386
|
+
const sys = {
|
|
387
|
+
mesh: ring,
|
|
388
|
+
update: (dt) => {
|
|
389
|
+
life += dt;
|
|
390
|
+
if (life >= 1.1) {
|
|
391
|
+
this.scene.remove(ring);
|
|
392
|
+
geo.dispose();
|
|
393
|
+
mat.dispose();
|
|
394
|
+
const idx = this.systems.indexOf(sys);
|
|
395
|
+
if (idx >= 0) this.systems.splice(idx, 1);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
ring.scale.setScalar(1 + life * 3.2);
|
|
399
|
+
ring.material.opacity = Math.max(0, 0.65 * (1 - life / 1.1));
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
this.systems.push(sys);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
_triggerPulse(x, y, z, colorHex, lift = 1, delayMs = 0) {
|
|
406
|
+
const spawn = () => {
|
|
407
|
+
const count = 38;
|
|
408
|
+
const positions = new Float32Array(count * 3);
|
|
409
|
+
const velocities = new Float32Array(count * 3);
|
|
410
|
+
|
|
411
|
+
for (let i = 0; i < count; i++) {
|
|
412
|
+
positions[i * 3] = x;
|
|
413
|
+
positions[i * 3 + 1] = y;
|
|
414
|
+
positions[i * 3 + 2] = z;
|
|
415
|
+
|
|
416
|
+
const a = (i / count) * Math.PI * 2;
|
|
417
|
+
const speed = 0.8 + Math.random() * 1.4;
|
|
418
|
+
velocities[i * 3] = Math.cos(a) * speed;
|
|
419
|
+
velocities[i * 3 + 1] = 0.8 + Math.random() * lift;
|
|
420
|
+
velocities[i * 3 + 2] = Math.sin(a) * speed;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const geo = new THREE.BufferGeometry();
|
|
424
|
+
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
425
|
+
const mat = new THREE.PointsMaterial({
|
|
426
|
+
size: 0.09,
|
|
427
|
+
color: colorHex,
|
|
428
|
+
transparent: true,
|
|
429
|
+
opacity: 0.92,
|
|
430
|
+
blending: THREE.AdditiveBlending,
|
|
431
|
+
depthWrite: false,
|
|
432
|
+
});
|
|
433
|
+
const points = new THREE.Points(geo, mat);
|
|
434
|
+
this.scene.add(points);
|
|
435
|
+
|
|
436
|
+
let life = 0;
|
|
437
|
+
const ttl = 0.68;
|
|
438
|
+
const sys = {
|
|
439
|
+
mesh: points,
|
|
440
|
+
update: (dt) => {
|
|
441
|
+
life += dt;
|
|
442
|
+
if (life >= ttl) {
|
|
443
|
+
this.scene.remove(points);
|
|
444
|
+
geo.dispose();
|
|
445
|
+
mat.dispose();
|
|
446
|
+
const idx = this.systems.indexOf(sys);
|
|
447
|
+
if (idx >= 0) this.systems.splice(idx, 1);
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const arr = geo.attributes.position.array;
|
|
452
|
+
for (let i = 0; i < count; i++) {
|
|
453
|
+
arr[i * 3] += velocities[i * 3] * dt;
|
|
454
|
+
arr[i * 3 + 1] += velocities[i * 3 + 1] * dt;
|
|
455
|
+
arr[i * 3 + 2] += velocities[i * 3 + 2] * dt;
|
|
456
|
+
velocities[i * 3 + 1] -= 2.8 * dt;
|
|
457
|
+
}
|
|
458
|
+
geo.attributes.position.needsUpdate = true;
|
|
459
|
+
mat.opacity = Math.max(0, 0.92 - (life / ttl) * 0.92);
|
|
460
|
+
},
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
this.systems.push(sys);
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
if (delayMs > 0) {
|
|
467
|
+
setTimeout(spawn, delayMs);
|
|
468
|
+
} else {
|
|
469
|
+
spawn();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
dispose() {
|
|
474
|
+
for (const system of this.systems) {
|
|
475
|
+
if (system.mesh) {
|
|
476
|
+
this.scene.remove(system.mesh);
|
|
477
|
+
disposeObject3D(system.mesh);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
this.systems = [];
|
|
481
|
+
}
|
|
482
|
+
}
|