watercooler 0.0.5 → 0.0.7
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/package.json +1 -1
- package/public/app.js +921 -259
- package/public/index.html +7 -7
package/public/app.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
3
|
+
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
|
4
|
+
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
|
5
|
+
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
|
3
6
|
|
|
4
7
|
// State
|
|
5
8
|
let config = { user: '', mailbox: '', avatar: null };
|
|
6
9
|
let messages = []; // Messages TO user (for main panel)
|
|
7
|
-
let allMessages = []; // All messages involving user (for
|
|
10
|
+
let allMessages = []; // All messages involving user (for desk dialogs)
|
|
8
11
|
let recipients = [];
|
|
9
12
|
let avatarStates = {}; // Map of name -> {tool_name, timestamp}
|
|
10
13
|
let scene, camera, renderer, controls;
|
|
@@ -12,10 +15,10 @@ let agentMeshes = new Map();
|
|
|
12
15
|
let connectionLines = [];
|
|
13
16
|
let raycaster, mouse;
|
|
14
17
|
|
|
15
|
-
// Color palette for agents
|
|
18
|
+
// Color palette for agents - modern muted tones
|
|
16
19
|
const agentColors = [
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
0x5EEAD4, 0x6EE7B7, 0x7DD3FC, 0xA78BFA, 0xFBBF24,
|
|
21
|
+
0xF9A8D4, 0x86EFAC, 0x93C5FD, 0xC4B5FD, 0x67E8F9
|
|
19
22
|
];
|
|
20
23
|
|
|
21
24
|
function getAgentColor(name) {
|
|
@@ -26,81 +29,130 @@ function getAgentColor(name) {
|
|
|
26
29
|
return agentColors[Math.abs(hash) % agentColors.length];
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
// Platform dimensions
|
|
33
|
+
const PLATFORM_SIZE = 60;
|
|
34
|
+
const PLATFORM_HEIGHT = 2;
|
|
35
|
+
const WALL_HEIGHT = 18;
|
|
36
|
+
|
|
37
|
+
// Animated objects
|
|
38
|
+
let holoSphere = null;
|
|
39
|
+
let holoParticles = null;
|
|
40
|
+
let glowLights = [];
|
|
41
|
+
let floatingParticles = [];
|
|
42
|
+
let composer = null;
|
|
43
|
+
let waterMesh = null;
|
|
44
|
+
|
|
29
45
|
// Initialize Three.js
|
|
30
46
|
function init() {
|
|
31
47
|
const container = document.getElementById('canvas-container');
|
|
32
48
|
|
|
33
49
|
scene = new THREE.Scene();
|
|
34
|
-
scene.background = new THREE.Color(
|
|
50
|
+
scene.background = new THREE.Color(0x1a3a3a);
|
|
51
|
+
scene.fog = new THREE.FogExp2(0x1a3a3a, 0.003);
|
|
35
52
|
|
|
36
|
-
camera = new THREE.PerspectiveCamera(
|
|
37
|
-
camera.position.set(
|
|
53
|
+
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
54
|
+
camera.position.set(55, 45, 55);
|
|
38
55
|
|
|
39
56
|
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
|
|
40
57
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
58
|
+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
|
41
59
|
renderer.shadowMap.enabled = true;
|
|
42
60
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
61
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
62
|
+
renderer.toneMappingExposure = 1.2;
|
|
43
63
|
container.appendChild(renderer.domElement);
|
|
44
64
|
|
|
45
65
|
controls = new OrbitControls(camera, renderer.domElement);
|
|
46
66
|
controls.enableDamping = true;
|
|
47
67
|
controls.dampingFactor = 0.05;
|
|
48
|
-
controls.maxPolarAngle = Math.PI / 2 - 0.
|
|
49
|
-
controls.minDistance =
|
|
50
|
-
controls.maxDistance =
|
|
68
|
+
controls.maxPolarAngle = Math.PI / 2 - 0.05;
|
|
69
|
+
controls.minDistance = 25;
|
|
70
|
+
controls.maxDistance = 120;
|
|
51
71
|
controls.enableZoom = true;
|
|
52
72
|
controls.zoomSpeed = 0.8;
|
|
53
|
-
controls.enablePan = false;
|
|
73
|
+
controls.enablePan = false;
|
|
74
|
+
controls.target.set(0, 5, 0);
|
|
54
75
|
controls.touches = {
|
|
55
76
|
ONE: THREE.TOUCH.ROTATE,
|
|
56
77
|
TWO: THREE.TOUCH.DOLLY_PAN
|
|
57
78
|
};
|
|
58
79
|
|
|
59
|
-
// Lighting
|
|
60
|
-
|
|
80
|
+
// === Lighting ===
|
|
81
|
+
// Soft ambient
|
|
82
|
+
const ambientLight = new THREE.AmbientLight(0x2d5a5a, 0.8);
|
|
61
83
|
scene.add(ambientLight);
|
|
62
84
|
|
|
63
|
-
|
|
64
|
-
dirLight.
|
|
85
|
+
// Main directional light (warm)
|
|
86
|
+
const dirLight = new THREE.DirectionalLight(0xfff5e6, 0.6);
|
|
87
|
+
dirLight.position.set(40, 80, 30);
|
|
65
88
|
dirLight.castShadow = true;
|
|
66
|
-
dirLight.shadow.camera.left = -
|
|
67
|
-
dirLight.shadow.camera.right =
|
|
68
|
-
dirLight.shadow.camera.top =
|
|
69
|
-
dirLight.shadow.camera.bottom = -
|
|
89
|
+
dirLight.shadow.camera.left = -40;
|
|
90
|
+
dirLight.shadow.camera.right = 40;
|
|
91
|
+
dirLight.shadow.camera.top = 40;
|
|
92
|
+
dirLight.shadow.camera.bottom = -40;
|
|
70
93
|
dirLight.shadow.mapSize.width = 2048;
|
|
71
94
|
dirLight.shadow.mapSize.height = 2048;
|
|
95
|
+
dirLight.shadow.bias = -0.001;
|
|
72
96
|
scene.add(dirLight);
|
|
73
97
|
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
// Fill light from below (teal tint)
|
|
99
|
+
const fillLight = new THREE.DirectionalLight(0x4fd1c5, 0.3);
|
|
100
|
+
fillLight.position.set(-20, 5, -20);
|
|
101
|
+
scene.add(fillLight);
|
|
102
|
+
|
|
103
|
+
// Hemisphere light for natural ambient
|
|
104
|
+
const hemiLight = new THREE.HemisphereLight(0x4fd1c5, 0x1a3a3a, 0.4);
|
|
105
|
+
scene.add(hemiLight);
|
|
106
|
+
|
|
107
|
+
// Additional accent lights for bloom effect
|
|
108
|
+
// Center glow from holographic sphere area
|
|
109
|
+
const centerGlow = new THREE.PointLight(0x4fd1c5, 0.6, 50);
|
|
110
|
+
centerGlow.position.set(0, PLATFORM_HEIGHT + 15, 0);
|
|
111
|
+
scene.add(centerGlow);
|
|
112
|
+
|
|
113
|
+
// Edge accent lights
|
|
114
|
+
const edgeLight1 = new THREE.PointLight(0x88ffdd, 0.4, 30);
|
|
115
|
+
edgeLight1.position.set(30, PLATFORM_HEIGHT + 10, 30);
|
|
116
|
+
scene.add(edgeLight1);
|
|
117
|
+
|
|
118
|
+
const edgeLight2 = new THREE.PointLight(0x88ffdd, 0.4, 30);
|
|
119
|
+
edgeLight2.position.set(-30, PLATFORM_HEIGHT + 10, -30);
|
|
120
|
+
scene.add(edgeLight2);
|
|
84
121
|
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
grid.material.opacity = 0.2;
|
|
88
|
-
grid.material.transparent = true;
|
|
89
|
-
scene.add(grid);
|
|
122
|
+
// === Platform ===
|
|
123
|
+
createPlatform();
|
|
90
124
|
|
|
91
|
-
//
|
|
92
|
-
|
|
125
|
+
// === Reflective Water Surface ===
|
|
126
|
+
createReflectiveWater();
|
|
127
|
+
|
|
128
|
+
// === Glass Walls ===
|
|
129
|
+
createGlassWalls();
|
|
130
|
+
|
|
131
|
+
// === Decorative Plants ===
|
|
132
|
+
createPlants();
|
|
133
|
+
|
|
134
|
+
// === Holographic Sphere ===
|
|
135
|
+
createHolographicSphere();
|
|
136
|
+
|
|
137
|
+
// === Ambient Glow Lights ===
|
|
138
|
+
createGlowLights();
|
|
139
|
+
|
|
140
|
+
// === Floating Particles ===
|
|
141
|
+
createFloatingParticles();
|
|
142
|
+
|
|
143
|
+
// === Background Stars ===
|
|
144
|
+
createBackgroundStars();
|
|
93
145
|
|
|
94
146
|
window.addEventListener('resize', onWindowResize);
|
|
95
147
|
|
|
96
|
-
// Raycaster for
|
|
148
|
+
// Raycaster for desk clicks
|
|
97
149
|
raycaster = new THREE.Raycaster();
|
|
98
150
|
mouse = new THREE.Vector2();
|
|
99
|
-
renderer.domElement.addEventListener('click',
|
|
151
|
+
renderer.domElement.addEventListener('click', onDeskClick);
|
|
100
152
|
|
|
101
153
|
// Add touch support for mobile
|
|
102
|
-
renderer.domElement.addEventListener('touchstart',
|
|
103
|
-
renderer.domElement.addEventListener('touchend',
|
|
154
|
+
renderer.domElement.addEventListener('touchstart', onDeskTouchStart, { passive: false });
|
|
155
|
+
renderer.domElement.addEventListener('touchend', onDeskTouchEnd, { passive: false });
|
|
104
156
|
|
|
105
157
|
// Disable context menu on mobile for better UX
|
|
106
158
|
renderer.domElement.addEventListener('contextmenu', (e) => e.preventDefault());
|
|
@@ -110,105 +162,649 @@ function init() {
|
|
|
110
162
|
setTimeout(onWindowResize, 100);
|
|
111
163
|
});
|
|
112
164
|
|
|
165
|
+
// === Post Processing ===
|
|
166
|
+
setupPostProcessing();
|
|
167
|
+
|
|
113
168
|
animate();
|
|
114
169
|
}
|
|
115
170
|
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
171
|
+
function createPlatform() {
|
|
172
|
+
// Main platform - dark concrete slab
|
|
173
|
+
const platformGeo = new THREE.BoxGeometry(PLATFORM_SIZE, PLATFORM_HEIGHT, PLATFORM_SIZE);
|
|
174
|
+
const platformMat = new THREE.MeshStandardMaterial({
|
|
175
|
+
color: 0x3a3a3a,
|
|
176
|
+
roughness: 0.4,
|
|
177
|
+
metalness: 0.1
|
|
178
|
+
});
|
|
179
|
+
const platform = new THREE.Mesh(platformGeo, platformMat);
|
|
180
|
+
platform.position.y = PLATFORM_HEIGHT / 2;
|
|
181
|
+
platform.receiveShadow = true;
|
|
182
|
+
platform.castShadow = true;
|
|
183
|
+
scene.add(platform);
|
|
184
|
+
|
|
185
|
+
// Edge trim - lighter accent
|
|
186
|
+
const trimGeo = new THREE.BoxGeometry(PLATFORM_SIZE + 0.5, 0.3, PLATFORM_SIZE + 0.5);
|
|
187
|
+
const trimMat = new THREE.MeshStandardMaterial({
|
|
188
|
+
color: 0x5a5a5a,
|
|
189
|
+
roughness: 0.3,
|
|
190
|
+
metalness: 0.3
|
|
191
|
+
});
|
|
192
|
+
const trim = new THREE.Mesh(trimGeo, trimMat);
|
|
193
|
+
trim.position.y = PLATFORM_HEIGHT + 0.15;
|
|
194
|
+
scene.add(trim);
|
|
195
|
+
|
|
196
|
+
// Floor surface - polished concrete with subtle grid
|
|
197
|
+
const floorGeo = new THREE.PlaneGeometry(PLATFORM_SIZE - 2, PLATFORM_SIZE - 2);
|
|
198
|
+
const floorMat = new THREE.MeshStandardMaterial({
|
|
199
|
+
color: 0x4a4a4a,
|
|
200
|
+
roughness: 0.2,
|
|
201
|
+
metalness: 0.15
|
|
202
|
+
});
|
|
203
|
+
const floor = new THREE.Mesh(floorGeo, floorMat);
|
|
204
|
+
floor.rotation.x = -Math.PI / 2;
|
|
205
|
+
floor.position.y = PLATFORM_HEIGHT + 0.02;
|
|
206
|
+
floor.receiveShadow = true;
|
|
207
|
+
scene.add(floor);
|
|
208
|
+
|
|
209
|
+
// Subtle grid on floor
|
|
210
|
+
const gridHelper = new THREE.GridHelper(PLATFORM_SIZE - 4, 20, 0x555555, 0x444444);
|
|
211
|
+
gridHelper.position.y = PLATFORM_HEIGHT + 0.05;
|
|
212
|
+
gridHelper.material.opacity = 0.15;
|
|
213
|
+
gridHelper.material.transparent = true;
|
|
214
|
+
scene.add(gridHelper);
|
|
215
|
+
|
|
216
|
+
// Ground below platform (dark reflection surface)
|
|
217
|
+
const groundGeo = new THREE.PlaneGeometry(300, 300);
|
|
218
|
+
const groundMat = new THREE.MeshStandardMaterial({
|
|
219
|
+
color: 0x1a3a3a,
|
|
220
|
+
roughness: 0.6,
|
|
221
|
+
metalness: 0.2
|
|
222
|
+
});
|
|
223
|
+
const ground = new THREE.Mesh(groundGeo, groundMat);
|
|
224
|
+
ground.rotation.x = -Math.PI / 2;
|
|
225
|
+
ground.position.y = -0.1;
|
|
226
|
+
ground.receiveShadow = true;
|
|
227
|
+
scene.add(ground);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function createReflectiveWater() {
|
|
231
|
+
// Reflective water surface below the platform
|
|
232
|
+
const waterSize = PLATFORM_SIZE * 1.5;
|
|
233
|
+
const waterGeo = new THREE.PlaneGeometry(waterSize, waterSize, 64, 64);
|
|
234
|
+
|
|
235
|
+
// Create a custom shader material for reflective water effect
|
|
236
|
+
const waterMat = new THREE.MeshPhysicalMaterial({
|
|
237
|
+
color: 0x0d3333,
|
|
238
|
+
metalness: 0.9,
|
|
239
|
+
roughness: 0.1,
|
|
240
|
+
transparent: true,
|
|
241
|
+
opacity: 0.85,
|
|
242
|
+
transmission: 0.3,
|
|
243
|
+
thickness: 0.5,
|
|
244
|
+
clearcoat: 1.0,
|
|
245
|
+
clearcoatRoughness: 0.1,
|
|
246
|
+
side: THREE.DoubleSide
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
waterMesh = new THREE.Mesh(waterGeo, waterMat);
|
|
250
|
+
waterMesh.rotation.x = -Math.PI / 2;
|
|
251
|
+
waterMesh.position.y = -0.5;
|
|
252
|
+
waterMesh.receiveShadow = true;
|
|
253
|
+
scene.add(waterMesh);
|
|
254
|
+
|
|
255
|
+
// Add subtle ripple effect using vertex displacement
|
|
256
|
+
const positions = waterMesh.geometry.attributes.position;
|
|
257
|
+
const initialPositions = positions.array.slice();
|
|
258
|
+
waterMesh.userData.initialPositions = initialPositions;
|
|
259
|
+
waterMesh.userData.ripplePhase = 0;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function setupPostProcessing() {
|
|
263
|
+
// Setup EffectComposer for bloom
|
|
264
|
+
composer = new EffectComposer(renderer);
|
|
265
|
+
|
|
266
|
+
// Add render pass
|
|
267
|
+
const renderPass = new RenderPass(scene, camera);
|
|
268
|
+
composer.addPass(renderPass);
|
|
269
|
+
|
|
270
|
+
// Add bloom pass
|
|
271
|
+
const bloomPass = new UnrealBloomPass(
|
|
272
|
+
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
273
|
+
0.8, // strength
|
|
274
|
+
0.4, // radius
|
|
275
|
+
0.75 // threshold
|
|
276
|
+
);
|
|
277
|
+
composer.addPass(bloomPass);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function createGlassWalls() {
|
|
281
|
+
const glassMat = new THREE.MeshPhysicalMaterial({
|
|
282
|
+
color: 0x88cccc,
|
|
283
|
+
transparent: true,
|
|
284
|
+
opacity: 0.08,
|
|
285
|
+
roughness: 0.05,
|
|
286
|
+
metalness: 0.0,
|
|
287
|
+
transmission: 0.95,
|
|
288
|
+
thickness: 0.5,
|
|
289
|
+
side: THREE.DoubleSide
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const wallHeight = WALL_HEIGHT;
|
|
293
|
+
const wallY = PLATFORM_HEIGHT + wallHeight / 2;
|
|
294
|
+
const halfSize = PLATFORM_SIZE / 2;
|
|
295
|
+
|
|
296
|
+
// Back wall
|
|
297
|
+
const backWall = new THREE.Mesh(
|
|
298
|
+
new THREE.PlaneGeometry(PLATFORM_SIZE, wallHeight),
|
|
299
|
+
glassMat
|
|
300
|
+
);
|
|
301
|
+
backWall.position.set(0, wallY, -halfSize);
|
|
302
|
+
scene.add(backWall);
|
|
303
|
+
|
|
304
|
+
// Left wall
|
|
305
|
+
const leftWall = new THREE.Mesh(
|
|
306
|
+
new THREE.PlaneGeometry(PLATFORM_SIZE, wallHeight),
|
|
307
|
+
glassMat
|
|
308
|
+
);
|
|
309
|
+
leftWall.position.set(-halfSize, wallY, 0);
|
|
310
|
+
leftWall.rotation.y = Math.PI / 2;
|
|
311
|
+
scene.add(leftWall);
|
|
312
|
+
|
|
313
|
+
// Right wall (partial, for openness)
|
|
314
|
+
const rightWall = new THREE.Mesh(
|
|
315
|
+
new THREE.PlaneGeometry(PLATFORM_SIZE, wallHeight),
|
|
316
|
+
glassMat
|
|
317
|
+
);
|
|
318
|
+
rightWall.position.set(halfSize, wallY, 0);
|
|
319
|
+
rightWall.rotation.y = -Math.PI / 2;
|
|
320
|
+
scene.add(rightWall);
|
|
321
|
+
|
|
322
|
+
// Glass edge frames (vertical pillars at corners)
|
|
323
|
+
const pillarGeo = new THREE.BoxGeometry(0.5, wallHeight, 0.5);
|
|
324
|
+
const pillarMat = new THREE.MeshStandardMaterial({
|
|
325
|
+
color: 0x777777,
|
|
326
|
+
roughness: 0.2,
|
|
327
|
+
metalness: 0.6
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const corners = [
|
|
331
|
+
[-halfSize, wallY, -halfSize],
|
|
332
|
+
[halfSize, wallY, -halfSize],
|
|
333
|
+
[-halfSize, wallY, halfSize],
|
|
334
|
+
[halfSize, wallY, halfSize]
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
corners.forEach(pos => {
|
|
338
|
+
const pillar = new THREE.Mesh(pillarGeo, pillarMat);
|
|
339
|
+
pillar.position.set(...pos);
|
|
340
|
+
pillar.castShadow = true;
|
|
341
|
+
scene.add(pillar);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Top edge frame
|
|
345
|
+
const topFrameMat = new THREE.MeshStandardMaterial({
|
|
346
|
+
color: 0x666666,
|
|
347
|
+
roughness: 0.2,
|
|
348
|
+
metalness: 0.5
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const frameY = PLATFORM_HEIGHT + wallHeight;
|
|
352
|
+
|
|
353
|
+
// Back top frame
|
|
354
|
+
const backFrame = new THREE.Mesh(new THREE.BoxGeometry(PLATFORM_SIZE, 0.3, 0.3), topFrameMat);
|
|
355
|
+
backFrame.position.set(0, frameY, -halfSize);
|
|
356
|
+
scene.add(backFrame);
|
|
357
|
+
|
|
358
|
+
// Left top frame
|
|
359
|
+
const leftFrame = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, PLATFORM_SIZE), topFrameMat);
|
|
360
|
+
leftFrame.position.set(-halfSize, frameY, 0);
|
|
361
|
+
scene.add(leftFrame);
|
|
362
|
+
|
|
363
|
+
// Right top frame
|
|
364
|
+
const rightFrame = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, PLATFORM_SIZE), topFrameMat);
|
|
365
|
+
rightFrame.position.set(halfSize, frameY, 0);
|
|
366
|
+
scene.add(rightFrame);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function createPlants() {
|
|
370
|
+
const plantPositions = [
|
|
371
|
+
// Corner clusters
|
|
372
|
+
[-25, PLATFORM_HEIGHT, -25],
|
|
373
|
+
[25, PLATFORM_HEIGHT, -25],
|
|
374
|
+
[-25, PLATFORM_HEIGHT, 25],
|
|
375
|
+
[25, PLATFORM_HEIGHT, 25],
|
|
376
|
+
// Edge accents
|
|
377
|
+
[-20, PLATFORM_HEIGHT, 27],
|
|
378
|
+
[20, PLATFORM_HEIGHT, 27],
|
|
379
|
+
[-27, PLATFORM_HEIGHT, 0],
|
|
380
|
+
[27, PLATFORM_HEIGHT, -15],
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
plantPositions.forEach(pos => {
|
|
384
|
+
createPlantCluster(pos[0], pos[1], pos[2]);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function createPlantCluster(x, y, z) {
|
|
389
|
+
const group = new THREE.Group();
|
|
390
|
+
|
|
391
|
+
// Planter box
|
|
392
|
+
const planterGeo = new THREE.BoxGeometry(3, 1.5, 3);
|
|
393
|
+
const planterMat = new THREE.MeshStandardMaterial({
|
|
394
|
+
color: 0x2a2a2a,
|
|
395
|
+
roughness: 0.6,
|
|
396
|
+
metalness: 0.1
|
|
397
|
+
});
|
|
398
|
+
const planter = new THREE.Mesh(planterGeo, planterMat);
|
|
399
|
+
planter.position.y = 0.75;
|
|
400
|
+
planter.castShadow = true;
|
|
401
|
+
planter.receiveShadow = true;
|
|
402
|
+
group.add(planter);
|
|
403
|
+
|
|
404
|
+
// Soil
|
|
405
|
+
const soilGeo = new THREE.BoxGeometry(2.6, 0.2, 2.6);
|
|
406
|
+
const soilMat = new THREE.MeshStandardMaterial({ color: 0x3d2817 });
|
|
407
|
+
const soil = new THREE.Mesh(soilGeo, soilMat);
|
|
408
|
+
soil.position.y = 1.5;
|
|
409
|
+
group.add(soil);
|
|
410
|
+
|
|
411
|
+
// Foliage - multiple spheres for bush look
|
|
412
|
+
const leafColors = [0x1a6b3a, 0x228B22, 0x2d8b4e, 0x1f7a3f];
|
|
413
|
+
|
|
414
|
+
for (let i = 0; i < 5; i++) {
|
|
415
|
+
const size = 0.6 + Math.random() * 0.8;
|
|
416
|
+
const leafGeo = new THREE.SphereGeometry(size, 8, 8);
|
|
417
|
+
const leafMat = new THREE.MeshStandardMaterial({
|
|
418
|
+
color: leafColors[Math.floor(Math.random() * leafColors.length)],
|
|
419
|
+
roughness: 0.8
|
|
420
|
+
});
|
|
421
|
+
const leaf = new THREE.Mesh(leafGeo, leafMat);
|
|
422
|
+
leaf.position.set(
|
|
423
|
+
(Math.random() - 0.5) * 1.5,
|
|
424
|
+
1.8 + Math.random() * 1.5,
|
|
425
|
+
(Math.random() - 0.5) * 1.5
|
|
426
|
+
);
|
|
427
|
+
leaf.castShadow = true;
|
|
428
|
+
group.add(leaf);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Tall fern-like elements (cone shapes)
|
|
432
|
+
for (let i = 0; i < 3; i++) {
|
|
433
|
+
const fernGeo = new THREE.ConeGeometry(0.3, 2 + Math.random() * 2, 6);
|
|
434
|
+
const fernMat = new THREE.MeshStandardMaterial({
|
|
435
|
+
color: 0x1a5c2e,
|
|
436
|
+
roughness: 0.7
|
|
437
|
+
});
|
|
438
|
+
const fern = new THREE.Mesh(fernGeo, fernMat);
|
|
439
|
+
fern.position.set(
|
|
440
|
+
(Math.random() - 0.5) * 1.5,
|
|
441
|
+
2.5 + Math.random() * 1.5,
|
|
442
|
+
(Math.random() - 0.5) * 1.5
|
|
443
|
+
);
|
|
444
|
+
fern.castShadow = true;
|
|
445
|
+
group.add(fern);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
group.position.set(x, y, z);
|
|
449
|
+
scene.add(group);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function createHolographicSphere() {
|
|
453
|
+
// Wireframe sphere
|
|
454
|
+
const sphereGeo = new THREE.IcosahedronGeometry(6, 3);
|
|
455
|
+
const sphereMat = new THREE.MeshBasicMaterial({
|
|
456
|
+
color: 0x4fd1c5,
|
|
457
|
+
wireframe: true,
|
|
458
|
+
transparent: true,
|
|
459
|
+
opacity: 0.3
|
|
460
|
+
});
|
|
461
|
+
holoSphere = new THREE.Mesh(sphereGeo, sphereMat);
|
|
462
|
+
holoSphere.position.set(0, PLATFORM_HEIGHT + 12, 0);
|
|
463
|
+
scene.add(holoSphere);
|
|
464
|
+
|
|
465
|
+
// Inner glow sphere
|
|
466
|
+
const innerGeo = new THREE.SphereGeometry(4, 32, 32);
|
|
467
|
+
const innerMat = new THREE.MeshBasicMaterial({
|
|
468
|
+
color: 0x4fd1c5,
|
|
469
|
+
transparent: true,
|
|
470
|
+
opacity: 0.05
|
|
471
|
+
});
|
|
472
|
+
const innerSphere = new THREE.Mesh(innerGeo, innerMat);
|
|
473
|
+
holoSphere.add(innerSphere);
|
|
474
|
+
|
|
475
|
+
// Point cloud on sphere surface
|
|
476
|
+
const particleCount = 300;
|
|
477
|
+
const positions = new Float32Array(particleCount * 3);
|
|
478
|
+
for (let i = 0; i < particleCount; i++) {
|
|
479
|
+
const theta = Math.random() * Math.PI * 2;
|
|
480
|
+
const phi = Math.acos(2 * Math.random() - 1);
|
|
481
|
+
const r = 5.5 + Math.random() * 0.5;
|
|
482
|
+
positions[i * 3] = r * Math.sin(phi) * Math.cos(theta);
|
|
483
|
+
positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta);
|
|
484
|
+
positions[i * 3 + 2] = r * Math.cos(phi);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const particleGeo = new THREE.BufferGeometry();
|
|
488
|
+
particleGeo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
489
|
+
const particleMat = new THREE.PointsMaterial({
|
|
490
|
+
color: 0x88ffee,
|
|
491
|
+
size: 0.15,
|
|
492
|
+
transparent: true,
|
|
493
|
+
opacity: 0.6,
|
|
494
|
+
blending: THREE.AdditiveBlending
|
|
495
|
+
});
|
|
496
|
+
holoParticles = new THREE.Points(particleGeo, particleMat);
|
|
497
|
+
holoSphere.add(holoParticles);
|
|
498
|
+
|
|
499
|
+
// Point light from the sphere
|
|
500
|
+
const sphereLight = new THREE.PointLight(0x4fd1c5, 0.8, 35);
|
|
501
|
+
sphereLight.position.copy(holoSphere.position);
|
|
502
|
+
scene.add(sphereLight);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function createGlowLights() {
|
|
506
|
+
// Floor-standing lamp posts
|
|
507
|
+
const lampPositions = [
|
|
508
|
+
[20, PLATFORM_HEIGHT, 15],
|
|
509
|
+
[-20, PLATFORM_HEIGHT, 15],
|
|
510
|
+
[20, PLATFORM_HEIGHT, -20],
|
|
511
|
+
[-20, PLATFORM_HEIGHT, -20],
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
lampPositions.forEach(pos => {
|
|
515
|
+
// Lamp post
|
|
516
|
+
const postGeo = new THREE.CylinderGeometry(0.15, 0.15, 6, 8);
|
|
517
|
+
const postMat = new THREE.MeshStandardMaterial({
|
|
518
|
+
color: 0x555555,
|
|
519
|
+
roughness: 0.3,
|
|
520
|
+
metalness: 0.7
|
|
521
|
+
});
|
|
522
|
+
const post = new THREE.Mesh(postGeo, postMat);
|
|
523
|
+
post.position.set(pos[0], pos[1] + 3, pos[2]);
|
|
524
|
+
post.castShadow = true;
|
|
525
|
+
scene.add(post);
|
|
120
526
|
|
|
121
|
-
//
|
|
122
|
-
|
|
527
|
+
// Lamp bulb
|
|
528
|
+
const bulbGeo = new THREE.SphereGeometry(0.4, 16, 16);
|
|
529
|
+
const bulbMat = new THREE.MeshBasicMaterial({
|
|
530
|
+
color: 0xffcc66,
|
|
531
|
+
transparent: true,
|
|
532
|
+
opacity: 0.9
|
|
533
|
+
});
|
|
534
|
+
const bulb = new THREE.Mesh(bulbGeo, bulbMat);
|
|
535
|
+
bulb.position.set(pos[0], pos[1] + 6.2, pos[2]);
|
|
536
|
+
scene.add(bulb);
|
|
123
537
|
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
538
|
+
// Point light
|
|
539
|
+
const light = new THREE.PointLight(0xffcc66, 0.5, 18);
|
|
540
|
+
light.position.set(pos[0], pos[1] + 6.2, pos[2]);
|
|
541
|
+
light.castShadow = false;
|
|
542
|
+
scene.add(light);
|
|
543
|
+
glowLights.push({ bulb, light, baseIntensity: 0.5 });
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function createFloatingParticles() {
|
|
548
|
+
const particleCount = 80;
|
|
549
|
+
const positions = new Float32Array(particleCount * 3);
|
|
550
|
+
|
|
551
|
+
for (let i = 0; i < particleCount; i++) {
|
|
552
|
+
positions[i * 3] = (Math.random() - 0.5) * PLATFORM_SIZE;
|
|
553
|
+
positions[i * 3 + 1] = PLATFORM_HEIGHT + 2 + Math.random() * WALL_HEIGHT;
|
|
554
|
+
positions[i * 3 + 2] = (Math.random() - 0.5) * PLATFORM_SIZE;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const geometry = new THREE.BufferGeometry();
|
|
558
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
559
|
+
|
|
560
|
+
const material = new THREE.PointsMaterial({
|
|
561
|
+
color: 0x88ffdd,
|
|
562
|
+
size: 0.12,
|
|
563
|
+
transparent: true,
|
|
564
|
+
opacity: 0.4,
|
|
565
|
+
blending: THREE.AdditiveBlending
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const particles = new THREE.Points(geometry, material);
|
|
569
|
+
scene.add(particles);
|
|
570
|
+
floatingParticles.push(particles);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function createBackgroundStars() {
|
|
574
|
+
// Distant stars/sparkles in the background
|
|
575
|
+
const starCount = 200;
|
|
576
|
+
const positions = new Float32Array(starCount * 3);
|
|
577
|
+
const sizes = new Float32Array(starCount);
|
|
578
|
+
|
|
579
|
+
for (let i = 0; i < starCount; i++) {
|
|
580
|
+
// Place stars far outside the platform
|
|
581
|
+
const theta = Math.random() * Math.PI * 2;
|
|
582
|
+
const phi = Math.acos(2 * Math.random() - 1);
|
|
583
|
+
const radius = 100 + Math.random() * 150;
|
|
129
584
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
leaves.position.set(x, 6, z);
|
|
134
|
-
leaves.castShadow = true;
|
|
585
|
+
positions[i * 3] = radius * Math.sin(phi) * Math.cos(theta);
|
|
586
|
+
positions[i * 3 + 1] = 20 + Math.random() * 100;
|
|
587
|
+
positions[i * 3 + 2] = radius * Math.sin(phi) * Math.sin(theta);
|
|
135
588
|
|
|
136
|
-
|
|
137
|
-
scene.add(leaves);
|
|
589
|
+
sizes[i] = 0.5 + Math.random() * 1.5;
|
|
138
590
|
}
|
|
591
|
+
|
|
592
|
+
const geometry = new THREE.BufferGeometry();
|
|
593
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
594
|
+
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
|
|
595
|
+
|
|
596
|
+
const material = new THREE.PointsMaterial({
|
|
597
|
+
color: 0xaaddff,
|
|
598
|
+
size: 1.0,
|
|
599
|
+
transparent: true,
|
|
600
|
+
opacity: 0.6,
|
|
601
|
+
blending: THREE.AdditiveBlending,
|
|
602
|
+
sizeAttenuation: true
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
const stars = new THREE.Points(geometry, material);
|
|
606
|
+
scene.add(stars);
|
|
607
|
+
|
|
608
|
+
// Animate stars with twinkle effect
|
|
609
|
+
stars.userData.twinklePhase = Math.random() * Math.PI * 2;
|
|
610
|
+
|
|
611
|
+
// Add to floatingParticles for animation
|
|
612
|
+
floatingParticles.push(stars);
|
|
139
613
|
}
|
|
140
614
|
|
|
141
|
-
function
|
|
615
|
+
function createAgentDesk(name, position, toolName = null) {
|
|
142
616
|
const color = getAgentColor(name);
|
|
143
617
|
const group = new THREE.Group();
|
|
144
618
|
group.position.copy(position);
|
|
619
|
+
group.position.y = PLATFORM_HEIGHT;
|
|
620
|
+
|
|
621
|
+
// Modern desk - white top with thin legs
|
|
622
|
+
const deskTopGeo = new THREE.BoxGeometry(5, 0.2, 3);
|
|
623
|
+
const deskMat = new THREE.MeshStandardMaterial({
|
|
624
|
+
color: 0xe8e8e8,
|
|
625
|
+
roughness: 0.3,
|
|
626
|
+
metalness: 0.1
|
|
627
|
+
});
|
|
628
|
+
const deskTop = new THREE.Mesh(deskTopGeo, deskMat);
|
|
629
|
+
deskTop.position.y = 2.5;
|
|
630
|
+
deskTop.castShadow = true;
|
|
631
|
+
deskTop.receiveShadow = true;
|
|
632
|
+
group.add(deskTop);
|
|
633
|
+
|
|
634
|
+
// Desk legs - thin metal
|
|
635
|
+
const legGeo = new THREE.CylinderGeometry(0.08, 0.08, 2.4, 8);
|
|
636
|
+
const legMat = new THREE.MeshStandardMaterial({
|
|
637
|
+
color: 0x999999,
|
|
638
|
+
roughness: 0.2,
|
|
639
|
+
metalness: 0.7
|
|
640
|
+
});
|
|
641
|
+
const legPositions = [
|
|
642
|
+
[-2.2, 1.2, -1.2],
|
|
643
|
+
[2.2, 1.2, -1.2],
|
|
644
|
+
[-2.2, 1.2, 1.2],
|
|
645
|
+
[2.2, 1.2, 1.2]
|
|
646
|
+
];
|
|
647
|
+
legPositions.forEach(pos => {
|
|
648
|
+
const leg = new THREE.Mesh(legGeo, legMat);
|
|
649
|
+
leg.position.set(...pos);
|
|
650
|
+
group.add(leg);
|
|
651
|
+
});
|
|
145
652
|
|
|
146
|
-
//
|
|
147
|
-
const
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
group.add(
|
|
170
|
-
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
653
|
+
// Modern chair - sleek
|
|
654
|
+
const chairSeatGeo = new THREE.BoxGeometry(1.8, 0.15, 1.8);
|
|
655
|
+
const chairMat = new THREE.MeshStandardMaterial({
|
|
656
|
+
color: 0x2a2a2a,
|
|
657
|
+
roughness: 0.5,
|
|
658
|
+
metalness: 0.2
|
|
659
|
+
});
|
|
660
|
+
const chairSeat = new THREE.Mesh(chairSeatGeo, chairMat);
|
|
661
|
+
chairSeat.position.set(0, 1.6, 3.2);
|
|
662
|
+
chairSeat.castShadow = true;
|
|
663
|
+
group.add(chairSeat);
|
|
664
|
+
|
|
665
|
+
// Chair back - curved look (box approximation)
|
|
666
|
+
const chairBackGeo = new THREE.BoxGeometry(1.8, 2.2, 0.15);
|
|
667
|
+
const chairBack = new THREE.Mesh(chairBackGeo, chairMat);
|
|
668
|
+
chairBack.position.set(0, 2.7, 4.1);
|
|
669
|
+
chairBack.castShadow = true;
|
|
670
|
+
group.add(chairBack);
|
|
671
|
+
|
|
672
|
+
// Chair post
|
|
673
|
+
const chairPostGeo = new THREE.CylinderGeometry(0.1, 0.1, 1.2, 8);
|
|
674
|
+
const chairPost = new THREE.Mesh(chairPostGeo, legMat);
|
|
675
|
+
chairPost.position.set(0, 0.9, 3.2);
|
|
676
|
+
group.add(chairPost);
|
|
677
|
+
|
|
678
|
+
// Chair base star
|
|
679
|
+
for (let i = 0; i < 5; i++) {
|
|
680
|
+
const armGeo = new THREE.CylinderGeometry(0.06, 0.06, 1.2, 6);
|
|
681
|
+
const arm = new THREE.Mesh(armGeo, legMat);
|
|
682
|
+
const angle = (i / 5) * Math.PI * 2;
|
|
683
|
+
arm.rotation.z = Math.PI / 2;
|
|
684
|
+
arm.position.set(
|
|
685
|
+
Math.cos(angle) * 0.5,
|
|
686
|
+
0.3,
|
|
687
|
+
3.2 + Math.sin(angle) * 0.5
|
|
688
|
+
);
|
|
689
|
+
arm.rotation.y = angle;
|
|
690
|
+
group.add(arm);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Person - Body (sitting, modern look)
|
|
694
|
+
const bodyGeo = new THREE.CylinderGeometry(0.6, 0.5, 2, 8);
|
|
695
|
+
const bodyMat = new THREE.MeshStandardMaterial({
|
|
696
|
+
color: color,
|
|
697
|
+
roughness: 0.6,
|
|
698
|
+
metalness: 0.05
|
|
699
|
+
});
|
|
700
|
+
const body = new THREE.Mesh(bodyGeo, bodyMat);
|
|
701
|
+
body.position.set(0, 2.7, 3.2);
|
|
702
|
+
body.castShadow = true;
|
|
703
|
+
group.add(body);
|
|
704
|
+
|
|
705
|
+
// Person - Head
|
|
706
|
+
const headGeo = new THREE.SphereGeometry(0.5, 16, 16);
|
|
707
|
+
const headMat = new THREE.MeshStandardMaterial({
|
|
708
|
+
color: 0xf5d0b0,
|
|
709
|
+
roughness: 0.7
|
|
710
|
+
});
|
|
711
|
+
const head = new THREE.Mesh(headGeo, headMat);
|
|
712
|
+
head.position.set(0, 4.0, 3.2);
|
|
713
|
+
head.castShadow = true;
|
|
714
|
+
group.add(head);
|
|
715
|
+
|
|
716
|
+
// Person - Arms on desk
|
|
717
|
+
const armObjGeo = new THREE.CylinderGeometry(0.12, 0.12, 1.8, 6);
|
|
718
|
+
const armMat = new THREE.MeshStandardMaterial({ color: color, roughness: 0.6 });
|
|
719
|
+
|
|
720
|
+
const leftArm = new THREE.Mesh(armObjGeo, armMat);
|
|
721
|
+
leftArm.rotation.z = Math.PI / 2;
|
|
722
|
+
leftArm.rotation.y = 0.3;
|
|
723
|
+
leftArm.position.set(-0.8, 2.8, 2);
|
|
724
|
+
group.add(leftArm);
|
|
725
|
+
|
|
726
|
+
const rightArm = new THREE.Mesh(armObjGeo, armMat);
|
|
727
|
+
rightArm.rotation.z = Math.PI / 2;
|
|
728
|
+
rightArm.rotation.y = -0.3;
|
|
729
|
+
rightArm.position.set(0.8, 2.8, 2);
|
|
730
|
+
group.add(rightArm);
|
|
731
|
+
|
|
732
|
+
// Monitor (modern flat screen)
|
|
733
|
+
const monitorStandGeo = new THREE.CylinderGeometry(0.5, 0.6, 0.1, 16);
|
|
734
|
+
const monitorMat = new THREE.MeshStandardMaterial({
|
|
735
|
+
color: 0x333333,
|
|
736
|
+
roughness: 0.3,
|
|
737
|
+
metalness: 0.5
|
|
738
|
+
});
|
|
739
|
+
const monitorStand = new THREE.Mesh(monitorStandGeo, monitorMat);
|
|
740
|
+
monitorStand.position.set(0, 2.65, 0.8);
|
|
741
|
+
group.add(monitorStand);
|
|
742
|
+
|
|
743
|
+
const monitorNeckGeo = new THREE.CylinderGeometry(0.08, 0.08, 1.2, 8);
|
|
744
|
+
const monitorNeck = new THREE.Mesh(monitorNeckGeo, monitorMat);
|
|
745
|
+
monitorNeck.position.set(0, 3.2, 0.8);
|
|
746
|
+
group.add(monitorNeck);
|
|
747
|
+
|
|
748
|
+
// Screen
|
|
749
|
+
const screenFrameGeo = new THREE.BoxGeometry(3, 1.8, 0.12);
|
|
750
|
+
const screenFrame = new THREE.Mesh(screenFrameGeo, monitorMat);
|
|
751
|
+
screenFrame.position.set(0, 4.0, 0.8);
|
|
752
|
+
screenFrame.castShadow = true;
|
|
753
|
+
group.add(screenFrame);
|
|
754
|
+
|
|
755
|
+
// Screen display (glowing)
|
|
756
|
+
const screenDisplayGeo = new THREE.PlaneGeometry(2.7, 1.5);
|
|
757
|
+
const screenDisplayMat = new THREE.MeshBasicMaterial({
|
|
758
|
+
color: 0x2a6b5e,
|
|
759
|
+
});
|
|
760
|
+
const screenDisplay = new THREE.Mesh(screenDisplayGeo, screenDisplayMat);
|
|
761
|
+
screenDisplay.position.set(0, 4.0, 0.87);
|
|
762
|
+
group.add(screenDisplay);
|
|
763
|
+
|
|
764
|
+
// Screen glow light
|
|
765
|
+
const screenLight = new THREE.PointLight(0x4fd1c5, 0.3, 6);
|
|
766
|
+
screenLight.position.set(0, 4.0, 1.5);
|
|
767
|
+
group.add(screenLight);
|
|
768
|
+
|
|
769
|
+
// Keyboard
|
|
770
|
+
const kbGeo = new THREE.BoxGeometry(1.6, 0.05, 0.5);
|
|
771
|
+
const kbMat = new THREE.MeshStandardMaterial({
|
|
772
|
+
color: 0x444444,
|
|
773
|
+
roughness: 0.5,
|
|
774
|
+
metalness: 0.3
|
|
775
|
+
});
|
|
776
|
+
const keyboard = new THREE.Mesh(kbGeo, kbMat);
|
|
777
|
+
keyboard.position.set(0, 2.63, 2);
|
|
778
|
+
group.add(keyboard);
|
|
779
|
+
|
|
780
|
+
// Name label sprite
|
|
188
781
|
const canvas = document.createElement('canvas');
|
|
189
782
|
const context = canvas.getContext('2d');
|
|
190
|
-
// High DPI canvas for crisp text
|
|
191
783
|
const scale = 2;
|
|
192
784
|
canvas.width = 512;
|
|
193
|
-
canvas.height = toolName ? 160 : 128;
|
|
785
|
+
canvas.height = toolName ? 160 : 128;
|
|
194
786
|
context.scale(scale, scale);
|
|
195
787
|
|
|
196
|
-
//
|
|
197
|
-
context.fillStyle = 'rgba(
|
|
788
|
+
// Frosted glass background
|
|
789
|
+
context.fillStyle = 'rgba(20, 60, 60, 0.85)';
|
|
198
790
|
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
199
791
|
context.fill();
|
|
200
792
|
|
|
201
|
-
//
|
|
202
|
-
context.
|
|
203
|
-
context.
|
|
793
|
+
// Subtle border
|
|
794
|
+
context.strokeStyle = 'rgba(79, 209, 197, 0.4)';
|
|
795
|
+
context.lineWidth = 1;
|
|
796
|
+
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
797
|
+
context.stroke();
|
|
798
|
+
|
|
799
|
+
context.font = 'bold 22px Arial';
|
|
800
|
+
context.fillStyle = '#e0f5f0';
|
|
204
801
|
context.textAlign = 'center';
|
|
205
802
|
context.textBaseline = 'middle';
|
|
206
803
|
context.fillText(name, 128, 24);
|
|
207
804
|
|
|
208
|
-
// Tool name text (if available)
|
|
209
805
|
if (toolName) {
|
|
210
|
-
context.font = 'italic
|
|
211
|
-
context.fillStyle = '#
|
|
806
|
+
context.font = 'italic 14px Arial';
|
|
807
|
+
context.fillStyle = '#4fd1c5';
|
|
212
808
|
context.fillText(toolName, 128, 56);
|
|
213
809
|
}
|
|
214
810
|
|
|
@@ -217,30 +813,21 @@ function createAgentHouse(name, position, toolName = null) {
|
|
|
217
813
|
texture.magFilter = THREE.LinearFilter;
|
|
218
814
|
const spriteMat = new THREE.SpriteMaterial({ map: texture });
|
|
219
815
|
const sprite = new THREE.Sprite(spriteMat);
|
|
220
|
-
sprite.position.set(0,
|
|
221
|
-
sprite.scale.set(
|
|
222
|
-
sprite.name = 'label';
|
|
816
|
+
sprite.position.set(0, 6.5, 2);
|
|
817
|
+
sprite.scale.set(7, toolName ? 2.2 : 1.8, 1);
|
|
818
|
+
sprite.name = 'label';
|
|
223
819
|
group.add(sprite);
|
|
224
820
|
|
|
225
|
-
// Path to house
|
|
226
|
-
const pathGeo = new THREE.PlaneGeometry(2, 8);
|
|
227
|
-
const pathMat = new THREE.MeshStandardMaterial({ color: 0xD2B48C });
|
|
228
|
-
const path = new THREE.Mesh(pathGeo, pathMat);
|
|
229
|
-
path.rotation.x = -Math.PI / 2;
|
|
230
|
-
path.position.set(0, 0.02, 7);
|
|
231
|
-
group.add(path);
|
|
232
|
-
|
|
233
821
|
scene.add(group);
|
|
234
822
|
agentMeshes.set(name, group);
|
|
235
823
|
|
|
236
824
|
return group;
|
|
237
825
|
}
|
|
238
826
|
|
|
239
|
-
function
|
|
240
|
-
const sprite =
|
|
827
|
+
function updateDeskLabel(desk, name, toolName = null) {
|
|
828
|
+
const sprite = desk.getObjectByName('label');
|
|
241
829
|
if (!sprite) return;
|
|
242
830
|
|
|
243
|
-
// Create new canvas with updated text
|
|
244
831
|
const canvas = document.createElement('canvas');
|
|
245
832
|
const context = canvas.getContext('2d');
|
|
246
833
|
const scale = 2;
|
|
@@ -248,67 +835,73 @@ function updateHouseLabel(house, name, toolName = null) {
|
|
|
248
835
|
canvas.height = toolName ? 160 : 128;
|
|
249
836
|
context.scale(scale, scale);
|
|
250
837
|
|
|
251
|
-
|
|
252
|
-
context.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
|
838
|
+
context.fillStyle = 'rgba(20, 60, 60, 0.85)';
|
|
253
839
|
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
254
840
|
context.fill();
|
|
255
841
|
|
|
256
|
-
|
|
257
|
-
context.
|
|
258
|
-
context.
|
|
842
|
+
context.strokeStyle = 'rgba(79, 209, 197, 0.4)';
|
|
843
|
+
context.lineWidth = 1;
|
|
844
|
+
context.roundRect(0, 0, 256, toolName ? 80 : 64, 16);
|
|
845
|
+
context.stroke();
|
|
846
|
+
|
|
847
|
+
context.font = 'bold 22px Arial';
|
|
848
|
+
context.fillStyle = '#e0f5f0';
|
|
259
849
|
context.textAlign = 'center';
|
|
260
850
|
context.textBaseline = 'middle';
|
|
261
851
|
context.fillText(name, 128, 24);
|
|
262
852
|
|
|
263
|
-
// Tool name text (if available)
|
|
264
853
|
if (toolName) {
|
|
265
|
-
context.font = 'italic
|
|
266
|
-
context.fillStyle = '#
|
|
854
|
+
context.font = 'italic 14px Arial';
|
|
855
|
+
context.fillStyle = '#4fd1c5';
|
|
267
856
|
context.fillText(toolName, 128, 56);
|
|
268
857
|
}
|
|
269
858
|
|
|
270
|
-
// Update texture
|
|
271
859
|
const texture = new THREE.CanvasTexture(canvas);
|
|
272
860
|
texture.minFilter = THREE.LinearFilter;
|
|
273
861
|
texture.magFilter = THREE.LinearFilter;
|
|
274
862
|
sprite.material.map = texture;
|
|
275
863
|
sprite.material.needsUpdate = true;
|
|
276
864
|
|
|
277
|
-
|
|
278
|
-
sprite.
|
|
279
|
-
sprite.scale.set(8, toolName ? 2.5 : 2, 1);
|
|
865
|
+
sprite.position.set(0, 6.5, 2);
|
|
866
|
+
sprite.scale.set(7, toolName ? 2.2 : 1.8, 1);
|
|
280
867
|
}
|
|
281
868
|
|
|
282
869
|
function createMessageParticle(fromPos, toPos) {
|
|
283
|
-
const particleGeo = new THREE.SphereGeometry(0.
|
|
284
|
-
const particleMat = new THREE.
|
|
285
|
-
color:
|
|
286
|
-
|
|
287
|
-
|
|
870
|
+
const particleGeo = new THREE.SphereGeometry(0.35, 12, 12);
|
|
871
|
+
const particleMat = new THREE.MeshBasicMaterial({
|
|
872
|
+
color: 0xff6b6b,
|
|
873
|
+
transparent: true,
|
|
874
|
+
opacity: 0.95
|
|
288
875
|
});
|
|
289
876
|
const particle = new THREE.Mesh(particleGeo, particleMat);
|
|
290
877
|
|
|
291
878
|
particle.position.copy(fromPos);
|
|
292
|
-
particle.position.y +=
|
|
879
|
+
particle.position.y += 5;
|
|
880
|
+
|
|
881
|
+
// Add a glow point light that follows particle
|
|
882
|
+
const glow = new THREE.PointLight(0xff6b6b, 1.0, 10);
|
|
883
|
+
particle.add(glow);
|
|
293
884
|
|
|
294
885
|
scene.add(particle);
|
|
295
886
|
|
|
296
|
-
// Animate particle
|
|
297
887
|
const startTime = Date.now();
|
|
298
|
-
const duration =
|
|
888
|
+
const duration = 1500;
|
|
299
889
|
|
|
300
890
|
function animateParticle() {
|
|
301
891
|
const elapsed = Date.now() - startTime;
|
|
302
892
|
const progress = Math.min(elapsed / duration, 1);
|
|
303
893
|
|
|
304
894
|
particle.position.lerpVectors(
|
|
305
|
-
new THREE.Vector3(fromPos.x, fromPos.y +
|
|
306
|
-
new THREE.Vector3(toPos.x, toPos.y +
|
|
895
|
+
new THREE.Vector3(fromPos.x, fromPos.y + 5, fromPos.z),
|
|
896
|
+
new THREE.Vector3(toPos.x, toPos.y + 5, toPos.z),
|
|
307
897
|
progress
|
|
308
898
|
);
|
|
309
899
|
|
|
310
|
-
|
|
311
|
-
|
|
900
|
+
particle.position.y += Math.sin(progress * Math.PI) * 2;
|
|
901
|
+
|
|
902
|
+
// Pulse size
|
|
903
|
+
const pulse = Math.sin(progress * Math.PI) * 0.2 + 1;
|
|
904
|
+
particle.scale.setScalar(pulse);
|
|
312
905
|
|
|
313
906
|
if (progress < 1) {
|
|
314
907
|
requestAnimationFrame(animateParticle);
|
|
@@ -324,48 +917,58 @@ function createConnectionLine(fromPos, toPos) {
|
|
|
324
917
|
const startPos = new THREE.Vector3(fromPos.x, fromPos.y + 5, fromPos.z);
|
|
325
918
|
const endPos = new THREE.Vector3(toPos.x, toPos.y + 5, toPos.z);
|
|
326
919
|
|
|
920
|
+
// Create curved line with points
|
|
921
|
+
const mid = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5);
|
|
922
|
+
mid.y += 2;
|
|
923
|
+
|
|
924
|
+
const curve = new THREE.QuadraticBezierCurve3(startPos, mid, endPos);
|
|
925
|
+
const points = curve.getPoints(50);
|
|
926
|
+
|
|
927
|
+
// Main line - thicker, glowing red
|
|
327
928
|
const material = new THREE.LineBasicMaterial({
|
|
328
929
|
color: 0xff6b6b,
|
|
329
|
-
opacity: 0.
|
|
930
|
+
opacity: 0.7,
|
|
330
931
|
transparent: true,
|
|
331
932
|
linewidth: 3
|
|
332
933
|
});
|
|
333
934
|
|
|
334
|
-
const points = [startPos, endPos];
|
|
335
|
-
|
|
336
935
|
const geometry = new THREE.BufferGeometry().setFromPoints(points);
|
|
337
936
|
const line = new THREE.Line(geometry, material);
|
|
338
937
|
|
|
339
938
|
scene.add(line);
|
|
340
939
|
connectionLines.push(line);
|
|
341
940
|
|
|
342
|
-
// Add
|
|
941
|
+
// Add small chevron markers along the path to show direction
|
|
343
942
|
const direction = new THREE.Vector3().subVectors(endPos, startPos).normalize();
|
|
344
|
-
const arrowPos = endPos.clone().sub(direction.clone().multiplyScalar(5));
|
|
345
|
-
|
|
346
|
-
const arrowGeometry = new THREE.ConeGeometry(0.5, 1.5, 8);
|
|
347
|
-
const arrowMaterial = new THREE.MeshStandardMaterial({
|
|
348
|
-
color: 0xff6b6b,
|
|
349
|
-
emissive: 0xff6b6b,
|
|
350
|
-
emissiveIntensity: 0.3
|
|
351
|
-
});
|
|
352
|
-
const arrowhead = new THREE.Mesh(arrowGeometry, arrowMaterial);
|
|
353
|
-
|
|
354
|
-
arrowhead.position.copy(arrowPos);
|
|
355
|
-
|
|
356
|
-
// Orient arrow to point along the line
|
|
357
943
|
const up = new THREE.Vector3(0, 1, 0);
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
944
|
+
const numMarkers = 4;
|
|
945
|
+
for (let i = 0; i < numMarkers; i++) {
|
|
946
|
+
const t = (i + 1) / (numMarkers + 1);
|
|
947
|
+
const markerPos = curve.getPoint(t);
|
|
948
|
+
|
|
949
|
+
const markerGeo = new THREE.ConeGeometry(0.25, 0.7, 8);
|
|
950
|
+
const markerMat = new THREE.MeshBasicMaterial({
|
|
951
|
+
color: 0xff6b6b,
|
|
952
|
+
transparent: true,
|
|
953
|
+
opacity: 0.5
|
|
954
|
+
});
|
|
955
|
+
const marker = new THREE.Mesh(markerGeo, markerMat);
|
|
956
|
+
|
|
957
|
+
// Get tangent at this point for direction
|
|
958
|
+
const tangent = curve.getTangent(t);
|
|
959
|
+
marker.position.copy(markerPos);
|
|
960
|
+
|
|
961
|
+
const markerQuat = new THREE.Quaternion();
|
|
962
|
+
markerQuat.setFromUnitVectors(up, tangent);
|
|
963
|
+
marker.setRotationFromQuaternion(markerQuat);
|
|
964
|
+
|
|
965
|
+
scene.add(marker);
|
|
966
|
+
connectionLines.push(marker);
|
|
967
|
+
}
|
|
364
968
|
|
|
365
|
-
// Send particle
|
|
366
969
|
setTimeout(() => {
|
|
367
970
|
createMessageParticle(fromPos, toPos);
|
|
368
|
-
},
|
|
971
|
+
}, 50);
|
|
369
972
|
}
|
|
370
973
|
|
|
371
974
|
function clearConnections() {
|
|
@@ -377,49 +980,47 @@ function updateVillage() {
|
|
|
377
980
|
clearConnections();
|
|
378
981
|
|
|
379
982
|
// Use recipients (from coworkers.db) as the authoritative list of agents
|
|
380
|
-
// This ensures we only show houses for registered coworkers
|
|
381
983
|
const allAgents = new Set([config.user.toLowerCase(), ...recipients.map(r => r.toLowerCase())]);
|
|
382
984
|
|
|
383
|
-
// Also add message participants
|
|
985
|
+
// Also add message participants
|
|
384
986
|
messages.forEach(m => {
|
|
385
987
|
allAgents.add(m.sender.toLowerCase());
|
|
386
988
|
allAgents.add(m.recipient.toLowerCase());
|
|
387
989
|
});
|
|
388
990
|
|
|
389
|
-
// Arrange agents in a circle
|
|
991
|
+
// Arrange agents in a circle on the platform
|
|
390
992
|
const agents = Array.from(allAgents);
|
|
391
|
-
const radius =
|
|
993
|
+
const radius = Math.min(20, Math.max(10, agents.length * 3));
|
|
392
994
|
|
|
393
995
|
agents.forEach((agent, index) => {
|
|
394
|
-
const angle = (index / agents.length) * Math.PI * 2;
|
|
996
|
+
const angle = (index / agents.length) * Math.PI * 2 - Math.PI / 2;
|
|
395
997
|
const x = Math.cos(angle) * radius;
|
|
396
998
|
const z = Math.sin(angle) * radius;
|
|
397
999
|
const position = new THREE.Vector3(x, 0, z);
|
|
398
1000
|
|
|
399
|
-
// Get tool name for this agent from avatar states
|
|
400
1001
|
const avatarState = avatarStates[agent.toLowerCase()];
|
|
401
1002
|
const toolName = avatarState?.tool_name || null;
|
|
402
1003
|
|
|
403
1004
|
if (!agentMeshes.has(agent)) {
|
|
404
|
-
|
|
1005
|
+
const group = createAgentDesk(agent, position, toolName);
|
|
1006
|
+
// Face desk toward center
|
|
1007
|
+
group.lookAt(new THREE.Vector3(0, PLATFORM_HEIGHT, 0));
|
|
405
1008
|
} else {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// Update label with current tool name
|
|
411
|
-
updateHouseLabel(house, agent, toolName);
|
|
1009
|
+
const desk = agentMeshes.get(agent);
|
|
1010
|
+
desk.position.set(x, PLATFORM_HEIGHT, z);
|
|
1011
|
+
desk.lookAt(new THREE.Vector3(0, PLATFORM_HEIGHT, 0));
|
|
1012
|
+
updateDeskLabel(desk, agent, toolName);
|
|
412
1013
|
}
|
|
413
1014
|
});
|
|
414
1015
|
|
|
415
|
-
// Remove
|
|
416
|
-
agentMeshes.forEach((
|
|
1016
|
+
// Remove desks for coworkers that no longer exist
|
|
1017
|
+
agentMeshes.forEach((desk, name) => {
|
|
417
1018
|
if (!allAgents.has(name)) {
|
|
418
1019
|
// Remove from scene
|
|
419
|
-
scene.remove(
|
|
1020
|
+
scene.remove(desk);
|
|
420
1021
|
|
|
421
1022
|
// Dispose of geometries and materials to prevent memory leaks
|
|
422
|
-
|
|
1023
|
+
desk.traverse((child) => {
|
|
423
1024
|
if (child.isMesh) {
|
|
424
1025
|
child.geometry.dispose();
|
|
425
1026
|
if (child.material) {
|
|
@@ -439,25 +1040,86 @@ function updateVillage() {
|
|
|
439
1040
|
|
|
440
1041
|
// Create connections for unread messages only
|
|
441
1042
|
allMessages.forEach(msg => {
|
|
442
|
-
const
|
|
443
|
-
const
|
|
1043
|
+
const fromDesk = agentMeshes.get(msg.sender.toLowerCase());
|
|
1044
|
+
const toDesk = agentMeshes.get(msg.recipient.toLowerCase());
|
|
444
1045
|
|
|
445
|
-
if (
|
|
1046
|
+
if (fromDesk && toDesk && !msg.read) {
|
|
446
1047
|
createConnectionLine(
|
|
447
|
-
|
|
448
|
-
|
|
1048
|
+
fromDesk.position,
|
|
1049
|
+
toDesk.position
|
|
449
1050
|
);
|
|
450
1051
|
}
|
|
451
1052
|
});
|
|
452
1053
|
|
|
453
|
-
// Update
|
|
454
|
-
|
|
1054
|
+
// Update desk labels with unread indicators
|
|
1055
|
+
updateDeskLabels();
|
|
455
1056
|
}
|
|
456
1057
|
|
|
457
1058
|
function animate() {
|
|
458
1059
|
requestAnimationFrame(animate);
|
|
1060
|
+
|
|
1061
|
+
const time = Date.now() * 0.001;
|
|
1062
|
+
|
|
1063
|
+
// Rotate holographic sphere
|
|
1064
|
+
if (holoSphere) {
|
|
1065
|
+
holoSphere.rotation.y = time * 0.15;
|
|
1066
|
+
holoSphere.rotation.x = Math.sin(time * 0.1) * 0.1;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Animate floating particles
|
|
1070
|
+
floatingParticles.forEach((particles, index) => {
|
|
1071
|
+
const positions = particles.geometry.attributes.position.array;
|
|
1072
|
+
|
|
1073
|
+
if (particles.userData.twinklePhase !== undefined) {
|
|
1074
|
+
// Star twinkling effect
|
|
1075
|
+
const twinkle = Math.sin(time * 2 + particles.userData.twinklePhase) * 0.3 + 0.7;
|
|
1076
|
+
particles.material.opacity = 0.4 + twinkle * 0.4;
|
|
1077
|
+
|
|
1078
|
+
// Slowly rotate stars
|
|
1079
|
+
particles.rotation.y = time * 0.02;
|
|
1080
|
+
} else {
|
|
1081
|
+
// Regular floating particles
|
|
1082
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
1083
|
+
positions[i + 1] += Math.sin(time + positions[i] * 0.1) * 0.003;
|
|
1084
|
+
}
|
|
1085
|
+
particles.geometry.attributes.position.needsUpdate = true;
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
// Subtle glow pulse on lamps
|
|
1090
|
+
glowLights.forEach((item, i) => {
|
|
1091
|
+
const pulse = Math.sin(time * 1.5 + i) * 0.15 + 1;
|
|
1092
|
+
item.light.intensity = item.baseIntensity * pulse;
|
|
1093
|
+
});
|
|
1094
|
+
|
|
1095
|
+
// Animate water ripples
|
|
1096
|
+
if (waterMesh && waterMesh.userData.initialPositions) {
|
|
1097
|
+
const positions = waterMesh.geometry.attributes.position;
|
|
1098
|
+
const initialPositions = waterMesh.userData.initialPositions;
|
|
1099
|
+
|
|
1100
|
+
for (let i = 0; i < positions.count; i++) {
|
|
1101
|
+
const x = initialPositions[i * 3];
|
|
1102
|
+
const y = initialPositions[i * 3 + 1];
|
|
1103
|
+
|
|
1104
|
+
// Create gentle ripple effect
|
|
1105
|
+
const distance = Math.sqrt(x * x + y * y);
|
|
1106
|
+
const wave1 = Math.sin(distance * 0.3 - time * 0.8) * 0.15;
|
|
1107
|
+
const wave2 = Math.sin(x * 0.2 + time * 0.5) * 0.1;
|
|
1108
|
+
const wave3 = Math.cos(y * 0.15 + time * 0.3) * 0.08;
|
|
1109
|
+
|
|
1110
|
+
positions.setZ(i, wave1 + wave2 + wave3);
|
|
1111
|
+
}
|
|
1112
|
+
positions.needsUpdate = true;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
459
1115
|
controls.update();
|
|
460
|
-
|
|
1116
|
+
|
|
1117
|
+
// Use composer for bloom effect if available, otherwise standard renderer
|
|
1118
|
+
if (composer) {
|
|
1119
|
+
composer.render();
|
|
1120
|
+
} else {
|
|
1121
|
+
renderer.render(scene, camera);
|
|
1122
|
+
}
|
|
461
1123
|
}
|
|
462
1124
|
|
|
463
1125
|
function onWindowResize() {
|
|
@@ -467,14 +1129,17 @@ function onWindowResize() {
|
|
|
467
1129
|
camera.updateProjectionMatrix();
|
|
468
1130
|
renderer.setSize(width, height);
|
|
469
1131
|
|
|
470
|
-
//
|
|
1132
|
+
// Resize composer for bloom effect
|
|
1133
|
+
if (composer) {
|
|
1134
|
+
composer.setSize(width, height);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
471
1137
|
if (width < 768) {
|
|
472
|
-
|
|
473
|
-
camera.position.
|
|
474
|
-
|
|
475
|
-
controls.minDistance = 30; // Prevent zooming too close on mobile
|
|
1138
|
+
camera.position.y = Math.max(camera.position.y, 40);
|
|
1139
|
+
camera.position.z = Math.max(camera.position.z, 50);
|
|
1140
|
+
controls.minDistance = 35;
|
|
476
1141
|
} else {
|
|
477
|
-
controls.minDistance =
|
|
1142
|
+
controls.minDistance = 25;
|
|
478
1143
|
}
|
|
479
1144
|
}
|
|
480
1145
|
|
|
@@ -609,9 +1274,9 @@ function updateUI() {
|
|
|
609
1274
|
});
|
|
610
1275
|
}
|
|
611
1276
|
|
|
612
|
-
// Update
|
|
1277
|
+
// Update desk dialog if it's open
|
|
613
1278
|
if (document.getElementById('house-dialog').classList.contains('active')) {
|
|
614
|
-
|
|
1279
|
+
updateDeskDialogContent();
|
|
615
1280
|
}
|
|
616
1281
|
}
|
|
617
1282
|
|
|
@@ -672,23 +1337,23 @@ async function sendMessage() {
|
|
|
672
1337
|
}
|
|
673
1338
|
}
|
|
674
1339
|
|
|
675
|
-
//
|
|
676
|
-
function
|
|
677
|
-
|
|
1340
|
+
// Desk click handler
|
|
1341
|
+
function onDeskClick(event) {
|
|
1342
|
+
handleDeskInteraction(event.clientX, event.clientY);
|
|
678
1343
|
}
|
|
679
1344
|
|
|
680
1345
|
// Touch handlers for mobile
|
|
681
1346
|
let touchStartX = 0;
|
|
682
1347
|
let touchStartY = 0;
|
|
683
1348
|
|
|
684
|
-
function
|
|
1349
|
+
function onDeskTouchStart(event) {
|
|
685
1350
|
if (event.touches.length === 1) {
|
|
686
1351
|
touchStartX = event.touches[0].clientX;
|
|
687
1352
|
touchStartY = event.touches[0].clientY;
|
|
688
1353
|
}
|
|
689
1354
|
}
|
|
690
1355
|
|
|
691
|
-
function
|
|
1356
|
+
function onDeskTouchEnd(event) {
|
|
692
1357
|
if (event.changedTouches.length === 1) {
|
|
693
1358
|
const touchEndX = event.changedTouches[0].clientX;
|
|
694
1359
|
const touchEndY = event.changedTouches[0].clientY;
|
|
@@ -701,13 +1366,13 @@ function onHouseTouchEnd(event) {
|
|
|
701
1366
|
|
|
702
1367
|
// Only trigger if touch didn't move much (tap vs swipe)
|
|
703
1368
|
if (moveDistance < 20) {
|
|
704
|
-
|
|
1369
|
+
handleDeskInteraction(touchEndX, touchEndY);
|
|
705
1370
|
}
|
|
706
1371
|
}
|
|
707
1372
|
}
|
|
708
1373
|
|
|
709
|
-
// Common
|
|
710
|
-
function
|
|
1374
|
+
// Common desk interaction handler
|
|
1375
|
+
function handleDeskInteraction(clientX, clientY) {
|
|
711
1376
|
// Calculate normalized device coordinates
|
|
712
1377
|
const rect = renderer.domElement.getBoundingClientRect();
|
|
713
1378
|
const x = ((clientX - rect.left) / rect.width) * 2 - 1;
|
|
@@ -718,34 +1383,34 @@ function handleHouseInteraction(clientX, clientY) {
|
|
|
718
1383
|
|
|
719
1384
|
raycaster.setFromCamera(mouse, camera);
|
|
720
1385
|
|
|
721
|
-
// Get all
|
|
722
|
-
const
|
|
1386
|
+
// Get all desk meshes
|
|
1387
|
+
const deskMeshes = [];
|
|
723
1388
|
agentMeshes.forEach((group, name) => {
|
|
724
1389
|
group.children.forEach(child => {
|
|
725
1390
|
if (child.isMesh && !child.userData.isBubble && !child.userData.isCup) {
|
|
726
1391
|
child.userData.agentName = name;
|
|
727
|
-
|
|
1392
|
+
deskMeshes.push(child);
|
|
728
1393
|
}
|
|
729
1394
|
});
|
|
730
1395
|
});
|
|
731
1396
|
|
|
732
|
-
const intersects = raycaster.intersectObjects(
|
|
1397
|
+
const intersects = raycaster.intersectObjects(deskMeshes);
|
|
733
1398
|
|
|
734
1399
|
if (intersects.length > 0) {
|
|
735
1400
|
const agentName = intersects[0].object.userData.agentName;
|
|
736
1401
|
if (agentName) {
|
|
737
|
-
|
|
1402
|
+
showDeskDialog(agentName);
|
|
738
1403
|
}
|
|
739
1404
|
}
|
|
740
1405
|
}
|
|
741
1406
|
|
|
742
|
-
// Global variable to track current agent for
|
|
743
|
-
let
|
|
1407
|
+
// Global variable to track current agent for desk dialog
|
|
1408
|
+
let currentDeskAgent = null;
|
|
744
1409
|
let currentTab = 'received';
|
|
745
1410
|
|
|
746
1411
|
// Show dialog with messages for a specific agent
|
|
747
|
-
async function
|
|
748
|
-
|
|
1412
|
+
async function showDeskDialog(agentName) {
|
|
1413
|
+
currentDeskAgent = agentName.toLowerCase();
|
|
749
1414
|
currentTab = 'received'; // Default to received tab
|
|
750
1415
|
|
|
751
1416
|
const dialog = document.getElementById('house-dialog');
|
|
@@ -785,35 +1450,35 @@ window.switchTab = function(tab) {
|
|
|
785
1450
|
document.getElementById('tab-sent').classList.toggle('active', tab === 'sent');
|
|
786
1451
|
|
|
787
1452
|
// Update content
|
|
788
|
-
|
|
1453
|
+
updateDeskDialogContent();
|
|
789
1454
|
};
|
|
790
1455
|
|
|
791
|
-
function
|
|
1456
|
+
function updateDeskDialogContent() {
|
|
792
1457
|
const content = document.getElementById('house-dialog-content');
|
|
793
1458
|
|
|
794
|
-
if (!
|
|
1459
|
+
if (!currentDeskAgent) return;
|
|
795
1460
|
|
|
796
1461
|
// Filter messages based on current tab - FROM THE AGENT'S PERSPECTIVE
|
|
797
1462
|
let filteredMessages;
|
|
798
1463
|
if (currentTab === 'received') {
|
|
799
1464
|
// Messages RECEIVED BY the agent (sent TO the agent)
|
|
800
1465
|
filteredMessages = allMessages.filter(m =>
|
|
801
|
-
m.recipient.toLowerCase() ===
|
|
1466
|
+
m.recipient.toLowerCase() === currentDeskAgent
|
|
802
1467
|
);
|
|
803
1468
|
} else {
|
|
804
1469
|
// Messages SENT BY the agent
|
|
805
1470
|
filteredMessages = allMessages.filter(m =>
|
|
806
|
-
m.sender.toLowerCase() ===
|
|
1471
|
+
m.sender.toLowerCase() === currentDeskAgent
|
|
807
1472
|
);
|
|
808
1473
|
}
|
|
809
1474
|
|
|
810
1475
|
// Update count badges
|
|
811
1476
|
const receivedCount = allMessages.filter(m =>
|
|
812
|
-
m.recipient.toLowerCase() ===
|
|
1477
|
+
m.recipient.toLowerCase() === currentDeskAgent
|
|
813
1478
|
).length;
|
|
814
1479
|
|
|
815
1480
|
const sentCount = allMessages.filter(m =>
|
|
816
|
-
m.sender.toLowerCase() ===
|
|
1481
|
+
m.sender.toLowerCase() === currentDeskAgent
|
|
817
1482
|
).length;
|
|
818
1483
|
|
|
819
1484
|
const receivedBadge = document.getElementById('received-count');
|
|
@@ -843,35 +1508,30 @@ function updateHouseDialogContent() {
|
|
|
843
1508
|
}
|
|
844
1509
|
}
|
|
845
1510
|
|
|
846
|
-
window.
|
|
1511
|
+
window.closeDeskDialog = function() {
|
|
847
1512
|
document.getElementById('house-dialog').classList.remove('active');
|
|
848
1513
|
};
|
|
849
1514
|
|
|
850
|
-
// Update
|
|
851
|
-
function
|
|
1515
|
+
// Update desk labels to show unread indicators and tool names
|
|
1516
|
+
function updateDeskLabels() {
|
|
852
1517
|
agentMeshes.forEach((group, name) => {
|
|
853
|
-
// Check for unread messages SENT TO this agent (messages they haven't read)
|
|
854
1518
|
const unreadCount = allMessages.filter(m =>
|
|
855
1519
|
m.recipient.toLowerCase() === name.toLowerCase() &&
|
|
856
1520
|
m.sender.toLowerCase() === config.user.toLowerCase() &&
|
|
857
1521
|
!m.read
|
|
858
1522
|
).length;
|
|
859
1523
|
|
|
860
|
-
// Also check if this agent has sent unread messages TO user
|
|
861
1524
|
const unreadFromAgent = allMessages.filter(m =>
|
|
862
1525
|
m.sender.toLowerCase() === name.toLowerCase() &&
|
|
863
1526
|
m.recipient.toLowerCase() === config.user.toLowerCase() &&
|
|
864
1527
|
!m.read
|
|
865
1528
|
).length;
|
|
866
1529
|
|
|
867
|
-
// Get tool name for this agent
|
|
868
1530
|
const avatarState = avatarStates[name.toLowerCase()];
|
|
869
1531
|
const toolName = avatarState?.tool_name || null;
|
|
870
1532
|
|
|
871
|
-
// Find the sprite label
|
|
872
1533
|
const sprite = group.children.find(c => c.isSprite);
|
|
873
1534
|
if (sprite) {
|
|
874
|
-
// Update the canvas texture - high DPI for crisp text
|
|
875
1535
|
const canvas = document.createElement('canvas');
|
|
876
1536
|
const context = canvas.getContext('2d');
|
|
877
1537
|
const scale = 2;
|
|
@@ -879,40 +1539,43 @@ function updateHouseLabels() {
|
|
|
879
1539
|
canvas.height = toolName ? 160 : 128;
|
|
880
1540
|
context.scale(scale, scale);
|
|
881
1541
|
|
|
882
|
-
// Background -
|
|
1542
|
+
// Background - themed colors for state
|
|
883
1543
|
if (unreadFromAgent > 0) {
|
|
884
|
-
|
|
885
|
-
context.fillStyle = 'rgba(220, 53, 69, 0.9)';
|
|
1544
|
+
context.fillStyle = 'rgba(220, 80, 80, 0.85)';
|
|
886
1545
|
} else if (unreadCount > 0) {
|
|
887
|
-
|
|
888
|
-
context.fillStyle = 'rgba(0, 123, 255, 0.9)';
|
|
1546
|
+
context.fillStyle = 'rgba(59, 130, 180, 0.85)';
|
|
889
1547
|
} else {
|
|
890
|
-
|
|
891
|
-
context.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
|
1548
|
+
context.fillStyle = 'rgba(20, 60, 60, 0.85)';
|
|
892
1549
|
}
|
|
893
1550
|
context.roundRect(0, 0, 350, toolName ? 80 : 64, 16);
|
|
894
1551
|
context.fill();
|
|
895
1552
|
|
|
896
|
-
//
|
|
897
|
-
context.
|
|
898
|
-
|
|
1553
|
+
// Border
|
|
1554
|
+
context.strokeStyle = unreadFromAgent > 0
|
|
1555
|
+
? 'rgba(255, 120, 120, 0.6)'
|
|
1556
|
+
: unreadCount > 0
|
|
1557
|
+
? 'rgba(100, 180, 255, 0.6)'
|
|
1558
|
+
: 'rgba(79, 209, 197, 0.3)';
|
|
1559
|
+
context.lineWidth = 1;
|
|
1560
|
+
context.roundRect(0, 0, 350, toolName ? 80 : 64, 16);
|
|
1561
|
+
context.stroke();
|
|
1562
|
+
|
|
1563
|
+
context.font = 'bold 22px Arial';
|
|
1564
|
+
context.fillStyle = '#e0f5f0';
|
|
899
1565
|
context.textAlign = 'center';
|
|
900
1566
|
context.textBaseline = 'middle';
|
|
901
1567
|
|
|
902
1568
|
if (unreadFromAgent > 0) {
|
|
903
|
-
|
|
904
|
-
context.fillText(`${name} 🔴 ${unreadFromAgent}`, 175, 24);
|
|
1569
|
+
context.fillText(`${name} ${unreadFromAgent}`, 175, 24);
|
|
905
1570
|
} else if (unreadCount > 0) {
|
|
906
|
-
|
|
907
|
-
context.fillText(`${name} 📤 ${unreadCount}`, 175, 24);
|
|
1571
|
+
context.fillText(`${name} ${unreadCount}`, 175, 24);
|
|
908
1572
|
} else {
|
|
909
1573
|
context.fillText(name, 175, 24);
|
|
910
1574
|
}
|
|
911
1575
|
|
|
912
|
-
// Tool name text (if available)
|
|
913
1576
|
if (toolName) {
|
|
914
|
-
context.font = 'italic
|
|
915
|
-
context.fillStyle = '#
|
|
1577
|
+
context.font = 'italic 14px Arial';
|
|
1578
|
+
context.fillStyle = '#4fd1c5';
|
|
916
1579
|
context.fillText(toolName, 175, 56);
|
|
917
1580
|
}
|
|
918
1581
|
|
|
@@ -922,9 +1585,8 @@ function updateHouseLabels() {
|
|
|
922
1585
|
sprite.material.map = texture;
|
|
923
1586
|
sprite.material.needsUpdate = true;
|
|
924
1587
|
|
|
925
|
-
|
|
926
|
-
sprite.
|
|
927
|
-
sprite.scale.set(8, toolName ? 2.5 : 2, 1);
|
|
1588
|
+
sprite.position.set(0, 6.5, 2);
|
|
1589
|
+
sprite.scale.set(7, toolName ? 2.2 : 1.8, 1);
|
|
928
1590
|
}
|
|
929
1591
|
});
|
|
930
1592
|
}
|