let-them-talk 3.6.1 → 3.7.0
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/CHANGELOG.md +46 -0
- package/README.md +86 -2
- package/cli.js +1 -1
- package/dashboard.html +12 -2
- package/office/agents.js +94 -17
- package/office/animation.js +35 -1
- package/office/campus-env.js +1483 -0
- package/office/environment.js +10 -0
- package/office/index.js +46 -16
- package/office/monitors.js +10 -1
- package/office/navigation.js +263 -0
- package/office/scene.js +3 -3
- package/office/spectator-camera.js +1 -1
- package/office/state.js +1 -1
- package/package.json +1 -1
- package/server.js +928 -23
package/office/environment.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as THREE from 'three';
|
|
|
2
2
|
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
3
3
|
import { S } from './state.js';
|
|
4
4
|
import { FLOOR_W, FLOOR_D, DESK_POSITIONS, RECEPTION_POS, ENVS, DRESSING_ROOM_POS, REST_AREA_POS } from './constants.js';
|
|
5
|
+
import { buildCampusEnvironment, getCampusDeskPositions } from './campus-env.js';
|
|
5
6
|
|
|
6
7
|
export function buildEnvironment() {
|
|
7
8
|
if (S.furnitureGroup) {
|
|
@@ -27,6 +28,15 @@ export function buildEnvironment() {
|
|
|
27
28
|
}
|
|
28
29
|
S.furnitureGroup = new THREE.Group();
|
|
29
30
|
S.deskMeshes = [];
|
|
31
|
+
|
|
32
|
+
if (S.currentEnv === 'campus') {
|
|
33
|
+
buildCampusEnvironment();
|
|
34
|
+
// Store campus desk positions for agent assignment
|
|
35
|
+
S._campusDeskPositions = getCampusDeskPositions();
|
|
36
|
+
S.scene.add(S.furnitureGroup);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
var env = ENVS[S.currentEnv] || ENVS.modern;
|
|
31
41
|
|
|
32
42
|
buildFloor(env);
|
package/office/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { DESK_POSITIONS, DRESSING_ROOM_POS, DRESSING_ROOM_ENTRANCE, REST_AREA_PO
|
|
|
5
5
|
import { initScene } from './scene.js';
|
|
6
6
|
import { buildEnvironment, updateTVScreen } from './environment.js';
|
|
7
7
|
import { updateAgent } from './animation.js';
|
|
8
|
-
import { syncAgents, processMessages, walkTo, showBubble } from './agents.js';
|
|
8
|
+
import { syncAgents, processMessages, walkTo, navigateTo, showBubble } from './agents.js';
|
|
9
9
|
// Side-effect: registers window.officeGetAppearance
|
|
10
10
|
import './appearance.js';
|
|
11
11
|
|
|
@@ -141,8 +141,8 @@ function executeCommand(agentName, action) {
|
|
|
141
141
|
agent.location = 'walking';
|
|
142
142
|
agent.isSitting = false;
|
|
143
143
|
showBubble(agent, 'Going to change!');
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
navigateTo(agent, DRESSING_ROOM_ENTRANCE.x, DRESSING_ROOM_ENTRANCE.z, function() {
|
|
145
|
+
navigateTo(agent, DRESSING_ROOM_POS.x, DRESSING_ROOM_POS.z, function() {
|
|
146
146
|
agent.location = 'dressing_room';
|
|
147
147
|
agent.isSitting = false;
|
|
148
148
|
showBubble(agent, 'Time for a new look!');
|
|
@@ -161,8 +161,8 @@ function executeCommand(agentName, action) {
|
|
|
161
161
|
agent.location = 'walking';
|
|
162
162
|
agent.isSitting = false;
|
|
163
163
|
showBubble(agent, 'Need a break...');
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
navigateTo(agent, REST_AREA_ENTRANCE.x, REST_AREA_ENTRANCE.z, function() {
|
|
165
|
+
navigateTo(agent, REST_AREA_POS.x, REST_AREA_POS.z, function() {
|
|
166
166
|
agent.location = 'rest';
|
|
167
167
|
agent.state = 'sleeping';
|
|
168
168
|
agent.isSitting = false;
|
|
@@ -176,7 +176,7 @@ function executeCommand(agentName, action) {
|
|
|
176
176
|
agent.state = 'active';
|
|
177
177
|
agent.isSitting = false;
|
|
178
178
|
showBubble(agent, 'Back to work!');
|
|
179
|
-
|
|
179
|
+
navigateTo(agent, agent.deskPos.x, agent.deskPos.z + 0.7, function() {
|
|
180
180
|
agent.location = 'desk';
|
|
181
181
|
agent.registered = true;
|
|
182
182
|
});
|
|
@@ -201,8 +201,8 @@ function waitForEditorClose(agent) {
|
|
|
201
201
|
if (agent.location === 'dressing_room') {
|
|
202
202
|
agent.location = 'walking';
|
|
203
203
|
showBubble(agent, 'Looking good!');
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
navigateTo(agent, DRESSING_ROOM_ENTRANCE.x, DRESSING_ROOM_ENTRANCE.z, function() {
|
|
205
|
+
navigateTo(agent, agent.deskPos.x, agent.deskPos.z + 0.7, function() {
|
|
206
206
|
agent.location = 'desk';
|
|
207
207
|
agent.registered = true;
|
|
208
208
|
});
|
|
@@ -226,6 +226,29 @@ function animate() {
|
|
|
226
226
|
updateAgent(S.agents3d[name], dt, time);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// Hide roof when camera is above ceiling height
|
|
230
|
+
if (S._roofGroup) {
|
|
231
|
+
S._roofGroup.visible = S.camera.position.y < 6.5;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Manager office door animation — opens when agent is near the door
|
|
235
|
+
if (S._managerDoor && S._managerOfficePos) {
|
|
236
|
+
var doorX = S._managerOfficePos.x;
|
|
237
|
+
var doorZ = S._managerOfficePos.z - 3.5; // front of office
|
|
238
|
+
var shouldOpen = false;
|
|
239
|
+
for (var an in S.agents3d) {
|
|
240
|
+
var ag = S.agents3d[an];
|
|
241
|
+
if (ag.target || ag.location === 'walking') {
|
|
242
|
+
var adx = ag.pos.x - doorX;
|
|
243
|
+
var adz = ag.pos.z - doorZ;
|
|
244
|
+
if (Math.sqrt(adx * adx + adz * adz) < 3) { shouldOpen = true; break; }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
S._managerDoorOpen = shouldOpen ? 1 : 0;
|
|
248
|
+
S._managerDoorLerp += (S._managerDoorOpen - S._managerDoorLerp) * Math.min(1, dt * 4);
|
|
249
|
+
S._managerDoor.position.x = S._managerDoorLerp * 1.3; // slide open to the right
|
|
250
|
+
}
|
|
251
|
+
|
|
229
252
|
// Update TV screen every ~0.5s for smooth ticker
|
|
230
253
|
if (!S._tvTimer) S._tvTimer = 0;
|
|
231
254
|
S._tvTimer += dt;
|
|
@@ -309,17 +332,24 @@ window.office3dSetEnvironment = function(env) {
|
|
|
309
332
|
if (env === S.currentEnv) return;
|
|
310
333
|
S.currentEnv = env;
|
|
311
334
|
if (S.scene) {
|
|
312
|
-
|
|
313
|
-
var i = 0;
|
|
335
|
+
// Remove all existing agents so they get recreated with proper desk assignments
|
|
314
336
|
for (var name in S.agents3d) {
|
|
315
337
|
var agent = S.agents3d[name];
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
338
|
+
S.scene.remove(agent.parts.group);
|
|
339
|
+
agent.parts.group.traverse(function(child) {
|
|
340
|
+
if (child.geometry) child.geometry.dispose();
|
|
341
|
+
if (child.material) {
|
|
342
|
+
if (child.material.map) child.material.map.dispose();
|
|
343
|
+
child.material.dispose();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
322
346
|
}
|
|
347
|
+
S.agents3d = {};
|
|
348
|
+
S.lastProcessedMsg = 0;
|
|
349
|
+
buildEnvironment();
|
|
350
|
+
// syncAgents will recreate all agents with correct desk assignments
|
|
351
|
+
syncAgents();
|
|
352
|
+
processMessages();
|
|
323
353
|
}
|
|
324
354
|
};
|
|
325
355
|
|
package/office/monitors.js
CHANGED
|
@@ -37,7 +37,16 @@ export function updateMonitorScreen(deskIdx, agentName, time) {
|
|
|
37
37
|
var agentInfo = (window.cachedAgents || {})[agentName] || {};
|
|
38
38
|
var lines = [];
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// Prominent warning when agent is NOT listening
|
|
41
|
+
if (agentInfo.status === 'active' && !agentInfo.is_listening) {
|
|
42
|
+
ctx.fillStyle = '#1a0808';
|
|
43
|
+
ctx.fillRect(0, 14, W, 14);
|
|
44
|
+
ctx.fillStyle = '#ef4444';
|
|
45
|
+
ctx.font = 'bold 10px monospace';
|
|
46
|
+
ctx.fillText('\u26A0 NOT LISTENING', 6, 25);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
var statusColor = agentInfo.is_listening ? '#28c840' : agentInfo.status === 'active' ? '#ef4444' : '#ffbd2e';
|
|
41
50
|
lines.push({ color: '#546178', text: '$ agent status' });
|
|
42
51
|
lines.push({ color: statusColor, text: ' ' + (agentInfo.status || 'unknown').toUpperCase() + (agentInfo.is_listening ? ' (listening)' : ' (working)') });
|
|
43
52
|
lines.push({ color: '#546178', text: '' });
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { S } from './state.js';
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// NAVIGATION SYSTEM — Waypoint graph pathfinding
|
|
5
|
+
// Agents walk along connected waypoints to avoid walls/objects
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
// Manager office geometry reference:
|
|
9
|
+
// Office group at (12, 5), size 8x7, walls at:
|
|
10
|
+
// Front (door): z = 5 - 3.5 = 1.5 (door at center x=12)
|
|
11
|
+
// Back: z = 5 + 3.5 = 8.5
|
|
12
|
+
// Left: x = 12 - 4 = 8
|
|
13
|
+
// Right: x = 12 + 4 = 16
|
|
14
|
+
// Glass partition at z = -7 (between workspace and rec)
|
|
15
|
+
// Glass partition at x = -8 (between designer and main)
|
|
16
|
+
|
|
17
|
+
var CAMPUS_WAYPOINTS = [
|
|
18
|
+
// === LOBBY / ENTRANCE ===
|
|
19
|
+
{ id: 'spawn', x: 0, z: 14 },
|
|
20
|
+
{ id: 'lobby', x: 0, z: 10 },
|
|
21
|
+
{ id: 'lobby_left', x: -6, z: 10 },
|
|
22
|
+
{ id: 'lobby_right', x: 6, z: 10 },
|
|
23
|
+
|
|
24
|
+
// === MAIN CORRIDOR (runs along z=6, above workspace) ===
|
|
25
|
+
{ id: 'corr_L', x: -8, z: 7 },
|
|
26
|
+
{ id: 'corr_CL', x: -3, z: 7 },
|
|
27
|
+
{ id: 'corr_C', x: 0, z: 7 },
|
|
28
|
+
{ id: 'corr_CR', x: 3, z: 7 },
|
|
29
|
+
{ id: 'corr_R', x: 7, z: 7 },
|
|
30
|
+
|
|
31
|
+
// === WORKSPACE ZONE (center area, between glass partitions) ===
|
|
32
|
+
{ id: 'work_N', x: 0, z: 4 }, // north end
|
|
33
|
+
{ id: 'work_NW', x: -5, z: 4 },
|
|
34
|
+
{ id: 'work_NE', x: 5, z: 4 },
|
|
35
|
+
{ id: 'work_W', x: -5, z: 0 },
|
|
36
|
+
{ id: 'work_C', x: 0, z: 0 },
|
|
37
|
+
{ id: 'work_E', x: 5, z: 0 },
|
|
38
|
+
{ id: 'work_SW', x: -5, z: -3 },
|
|
39
|
+
{ id: 'work_S', x: 0, z: -5 },
|
|
40
|
+
{ id: 'work_SE', x: 5, z: -3 },
|
|
41
|
+
|
|
42
|
+
// === DESIGNER WING (left of glass partition x=-8) ===
|
|
43
|
+
{ id: 'design_gate', x: -8, z: 3 }, // gap in partition
|
|
44
|
+
{ id: 'design_N', x: -12, z: 3 },
|
|
45
|
+
{ id: 'design_C', x: -12.5, z: 0 },
|
|
46
|
+
{ id: 'design_S', x: -12, z: -3 },
|
|
47
|
+
|
|
48
|
+
// === MANAGER OFFICE (right side, enclosed glass room) ===
|
|
49
|
+
// Office walls: left x=8, right x=16, front z=1.5, back z=8.5
|
|
50
|
+
// Door at front wall center (x=12, z=1.5)
|
|
51
|
+
// Path must go AROUND the left-front corner, then to door from outside
|
|
52
|
+
{ id: 'mgr_hallway', x: 7, z: 3 }, // south of corridor, OUTSIDE left wall (x<8)
|
|
53
|
+
{ id: 'mgr_corner', x: 7, z: 0 }, // past the front-left corner (x<8, z<1.5)
|
|
54
|
+
{ id: 'mgr_outside', x: 12, z: 0 }, // in front of door, OUTSIDE front wall (z<1.5)
|
|
55
|
+
{ id: 'mgr_doorstep', x: 12, z: 1.5 }, // at the door threshold (triggers door open)
|
|
56
|
+
{ id: 'mgr_entry', x: 12, z: 3 }, // just inside the door
|
|
57
|
+
{ id: 'mgr_center', x: 12, z: 5 }, // middle of office
|
|
58
|
+
{ id: 'mgr_desk', x: 12, z: 7 }, // at the desk/chair
|
|
59
|
+
|
|
60
|
+
// === BACK ZONE CORRIDOR (runs along z=-7 to z=-8, south of glass partition) ===
|
|
61
|
+
{ id: 'back_gate', x: 0, z: -6.5 }, // gap in glass partition
|
|
62
|
+
{ id: 'back_L', x: -8, z: -8 },
|
|
63
|
+
{ id: 'back_C', x: 0, z: -8 },
|
|
64
|
+
{ id: 'back_R', x: 8, z: -8 },
|
|
65
|
+
|
|
66
|
+
// === BAR (back left) ===
|
|
67
|
+
{ id: 'bar_entry', x: -10, z: -10 },
|
|
68
|
+
{ id: 'bar_center', x: -14, z: -12 },
|
|
69
|
+
|
|
70
|
+
// === REC CENTER (back center) ===
|
|
71
|
+
{ id: 'rec_entry', x: 0, z: -10 },
|
|
72
|
+
{ id: 'rec_center', x: 0, z: -12 },
|
|
73
|
+
|
|
74
|
+
// === GYM (back right) ===
|
|
75
|
+
{ id: 'gym_entry', x: 10, z: -10 },
|
|
76
|
+
{ id: 'gym_center', x: 14, z: -12 },
|
|
77
|
+
|
|
78
|
+
// === MEZZANINE / STAIRS ===
|
|
79
|
+
{ id: 'stairs_bot', x: 20, z: -5 },
|
|
80
|
+
{ id: 'stairs_top', x: 20, z: -8 },
|
|
81
|
+
{ id: 'mezz_C', x: 0, z: -13 },
|
|
82
|
+
|
|
83
|
+
// === REST / DRESSING (right wing, for old office compat) ===
|
|
84
|
+
{ id: 'rest_entry', x: 7.5, z: -5.5 },
|
|
85
|
+
{ id: 'dress_entry', x: 7.5, z: -1.5 },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
var CAMPUS_CONNECTIONS = [
|
|
89
|
+
// Lobby connections
|
|
90
|
+
['spawn', 'lobby'],
|
|
91
|
+
['lobby', 'lobby_left'],
|
|
92
|
+
['lobby', 'lobby_right'],
|
|
93
|
+
['lobby', 'corr_C'],
|
|
94
|
+
['lobby_left', 'corr_L'],
|
|
95
|
+
['lobby_right', 'corr_R'],
|
|
96
|
+
|
|
97
|
+
// Main corridor (horizontal)
|
|
98
|
+
['corr_L', 'corr_CL'],
|
|
99
|
+
['corr_CL', 'corr_C'],
|
|
100
|
+
['corr_C', 'corr_CR'],
|
|
101
|
+
['corr_CR', 'corr_R'],
|
|
102
|
+
|
|
103
|
+
// Corridor → workspace
|
|
104
|
+
['corr_C', 'work_N'],
|
|
105
|
+
['corr_CL', 'work_NW'],
|
|
106
|
+
['corr_CR', 'work_NE'],
|
|
107
|
+
|
|
108
|
+
// Workspace grid
|
|
109
|
+
['work_N', 'work_NW'],
|
|
110
|
+
['work_N', 'work_NE'],
|
|
111
|
+
['work_N', 'work_C'],
|
|
112
|
+
['work_NW', 'work_W'],
|
|
113
|
+
['work_NE', 'work_E'],
|
|
114
|
+
['work_W', 'work_C'],
|
|
115
|
+
['work_C', 'work_E'],
|
|
116
|
+
['work_W', 'work_SW'],
|
|
117
|
+
['work_C', 'work_S'],
|
|
118
|
+
['work_E', 'work_SE'],
|
|
119
|
+
['work_SW', 'work_S'],
|
|
120
|
+
['work_S', 'work_SE'],
|
|
121
|
+
|
|
122
|
+
// Designer wing (through gap in glass partition)
|
|
123
|
+
['work_NW', 'design_gate'],
|
|
124
|
+
['corr_L', 'design_gate'],
|
|
125
|
+
['design_gate', 'design_N'],
|
|
126
|
+
['design_N', 'design_C'],
|
|
127
|
+
['design_C', 'design_S'],
|
|
128
|
+
|
|
129
|
+
// Manager office — path goes AROUND the corner then through door
|
|
130
|
+
// corr_R(7,7) → mgr_hallway(7,3) → mgr_corner(7,0) → mgr_outside(12,0) → door → inside
|
|
131
|
+
['corr_R', 'mgr_hallway'], // walk south, outside left wall (x=7 < wall x=8)
|
|
132
|
+
['mgr_hallway', 'mgr_corner'], // walk further south past front-left corner (z=0 < wall z=1.5)
|
|
133
|
+
['mgr_corner', 'mgr_outside'], // walk east to front of door (z=0, safely below front wall z=1.5)
|
|
134
|
+
['mgr_outside', 'mgr_doorstep'], // step to door threshold (triggers open)
|
|
135
|
+
['mgr_doorstep', 'mgr_entry'], // walk through open door into office
|
|
136
|
+
['mgr_entry', 'mgr_center'], // walk deeper inside
|
|
137
|
+
['mgr_center', 'mgr_desk'], // walk to desk
|
|
138
|
+
|
|
139
|
+
// Workspace → back zone (through gap in glass partition at z=-7)
|
|
140
|
+
['work_S', 'back_gate'],
|
|
141
|
+
['back_gate', 'back_C'],
|
|
142
|
+
|
|
143
|
+
// Back corridor
|
|
144
|
+
['back_L', 'back_C'],
|
|
145
|
+
['back_C', 'back_R'],
|
|
146
|
+
['work_SW', 'back_L'],
|
|
147
|
+
['work_SE', 'back_R'],
|
|
148
|
+
|
|
149
|
+
// Back zones
|
|
150
|
+
['back_L', 'bar_entry'],
|
|
151
|
+
['bar_entry', 'bar_center'],
|
|
152
|
+
['back_C', 'rec_entry'],
|
|
153
|
+
['rec_entry', 'rec_center'],
|
|
154
|
+
['back_R', 'gym_entry'],
|
|
155
|
+
['gym_entry', 'gym_center'],
|
|
156
|
+
|
|
157
|
+
// Stairs / mezzanine
|
|
158
|
+
['back_R', 'stairs_bot'],
|
|
159
|
+
['stairs_bot', 'stairs_top'],
|
|
160
|
+
['stairs_top', 'mezz_C'],
|
|
161
|
+
|
|
162
|
+
// Rest/dressing (legacy)
|
|
163
|
+
['work_SE', 'rest_entry'],
|
|
164
|
+
['work_E', 'dress_entry'],
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
// === BUILD GRAPH ===
|
|
168
|
+
var adjacency = {};
|
|
169
|
+
var waypointMap = {};
|
|
170
|
+
|
|
171
|
+
function buildGraph() {
|
|
172
|
+
adjacency = {};
|
|
173
|
+
waypointMap = {};
|
|
174
|
+
CAMPUS_WAYPOINTS.forEach(function(wp) {
|
|
175
|
+
adjacency[wp.id] = [];
|
|
176
|
+
waypointMap[wp.id] = wp;
|
|
177
|
+
});
|
|
178
|
+
CAMPUS_CONNECTIONS.forEach(function(conn) {
|
|
179
|
+
if (adjacency[conn[0]] && adjacency[conn[1]]) {
|
|
180
|
+
adjacency[conn[0]].push(conn[1]);
|
|
181
|
+
adjacency[conn[1]].push(conn[0]);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
buildGraph();
|
|
186
|
+
|
|
187
|
+
// Find nearest waypoint to a world position
|
|
188
|
+
function nearestWaypoint(x, z) {
|
|
189
|
+
var best = null, bestDist = Infinity;
|
|
190
|
+
CAMPUS_WAYPOINTS.forEach(function(wp) {
|
|
191
|
+
var dx = wp.x - x, dz = wp.z - z;
|
|
192
|
+
var d = dx * dx + dz * dz;
|
|
193
|
+
if (d < bestDist) { bestDist = d; best = wp.id; }
|
|
194
|
+
});
|
|
195
|
+
return best;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// BFS shortest path
|
|
199
|
+
function findPath(startId, endId) {
|
|
200
|
+
if (startId === endId) return [startId];
|
|
201
|
+
var visited = {};
|
|
202
|
+
var queue = [[startId]];
|
|
203
|
+
visited[startId] = true;
|
|
204
|
+
while (queue.length > 0) {
|
|
205
|
+
var path = queue.shift();
|
|
206
|
+
var current = path[path.length - 1];
|
|
207
|
+
var neighbors = adjacency[current] || [];
|
|
208
|
+
for (var i = 0; i < neighbors.length; i++) {
|
|
209
|
+
var next = neighbors[i];
|
|
210
|
+
if (visited[next]) continue;
|
|
211
|
+
var newPath = path.concat([next]);
|
|
212
|
+
if (next === endId) return newPath;
|
|
213
|
+
visited[next] = true;
|
|
214
|
+
queue.push(newPath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ==================== PUBLIC API ====================
|
|
221
|
+
|
|
222
|
+
export function getNavigationPath(fromX, fromZ, toX, toZ) {
|
|
223
|
+
if (S.currentEnv !== 'campus') {
|
|
224
|
+
return [{ x: toX, z: toZ }];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var startWP = nearestWaypoint(fromX, fromZ);
|
|
228
|
+
var endWP = nearestWaypoint(toX, toZ);
|
|
229
|
+
|
|
230
|
+
if (!startWP || !endWP || startWP === endWP) {
|
|
231
|
+
return [{ x: toX, z: toZ }];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
var wpPath = findPath(startWP, endWP);
|
|
235
|
+
if (!wpPath || wpPath.length === 0) {
|
|
236
|
+
return [{ x: toX, z: toZ }];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
var result = [];
|
|
240
|
+
// Skip first waypoint if very close to current position
|
|
241
|
+
var firstWP = waypointMap[wpPath[0]];
|
|
242
|
+
var dx0 = firstWP.x - fromX, dz0 = firstWP.z - fromZ;
|
|
243
|
+
var startIdx = (dx0 * dx0 + dz0 * dz0 < 4) ? 1 : 0;
|
|
244
|
+
|
|
245
|
+
for (var i = startIdx; i < wpPath.length; i++) {
|
|
246
|
+
var wp = waypointMap[wpPath[i]];
|
|
247
|
+
var point = { x: wp.x, z: wp.z };
|
|
248
|
+
// Flag door waypoint
|
|
249
|
+
if (wpPath[i] === 'mgr_doorstep') {
|
|
250
|
+
point.triggerDoor = 'open';
|
|
251
|
+
}
|
|
252
|
+
result.push(point);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Add final walk to exact destination if far from last waypoint
|
|
256
|
+
var lastWP = waypointMap[wpPath[wpPath.length - 1]];
|
|
257
|
+
var dxEnd = lastWP.x - toX, dzEnd = lastWP.z - toZ;
|
|
258
|
+
if (dxEnd * dxEnd + dzEnd * dzEnd > 1) {
|
|
259
|
+
result.push({ x: toX, z: toZ });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return result;
|
|
263
|
+
}
|
package/office/scene.js
CHANGED
|
@@ -9,10 +9,10 @@ export function initScene() {
|
|
|
9
9
|
|
|
10
10
|
S.scene = new THREE.Scene();
|
|
11
11
|
S.scene.background = new THREE.Color(0x0d1117);
|
|
12
|
-
S.scene.fog = new THREE.Fog(0x0d1117,
|
|
12
|
+
S.scene.fog = new THREE.Fog(0x0d1117, 30, 80);
|
|
13
13
|
|
|
14
|
-
S.camera = new THREE.PerspectiveCamera(50, S.container.clientWidth / S.container.clientHeight, 0.1,
|
|
15
|
-
S.camera.position.set(0,
|
|
14
|
+
S.camera = new THREE.PerspectiveCamera(50, S.container.clientWidth / S.container.clientHeight, 0.1, 300);
|
|
15
|
+
S.camera.position.set(0, 15, 22);
|
|
16
16
|
S.camera.lookAt(0, 0, 0);
|
|
17
17
|
|
|
18
18
|
S.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
@@ -131,7 +131,7 @@ export function SpectatorCamera(camera, domElement) {
|
|
|
131
131
|
if (keys['KeyA'] || keys['ArrowLeft']) moveDir.x -= 1;
|
|
132
132
|
if (keys['KeyD'] || keys['ArrowRight']) moveDir.x += 1;
|
|
133
133
|
if (keys['KeyE'] || keys['Space']) moveDir.y += 1;
|
|
134
|
-
if (keys['KeyQ']
|
|
134
|
+
if (keys['KeyQ']) moveDir.y -= 1;
|
|
135
135
|
|
|
136
136
|
if (moveDir.lengthSq() > 0) {
|
|
137
137
|
moveDir.normalize();
|
package/office/state.js
CHANGED