opencroc 1.8.2 → 1.8.4

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.
Files changed (45) hide show
  1. package/README.md +383 -417
  2. package/package.json +1 -1
  3. package/dist/web/index.html +0 -12
  4. package/dist/web/public/botreview/char_0.png +0 -0
  5. package/dist/web/public/botreview/char_1.png +0 -0
  6. package/dist/web/public/botreview/char_2.png +0 -0
  7. package/dist/web/public/botreview/coffee-machine.gif +0 -0
  8. package/dist/web/public/botreview/server.gif +0 -0
  9. package/dist/web/public/botreview/walls.png +0 -0
  10. package/dist/web/public/star/desk-v3.webp +0 -0
  11. package/dist/web/public/star/office_bg_small.webp +0 -0
  12. package/dist/web/public/star/star-idle-v5.png +0 -0
  13. package/dist/web/public/star/star-working-spritesheet-grid.webp +0 -0
  14. package/dist/web/src/app/AppLayout.tsx +0 -34
  15. package/dist/web/src/app/AppRouter.tsx +0 -46
  16. package/dist/web/src/app/bootstrap.tsx +0 -22
  17. package/dist/web/src/app/routes.tsx +0 -52
  18. package/dist/web/src/features/office/runtime/index.ts +0 -1
  19. package/dist/web/src/features/office/runtime/mount.ts +0 -809
  20. package/dist/web/src/features/pixel/runtime/index.ts +0 -1
  21. package/dist/web/src/features/pixel/runtime/mount.ts +0 -728
  22. package/dist/web/src/features/studio/runtime/index.ts +0 -1
  23. package/dist/web/src/features/studio/runtime/mount.ts +0 -664
  24. package/dist/web/src/features/three/engine/index.ts +0 -1
  25. package/dist/web/src/main.tsx +0 -7
  26. package/dist/web/src/pages/office/index.ts +0 -1
  27. package/dist/web/src/pages/office/page.tsx +0 -283
  28. package/dist/web/src/pages/pixel/index.ts +0 -1
  29. package/dist/web/src/pages/pixel/page.tsx +0 -564
  30. package/dist/web/src/pages/studio/index.ts +0 -1
  31. package/dist/web/src/pages/studio/page.tsx +0 -446
  32. package/dist/web/src/runtime/agents.ts +0 -738
  33. package/dist/web/src/runtime/camera.ts +0 -132
  34. package/dist/web/src/runtime/dataviz.ts +0 -312
  35. package/dist/web/src/runtime/effects.ts +0 -482
  36. package/dist/web/src/runtime/engine.ts +0 -528
  37. package/dist/web/src/runtime/office.ts +0 -932
  38. package/dist/web/src/runtime/state.ts +0 -37
  39. package/dist/web/src/runtime/ui.ts +0 -388
  40. package/dist/web/src/shared/assets.ts +0 -4
  41. package/dist/web/src/shared/navigation.ts +0 -47
  42. package/dist/web/src/styles/app-layout.css +0 -19
  43. package/dist/web/src/styles/office.css +0 -268
  44. package/dist/web/tsconfig.json +0 -28
  45. package/dist/web/vite.config.ts +0 -93
@@ -1,932 +0,0 @@
1
- /* ═══════════════════════════════════════════════════════════════════════════════
2
- OpenCroc Studio 3D — Office Environment
3
- Low-poly 3D office built from primitives
4
- ~3000 lines
5
- ═══════════════════════════════════════════════════════════════════════════════ */
6
-
7
- import * as THREE from 'three';
8
- import { getScene } from './engine';
9
-
10
- let officeGroup = null;
11
- let currentTheme = 'dark';
12
-
13
- /* ─── Material Cache ───────────────────────────────────────────────────────── */
14
- const MAT = {};
15
-
16
- function disposeMaterialCache() {
17
- Object.keys(MAT).forEach((key) => {
18
- MAT[key]?.dispose?.();
19
- delete MAT[key];
20
- });
21
- }
22
-
23
- function disposeOfficeGroup(scene) {
24
- if (!officeGroup) return;
25
- scene?.remove(officeGroup);
26
- officeGroup.traverse((child) => {
27
- child.geometry?.dispose?.();
28
- });
29
- officeGroup = null;
30
- DESK_POSITIONS.length = 0;
31
- POND_POSITIONS.length = 0;
32
- DESK_INDICATORS.clear();
33
- disposeMaterialCache();
34
- }
35
-
36
- function initMaterials(theme) {
37
- const dk = theme === 'dark';
38
- MAT.floor = new THREE.MeshStandardMaterial({ color: dk ? 0x1a2332 : 0xe2e8f0, roughness: 0.8, metalness: 0.05 });
39
- MAT.wall = new THREE.MeshStandardMaterial({ color: dk ? 0x1e293b : 0xf8fafc, roughness: 0.7, metalness: 0.0, transparent: true, opacity: 0.85 });
40
- MAT.wallGlass = new THREE.MeshPhysicalMaterial({ color: dk ? 0x1e3a5f : 0xbfdbfe, roughness: 0.1, metalness: 0.1, transparent: true, opacity: 0.25, transmission: 0.6, thickness: 0.5, ior: 1.5 });
41
- MAT.desk = new THREE.MeshStandardMaterial({ color: dk ? 0x2d3748 : 0xcbd5e1, roughness: 0.5, metalness: 0.2 });
42
- MAT.deskTop = new THREE.MeshStandardMaterial({ color: dk ? 0x374151 : 0xdde4ed, roughness: 0.4, metalness: 0.15 });
43
- MAT.chair = new THREE.MeshStandardMaterial({ color: dk ? 0x4a5568 : 0x94a3b8, roughness: 0.6, metalness: 0.1 });
44
- MAT.screen = new THREE.MeshStandardMaterial({ color: dk ? 0x000000 : 0x111111, roughness: 0.1, metalness: 0.8 });
45
- MAT.screenGlow= new THREE.MeshBasicMaterial({ color: dk ? 0x34d399 : 0x059669, transparent: true, opacity: 0.6 });
46
- MAT.metal = new THREE.MeshStandardMaterial({ color: dk ? 0x64748b : 0x94a3b8, roughness: 0.3, metalness: 0.7 });
47
- MAT.accent = new THREE.MeshStandardMaterial({ color: dk ? 0x34d399 : 0x059669, roughness: 0.4, metalness: 0.3, emissive: dk ? 0x34d399 : 0x059669, emissiveIntensity: dk ? 0.3 : 0.1 });
48
- MAT.neon = new THREE.MeshBasicMaterial({ color: dk ? 0x34d399 : 0x059669, transparent: true, opacity: dk ? 0.8 : 0.4 });
49
- MAT.neonBlue = new THREE.MeshBasicMaterial({ color: dk ? 0x60a5fa : 0x2563eb, transparent: true, opacity: dk ? 0.6 : 0.3 });
50
- MAT.neonPurple= new THREE.MeshBasicMaterial({ color: dk ? 0xa78bfa : 0x7c3aed, transparent: true, opacity: dk ? 0.5 : 0.25 });
51
- MAT.server = new THREE.MeshStandardMaterial({ color: dk ? 0x1a2332 : 0xcbd5e1, roughness: 0.3, metalness: 0.6 });
52
- MAT.coffee = new THREE.MeshStandardMaterial({ color: dk ? 0x78350f : 0xa16207, roughness: 0.5, metalness: 0.1 });
53
- MAT.plant = new THREE.MeshStandardMaterial({ color: dk ? 0x166534 : 0x22c55e, roughness: 0.8, metalness: 0.0 });
54
- MAT.pot = new THREE.MeshStandardMaterial({ color: dk ? 0x92400e : 0xfbbf24, roughness: 0.6, metalness: 0.0 });
55
- MAT.whiteboard= new THREE.MeshStandardMaterial({ color: dk ? 0xf8fafc : 0xffffff, roughness: 0.2, metalness: 0.05 });
56
- MAT.frame = new THREE.MeshStandardMaterial({ color: dk ? 0x374151 : 0x64748b, roughness: 0.4, metalness: 0.5 });
57
- MAT.carpet = new THREE.MeshStandardMaterial({ color: dk ? 0x1e1b4b : 0xc7d2fe, roughness: 0.95, metalness: 0.0 });
58
- MAT.pillar = new THREE.MeshStandardMaterial({ color: dk ? 0x334155 : 0xe2e8f0, roughness: 0.5, metalness: 0.3 });
59
- }
60
-
61
- /* ═══════════════════════════════════════════════════════════════════════════════
62
- createOffice — Build the full 3D office environment
63
- ═══════════════════════════════════════════════════════════════════════════════ */
64
- export async function createOffice(theme) {
65
- currentTheme = theme;
66
- disposeOfficeGroup(getScene());
67
- initMaterials(theme);
68
-
69
- const scene = getScene();
70
- officeGroup = new THREE.Group();
71
- officeGroup.name = 'office';
72
-
73
- buildFloor();
74
- buildWalls();
75
- buildGlassPartitions();
76
- buildPondZone();
77
- buildDesks(6);
78
- buildServerRack();
79
- buildCoffeeMachine();
80
- buildPlants();
81
- buildWhiteboard();
82
- buildPillars();
83
- buildCarpet();
84
- buildCeilingLights();
85
- buildNeonStrips();
86
- buildCenterPlatform();
87
- buildBookshelf();
88
- buildWaterCooler();
89
- buildDecorativeElements();
90
-
91
- scene.add(officeGroup);
92
- }
93
-
94
- /* ═══════════════════════════════════════════════════════════════════════════════
95
- Office Floor
96
- ═══════════════════════════════════════════════════════════════════════════════ */
97
- function buildFloor() {
98
- const geo = new THREE.BoxGeometry(28, 0.2, 20);
99
- const floor = new THREE.Mesh(geo, MAT.floor);
100
- floor.position.set(0, 0.1, 0);
101
- floor.receiveShadow = true;
102
- floor.name = 'office-floor';
103
- officeGroup.add(floor);
104
-
105
- // Floor edge trim (accent line)
106
- const edgeGeo = new THREE.BoxGeometry(28.2, 0.05, 0.05);
107
- const edgeFront = new THREE.Mesh(edgeGeo, MAT.neon);
108
- edgeFront.position.set(0, 0.22, 10);
109
- officeGroup.add(edgeFront);
110
- const edgeBack = edgeFront.clone();
111
- edgeBack.position.z = -10;
112
- officeGroup.add(edgeBack);
113
-
114
- const edgeSideGeo = new THREE.BoxGeometry(0.05, 0.05, 20.2);
115
- const edgeLeft = new THREE.Mesh(edgeSideGeo, MAT.neon);
116
- edgeLeft.position.set(-14, 0.22, 0);
117
- officeGroup.add(edgeLeft);
118
- const edgeRight = edgeLeft.clone();
119
- edgeRight.position.x = 14;
120
- officeGroup.add(edgeRight);
121
- }
122
-
123
- /* ═══════════════════════════════════════════════════════════════════════════════
124
- Walls
125
- ═══════════════════════════════════════════════════════════════════════════════ */
126
- function buildWalls() {
127
- // Back wall
128
- const backGeo = new THREE.BoxGeometry(28, 6, 0.15);
129
- const backWall = new THREE.Mesh(backGeo, MAT.wall);
130
- backWall.position.set(0, 3.2, -10);
131
- backWall.castShadow = true;
132
- backWall.receiveShadow = true;
133
- officeGroup.add(backWall);
134
-
135
- // Left wall
136
- const sideGeo = new THREE.BoxGeometry(0.15, 6, 20);
137
- const leftWall = new THREE.Mesh(sideGeo, MAT.wall);
138
- leftWall.position.set(-14, 3.2, 0);
139
- leftWall.castShadow = true;
140
- leftWall.receiveShadow = true;
141
- officeGroup.add(leftWall);
142
-
143
- // Right wall (half + glass)
144
- const rightLower = new THREE.Mesh(new THREE.BoxGeometry(0.15, 2, 20), MAT.wall);
145
- rightLower.position.set(14, 1.2, 0);
146
- rightLower.castShadow = true;
147
- officeGroup.add(rightLower);
148
-
149
- // Wall accent strips
150
- const stripGeo = new THREE.BoxGeometry(28, 0.04, 0.04);
151
- const strip1 = new THREE.Mesh(stripGeo, MAT.neon);
152
- strip1.position.set(0, 5, -9.9);
153
- officeGroup.add(strip1);
154
- const strip2 = new THREE.Mesh(new THREE.BoxGeometry(0.04, 6, 0.04), MAT.neonBlue);
155
- strip2.position.set(-13.9, 3.2, 9.5);
156
- officeGroup.add(strip2);
157
- }
158
-
159
- /* ═══════════════════════════════════════════════════════════════════════════════
160
- Glass Partitions
161
- ═══════════════════════════════════════════════════════════════════════════════ */
162
- function buildGlassPartitions() {
163
- // Partition between server area and desks
164
- const partGeo = new THREE.BoxGeometry(0.08, 4, 8);
165
- const part1 = new THREE.Mesh(partGeo, MAT.wallGlass);
166
- part1.position.set(-5, 2.2, -3);
167
- officeGroup.add(part1);
168
-
169
- // Partition near center
170
- const part2Geo = new THREE.BoxGeometry(8, 3.5, 0.08);
171
- const part2 = new THREE.Mesh(part2Geo, MAT.wallGlass);
172
- part2.position.set(3, 1.95, 3);
173
- officeGroup.add(part2);
174
-
175
- // Glass frame accents
176
- const framGeo = new THREE.BoxGeometry(0.06, 4, 0.06);
177
- const frame1 = new THREE.Mesh(framGeo, MAT.frame);
178
- frame1.position.set(-5, 2.2, 1);
179
- officeGroup.add(frame1);
180
- const frame2 = frame1.clone();
181
- frame2.position.z = -7;
182
- officeGroup.add(frame2);
183
- }
184
-
185
- /* ═══════════════════════════════════════════════════════════════════════════════
186
- Agent Desks — Each agent gets a desk with monitor and chair
187
- ═══════════════════════════════════════════════════════════════════════════════ */
188
- export const DESK_POSITIONS = [];
189
- export const POND_POSITIONS = [];
190
- const DESK_INDICATORS = new Map();
191
-
192
- function buildPondZone() {
193
- const pond = new THREE.Group();
194
- pond.name = 'agent-pond';
195
-
196
- const centerX = -9;
197
- const centerZ = 6.2;
198
-
199
- const rim = new THREE.Mesh(
200
- new THREE.CylinderGeometry(3.4, 3.5, 0.16, 24),
201
- MAT.frame,
202
- );
203
- rim.position.set(centerX, 0.25, centerZ);
204
- rim.receiveShadow = true;
205
- pond.add(rim);
206
-
207
- const water = new THREE.Mesh(
208
- new THREE.CylinderGeometry(3.1, 3.2, 0.1, 24),
209
- new THREE.MeshStandardMaterial({
210
- color: currentTheme === 'dark' ? 0x0f3a5d : 0x7dd3fc,
211
- roughness: 0.18,
212
- metalness: 0.2,
213
- transparent: true,
214
- opacity: currentTheme === 'dark' ? 0.72 : 0.52,
215
- emissive: currentTheme === 'dark' ? 0x0ea5e9 : 0x0369a1,
216
- emissiveIntensity: currentTheme === 'dark' ? 0.16 : 0.06,
217
- }),
218
- );
219
- water.position.set(centerX, 0.3, centerZ);
220
- pond.add(water);
221
-
222
- // Pixel-style stepping stones around the pond edge.
223
- for (let i = 0; i < 10; i++) {
224
- const a = (i / 10) * Math.PI * 2;
225
- const r = 3.7 + (i % 2) * 0.25;
226
- const stone = new THREE.Mesh(
227
- new THREE.BoxGeometry(0.32, 0.06, 0.32),
228
- MAT.deskTop,
229
- );
230
- stone.position.set(centerX + Math.cos(a) * r, 0.23, centerZ + Math.sin(a) * r);
231
- stone.rotation.y = a * 0.7;
232
- stone.receiveShadow = true;
233
- pond.add(stone);
234
- }
235
-
236
- POND_POSITIONS.length = 0;
237
- const count = 24;
238
- for (let i = 0; i < count; i++) {
239
- const a = (i / count) * Math.PI * 2;
240
- const r = 1.55 + (i % 3) * 0.34;
241
- POND_POSITIONS.push({
242
- x: centerX + Math.cos(a) * r,
243
- z: centerZ + Math.sin(a) * r,
244
- });
245
- }
246
-
247
- officeGroup.add(pond);
248
- }
249
-
250
- function buildDesks(count) {
251
- DESK_POSITIONS.length = 0;
252
- DESK_INDICATORS.clear();
253
- const rows = 2;
254
- const cols = Math.ceil(count / rows);
255
- const xStart = -2;
256
- const zStart = -6;
257
- const xSpacing = 4.5;
258
- const zSpacing = 5;
259
-
260
- for (let i = 0; i < count; i++) {
261
- const row = Math.floor(i / cols);
262
- const col = i % cols;
263
- const x = xStart + col * xSpacing;
264
- const z = zStart + row * zSpacing;
265
-
266
- DESK_POSITIONS.push({ x, z });
267
- buildSingleDesk(x, z, i);
268
- }
269
- }
270
-
271
- function buildSingleDesk(x, z, idx) {
272
- const desk = new THREE.Group();
273
- desk.name = `desk-${idx}`;
274
-
275
- // Desktop surface
276
- const topGeo = new THREE.BoxGeometry(2.4, 0.08, 1.2);
277
- const top = new THREE.Mesh(topGeo, MAT.deskTop);
278
- top.position.set(0, 1.0, 0);
279
- top.castShadow = true;
280
- top.receiveShadow = true;
281
- desk.add(top);
282
-
283
- // Legs (4)
284
- const legGeo = new THREE.BoxGeometry(0.08, 0.8, 0.08);
285
- const positions = [
286
- [-1.1, 0.6, -0.5], [1.1, 0.6, -0.5],
287
- [-1.1, 0.6, 0.5], [1.1, 0.6, 0.5],
288
- ];
289
- positions.forEach(p => {
290
- const leg = new THREE.Mesh(legGeo, MAT.desk);
291
- leg.position.set(...p);
292
- leg.castShadow = true;
293
- desk.add(leg);
294
- });
295
-
296
- // Monitor
297
- const monitorGroup = new THREE.Group();
298
- // Screen
299
- const scrGeo = new THREE.BoxGeometry(1.0, 0.7, 0.04);
300
- const scr = new THREE.Mesh(scrGeo, MAT.screen);
301
- scr.position.set(0, 1.65, -0.3);
302
- scr.castShadow = true;
303
- monitorGroup.add(scr);
304
-
305
- // Screen glow face
306
- const glowGeo = new THREE.PlaneGeometry(0.92, 0.62);
307
- const glow = new THREE.Mesh(glowGeo, MAT.screenGlow);
308
- glow.position.set(0, 1.65, -0.278);
309
- monitorGroup.add(glow);
310
-
311
- // Monitor stand
312
- const standGeo = new THREE.BoxGeometry(0.06, 0.28, 0.06);
313
- const stand = new THREE.Mesh(standGeo, MAT.metal);
314
- stand.position.set(0, 1.18, -0.3);
315
- monitorGroup.add(stand);
316
-
317
- // Monitor base
318
- const baseGeo = new THREE.CylinderGeometry(0.18, 0.2, 0.04, 16);
319
- const base = new THREE.Mesh(baseGeo, MAT.metal);
320
- base.position.set(0, 1.04, -0.3);
321
- monitorGroup.add(base);
322
-
323
- desk.add(monitorGroup);
324
-
325
- // Keyboard
326
- const kbGeo = new THREE.BoxGeometry(0.6, 0.02, 0.2);
327
- const kb = new THREE.Mesh(kbGeo, MAT.desk);
328
- kb.position.set(0, 1.06, 0.15);
329
- desk.add(kb);
330
-
331
- // Mouse
332
- const mouseGeo = new THREE.BoxGeometry(0.1, 0.02, 0.14);
333
- const mouse = new THREE.Mesh(mouseGeo, MAT.desk);
334
- mouse.position.set(0.5, 1.06, 0.15);
335
- desk.add(mouse);
336
-
337
- // Chair
338
- const chairGroup = new THREE.Group();
339
- // Seat
340
- const seatGeo = new THREE.BoxGeometry(0.7, 0.08, 0.7);
341
- const seat = new THREE.Mesh(seatGeo, MAT.chair);
342
- seat.position.set(0, 0.7, 0.9);
343
- seat.castShadow = true;
344
- chairGroup.add(seat);
345
- // Back rest
346
- const backGeo = new THREE.BoxGeometry(0.7, 0.6, 0.06);
347
- const back = new THREE.Mesh(backGeo, MAT.chair);
348
- back.position.set(0, 1.04, 1.23);
349
- back.castShadow = true;
350
- chairGroup.add(back);
351
- // Chair base
352
- const cbaseGeo = new THREE.CylinderGeometry(0.04, 0.04, 0.5, 8);
353
- const cbase = new THREE.Mesh(cbaseGeo, MAT.metal);
354
- cbase.position.set(0, 0.45, 0.9);
355
- chairGroup.add(cbase);
356
- // Chair wheels (5-star base)
357
- const wheelGeo = new THREE.CylinderGeometry(0.25, 0.28, 0.03, 5);
358
- const wheel = new THREE.Mesh(wheelGeo, MAT.metal);
359
- wheel.position.set(0, 0.2, 0.9);
360
- chairGroup.add(wheel);
361
-
362
- desk.add(chairGroup);
363
-
364
- // Desk lamp
365
- if (idx % 2 === 0) {
366
- const lampGroup = new THREE.Group();
367
- const lampBase = new THREE.Mesh(
368
- new THREE.CylinderGeometry(0.1, 0.12, 0.04, 16), MAT.metal);
369
- lampBase.position.set(-0.8, 1.06, -0.2);
370
- lampGroup.add(lampBase);
371
- const lampArm = new THREE.Mesh(
372
- new THREE.CylinderGeometry(0.015, 0.015, 0.5, 8), MAT.metal);
373
- lampArm.position.set(-0.8, 1.31, -0.2);
374
- lampGroup.add(lampArm);
375
- const lampHead = new THREE.Mesh(
376
- new THREE.ConeGeometry(0.1, 0.12, 8), MAT.accent);
377
- lampHead.position.set(-0.8, 1.58, -0.2);
378
- lampHead.rotation.x = Math.PI;
379
- lampGroup.add(lampHead);
380
- desk.add(lampGroup);
381
- }
382
-
383
- // Coffee mug (alternating desks)
384
- if (idx % 3 === 1) {
385
- const mugGeo = new THREE.CylinderGeometry(0.05, 0.04, 0.1, 12);
386
- const mug = new THREE.Mesh(mugGeo, MAT.coffee);
387
- mug.position.set(0.8, 1.09, 0.0);
388
- desk.add(mug);
389
- }
390
-
391
- // Occupancy beacon near each desk (idle=dim, occupied=bright).
392
- const indicator = new THREE.Mesh(
393
- new THREE.CylinderGeometry(0.06, 0.06, 0.04, 12),
394
- new THREE.MeshStandardMaterial({
395
- color: currentTheme === 'dark' ? 0x475569 : 0x94a3b8,
396
- roughness: 0.25,
397
- metalness: 0.4,
398
- emissive: currentTheme === 'dark' ? 0x0f172a : 0x64748b,
399
- emissiveIntensity: 0.12,
400
- }),
401
- );
402
- indicator.position.set(1.0, 1.08, -0.32);
403
- desk.add(indicator);
404
- DESK_INDICATORS.set(idx, indicator);
405
-
406
- desk.position.set(x, 0.2, z);
407
- officeGroup.add(desk);
408
- }
409
-
410
- export function setDeskOccupied(index, occupied) {
411
- const indicator = DESK_INDICATORS.get(index);
412
- if (!indicator) return;
413
- const mat = indicator.material;
414
- if (!mat) return;
415
-
416
- if (occupied) {
417
- mat.color.setHex(currentTheme === 'dark' ? 0x34d399 : 0x059669);
418
- mat.emissive.setHex(currentTheme === 'dark' ? 0x34d399 : 0x047857);
419
- mat.emissiveIntensity = currentTheme === 'dark' ? 0.62 : 0.35;
420
- } else {
421
- mat.color.setHex(currentTheme === 'dark' ? 0x475569 : 0x94a3b8);
422
- mat.emissive.setHex(currentTheme === 'dark' ? 0x0f172a : 0x64748b);
423
- mat.emissiveIntensity = 0.12;
424
- }
425
- }
426
-
427
- /* ═══════════════════════════════════════════════════════════════════════════════
428
- Server Rack
429
- ═══════════════════════════════════════════════════════════════════════════════ */
430
- function buildServerRack() {
431
- const rack = new THREE.Group();
432
- rack.name = 'server-rack';
433
-
434
- // Main cabinet
435
- const cabinetGeo = new THREE.BoxGeometry(1.0, 3.5, 0.8);
436
- const cabinet = new THREE.Mesh(cabinetGeo, MAT.server);
437
- cabinet.position.set(0, 1.95, 0);
438
- cabinet.castShadow = true;
439
- rack.add(cabinet);
440
-
441
- // Server units (stacked)
442
- for (let i = 0; i < 6; i++) {
443
- const unitGeo = new THREE.BoxGeometry(0.9, 0.35, 0.7);
444
- const unit = new THREE.Mesh(unitGeo, MAT.desk);
445
- unit.position.set(0, 0.5 + i * 0.5, 0);
446
- rack.add(unit);
447
-
448
- // LED lights on each server unit
449
- const ledColors = [MAT.neon, MAT.neonBlue, MAT.neon, MAT.neonBlue, MAT.neon, MAT.neonPurple];
450
- for (let j = 0; j < 3; j++) {
451
- const ledGeo = new THREE.BoxGeometry(0.03, 0.03, 0.01);
452
- const led = new THREE.Mesh(ledGeo, ledColors[i]);
453
- led.position.set(-0.3 + j * 0.15, 0.5 + i * 0.5, 0.36);
454
- rack.add(led);
455
- }
456
- }
457
-
458
- // Status light on top
459
- const statusLight = new THREE.Mesh(
460
- new THREE.SphereGeometry(0.06, 8, 8), MAT.accent);
461
- statusLight.position.set(0, 3.8, 0);
462
- rack.add(statusLight);
463
-
464
- rack.position.set(-10, 0.2, -6);
465
- officeGroup.add(rack);
466
-
467
- // Second rack
468
- const rack2 = rack.clone();
469
- rack2.position.set(-10, 0.2, -3);
470
- rack2.name = 'server-rack-2';
471
- officeGroup.add(rack2);
472
- }
473
-
474
- /* ═══════════════════════════════════════════════════════════════════════════════
475
- Coffee Machine Area
476
- ═══════════════════════════════════════════════════════════════════════════════ */
477
- function buildCoffeeMachine() {
478
- const area = new THREE.Group();
479
- area.name = 'coffee-area';
480
-
481
- // Counter
482
- const counterGeo = new THREE.BoxGeometry(2.5, 1.0, 0.8);
483
- const counter = new THREE.Mesh(counterGeo, MAT.deskTop);
484
- counter.position.set(0, 0.7, 0);
485
- counter.castShadow = true;
486
- counter.receiveShadow = true;
487
- area.add(counter);
488
-
489
- // Machine body
490
- const machineGeo = new THREE.BoxGeometry(0.5, 0.6, 0.4);
491
- const machine = new THREE.Mesh(machineGeo, MAT.server);
492
- machine.position.set(-0.4, 1.5, 0);
493
- machine.castShadow = true;
494
- area.add(machine);
495
-
496
- // Machine spout
497
- const spoutGeo = new THREE.CylinderGeometry(0.02, 0.02, 0.15, 8);
498
- const spout = new THREE.Mesh(spoutGeo, MAT.metal);
499
- spout.position.set(-0.4, 1.13, 0.15);
500
- area.add(spout);
501
-
502
- // Cups
503
- for (let i = 0; i < 3; i++) {
504
- const cupGeo = new THREE.CylinderGeometry(0.04, 0.035, 0.08, 8);
505
- const cup = new THREE.Mesh(cupGeo, MAT.coffee);
506
- cup.position.set(0.3 + i * 0.15, 1.24, 0);
507
- area.add(cup);
508
- }
509
-
510
- // Green indicator LED
511
- const led = new THREE.Mesh(
512
- new THREE.SphereGeometry(0.025, 8, 8), MAT.accent);
513
- led.position.set(-0.15, 1.7, 0.21);
514
- area.add(led);
515
-
516
- area.position.set(11, 0.2, -7);
517
- officeGroup.add(area);
518
- }
519
-
520
- /* ═══════════════════════════════════════════════════════════════════════════════
521
- Plants
522
- ═══════════════════════════════════════════════════════════════════════════════ */
523
- function buildPlants() {
524
- const positions = [
525
- [-12, 0.2, 8], [12, 0.2, 8], [-12, 0.2, -8], [8, 0.2, -8],
526
- [0, 0.2, 9], [-7, 0.2, 9],
527
- ];
528
-
529
- positions.forEach((pos, i) => {
530
- const plant = new THREE.Group();
531
- plant.name = `plant-${i}`;
532
-
533
- // Pot
534
- const potGeo = new THREE.CylinderGeometry(0.25, 0.2, 0.35, 8);
535
- const pot = new THREE.Mesh(potGeo, MAT.pot);
536
- pot.position.set(0, 0.175, 0);
537
- pot.castShadow = true;
538
- plant.add(pot);
539
-
540
- // Leaves (spheres at different angles)
541
- const leafMat = MAT.plant;
542
- for (let j = 0; j < 5; j++) {
543
- const leafGeo = new THREE.SphereGeometry(0.15 + Math.random() * 0.1, 6, 6);
544
- const leaf = new THREE.Mesh(leafGeo, leafMat);
545
- const angle = (j / 5) * Math.PI * 2;
546
- leaf.position.set(
547
- Math.cos(angle) * 0.15,
548
- 0.5 + Math.random() * 0.3,
549
- Math.sin(angle) * 0.15
550
- );
551
- leaf.scale.y = 1.2 + Math.random() * 0.3;
552
- plant.add(leaf);
553
- }
554
-
555
- // Center trunk
556
- const trunkGeo = new THREE.CylinderGeometry(0.03, 0.04, 0.4, 6);
557
- const trunk = new THREE.Mesh(trunkGeo, MAT.coffee);
558
- trunk.position.set(0, 0.5, 0);
559
- plant.add(trunk);
560
-
561
- plant.position.set(...pos);
562
- officeGroup.add(plant);
563
- });
564
- }
565
-
566
- /* ═══════════════════════════════════════════════════════════════════════════════
567
- Whiteboard
568
- ═══════════════════════════════════════════════════════════════════════════════ */
569
- function buildWhiteboard() {
570
- const wb = new THREE.Group();
571
- wb.name = 'whiteboard';
572
-
573
- // Board
574
- const boardGeo = new THREE.BoxGeometry(3, 2, 0.06);
575
- const board = new THREE.Mesh(boardGeo, MAT.whiteboard);
576
- board.position.set(0, 3.2, -9.9);
577
- board.castShadow = true;
578
- wb.add(board);
579
-
580
- // Frame
581
- const frameGeo = new THREE.BoxGeometry(3.1, 2.1, 0.04);
582
- const frame = new THREE.Mesh(frameGeo, MAT.frame);
583
- frame.position.set(0, 3.2, -9.95);
584
- wb.add(frame);
585
-
586
- // Colored marks on the board
587
- const markColors = [0x34d399, 0x60a5fa, 0xf87171, 0xfbbf24];
588
- for (let i = 0; i < 4; i++) {
589
- const markGeo = new THREE.BoxGeometry(0.6, 0.04, 0.01);
590
- const mark = new THREE.Mesh(markGeo, new THREE.MeshBasicMaterial({
591
- color: markColors[i], transparent: true, opacity: 0.6,
592
- }));
593
- mark.position.set(-0.9 + i * 0.6, 3.5 - i * 0.2, -9.86);
594
- wb.add(mark);
595
- }
596
-
597
- officeGroup.add(wb);
598
- }
599
-
600
- /* ═══════════════════════════════════════════════════════════════════════════════
601
- Pillars
602
- ═══════════════════════════════════════════════════════════════════════════════ */
603
- function buildPillars() {
604
- const positions = [[-8, 0], [8, 0], [-8, -6], [8, -6]];
605
- positions.forEach(([x, z], i) => {
606
- const pillarGeo = new THREE.BoxGeometry(0.4, 6.2, 0.4);
607
- const pillar = new THREE.Mesh(pillarGeo, MAT.pillar);
608
- pillar.position.set(x, 3.3, z);
609
- pillar.castShadow = true;
610
- pillar.name = `pillar-${i}`;
611
- officeGroup.add(pillar);
612
-
613
- // Neon accent at pillar base
614
- const neonGeo = new THREE.BoxGeometry(0.5, 0.04, 0.5);
615
- const neon = new THREE.Mesh(neonGeo, i < 2 ? MAT.neon : MAT.neonBlue);
616
- neon.position.set(x, 0.22, z);
617
- officeGroup.add(neon);
618
- });
619
- }
620
-
621
- /* ═══════════════════════════════════════════════════════════════════════════════
622
- Carpet
623
- ═══════════════════════════════════════════════════════════════════════════════ */
624
- function buildCarpet() {
625
- const carpetGeo = new THREE.BoxGeometry(10, 0.03, 7);
626
- const carpet = new THREE.Mesh(carpetGeo, MAT.carpet);
627
- carpet.position.set(2, 0.22, -3);
628
- carpet.receiveShadow = true;
629
- carpet.name = 'carpet';
630
- officeGroup.add(carpet);
631
- }
632
-
633
- /* ═══════════════════════════════════════════════════════════════════════════════
634
- Ceiling Lights
635
- ═══════════════════════════════════════════════════════════════════════════════ */
636
- function buildCeilingLights() {
637
- const positions = [[-4, -4], [4, -4], [-4, 3], [4, 3], [0, 0]];
638
- positions.forEach(([x, z], i) => {
639
- const lightGroup = new THREE.Group();
640
- lightGroup.name = `ceiling-light-${i}`;
641
-
642
- // Housing
643
- const housingGeo = new THREE.BoxGeometry(1.5, 0.06, 0.4);
644
- const housing = new THREE.Mesh(housingGeo, MAT.metal);
645
- housing.position.set(0, 6.0, 0);
646
- lightGroup.add(housing);
647
-
648
- // Light panel (emitting)
649
- const panelGeo = new THREE.BoxGeometry(1.4, 0.02, 0.35);
650
- const panelMat = new THREE.MeshBasicMaterial({
651
- color: currentTheme === 'dark' ? 0xddeeff : 0xfffef0,
652
- transparent: true,
653
- opacity: currentTheme === 'dark' ? 0.3 : 0.6,
654
- });
655
- const panel = new THREE.Mesh(panelGeo, panelMat);
656
- panel.position.set(0, 5.97, 0);
657
- panel.name = 'light-panel';
658
- lightGroup.add(panel);
659
-
660
- // Wire
661
- const wireGeo = new THREE.CylinderGeometry(0.008, 0.008, 0.5, 4);
662
- const wire = new THREE.Mesh(wireGeo, MAT.metal);
663
- wire.position.set(-0.5, 6.25, 0);
664
- lightGroup.add(wire);
665
- const wire2 = wire.clone();
666
- wire2.position.x = 0.5;
667
- lightGroup.add(wire2);
668
-
669
- lightGroup.position.set(x, 0, z);
670
- officeGroup.add(lightGroup);
671
- });
672
- }
673
-
674
- /* ═══════════════════════════════════════════════════════════════════════════════
675
- Neon Strips — Decorative glowing lines
676
- ═══════════════════════════════════════════════════════════════════════════════ */
677
- function buildNeonStrips() {
678
- // Floor neon strips (crossing pattern)
679
- const stripGeo = new THREE.BoxGeometry(0.04, 0.02, 14);
680
- const strip1 = new THREE.Mesh(stripGeo, MAT.neon);
681
- strip1.position.set(-5.04, 0.23, -2);
682
- officeGroup.add(strip1);
683
-
684
- const strip2Geo = new THREE.BoxGeometry(12, 0.02, 0.04);
685
- const strip2 = new THREE.Mesh(strip2Geo, MAT.neonBlue);
686
- strip2.position.set(2, 0.23, 3.04);
687
- officeGroup.add(strip2);
688
-
689
- // Ceiling neon strip
690
- const ceilStripGeo = new THREE.BoxGeometry(26, 0.03, 0.03);
691
- const ceilStrip = new THREE.Mesh(ceilStripGeo, MAT.neonPurple);
692
- ceilStrip.position.set(0, 6.15, 0);
693
- officeGroup.add(ceilStrip);
694
-
695
- const ceilStrip2Geo = new THREE.BoxGeometry(0.03, 0.03, 18);
696
- const ceilStrip2 = new THREE.Mesh(ceilStrip2Geo, MAT.neon);
697
- ceilStrip2.position.set(0, 6.15, 0);
698
- officeGroup.add(ceilStrip2);
699
- }
700
-
701
- /* ═══════════════════════════════════════════════════════════════════════════════
702
- Center Platform — Hologram display base
703
- ═══════════════════════════════════════════════════════════════════════════════ */
704
- function buildCenterPlatform() {
705
- const platform = new THREE.Group();
706
- platform.name = 'center-platform';
707
-
708
- // Octagonal base
709
- const baseGeo = new THREE.CylinderGeometry(1.5, 1.6, 0.15, 8);
710
- const base = new THREE.Mesh(baseGeo, MAT.metal);
711
- base.position.set(0, 0.28, 0);
712
- base.receiveShadow = true;
713
- platform.add(base);
714
-
715
- // Inner ring
716
- const innerGeo = new THREE.CylinderGeometry(1.0, 1.0, 0.2, 16);
717
- const inner = new THREE.Mesh(innerGeo, MAT.accent);
718
- inner.position.set(0, 0.32, 0);
719
- platform.add(inner);
720
-
721
- // Rotating accent ring
722
- const ringGeo = new THREE.TorusGeometry(1.3, 0.02, 8, 32);
723
- const ring = new THREE.Mesh(ringGeo, MAT.neon);
724
- ring.rotation.x = Math.PI / 2;
725
- ring.position.set(0, 0.4, 0);
726
- ring.name = 'holo-ring';
727
- platform.add(ring);
728
-
729
- // Second ring (tilted)
730
- const ring2 = new THREE.Mesh(
731
- new THREE.TorusGeometry(1.1, 0.015, 8, 32), MAT.neonBlue);
732
- ring2.rotation.x = Math.PI / 2;
733
- ring2.rotation.z = Math.PI / 6;
734
- ring2.position.set(0, 0.5, 0);
735
- ring2.name = 'holo-ring-2';
736
- platform.add(ring2);
737
-
738
- officeGroup.add(platform);
739
- }
740
-
741
- /* ═══════════════════════════════════════════════════════════════════════════════
742
- Bookshelf
743
- ═══════════════════════════════════════════════════════════════════════════════ */
744
- function buildBookshelf() {
745
- const shelf = new THREE.Group();
746
- shelf.name = 'bookshelf';
747
-
748
- // Frame
749
- const frameGeo = new THREE.BoxGeometry(2.0, 3.0, 0.4);
750
- const frame = new THREE.Mesh(frameGeo, MAT.desk);
751
- frame.position.set(0, 1.7, 0);
752
- frame.castShadow = true;
753
- shelf.add(frame);
754
-
755
- // Shelves
756
- for (let i = 0; i < 4; i++) {
757
- const shelfGeo = new THREE.BoxGeometry(1.9, 0.04, 0.38);
758
- const sh = new THREE.Mesh(shelfGeo, MAT.deskTop);
759
- sh.position.set(0, 0.5 + i * 0.7, 0);
760
- shelf.add(sh);
761
- }
762
-
763
- // Books (colored blocks)
764
- const bookColors = [0x60a5fa, 0xf87171, 0x34d399, 0xfbbf24, 0xa78bfa, 0xf472b6];
765
- for (let row = 0; row < 3; row++) {
766
- let bx = -0.8;
767
- for (let j = 0; j < 5; j++) {
768
- const w = 0.08 + Math.random() * 0.15;
769
- const h = 0.25 + Math.random() * 0.15;
770
- const bookGeo = new THREE.BoxGeometry(w, h, 0.25);
771
- const bookMat = new THREE.MeshStandardMaterial({
772
- color: bookColors[(row * 5 + j) % bookColors.length],
773
- roughness: 0.8,
774
- });
775
- const book = new THREE.Mesh(bookGeo, bookMat);
776
- book.position.set(bx + w / 2, 0.55 + row * 0.7 + h / 2, 0);
777
- shelf.add(book);
778
- bx += w + 0.02;
779
- }
780
- }
781
-
782
- shelf.position.set(-12.5, 0.2, -4);
783
- shelf.rotation.y = Math.PI / 2;
784
- officeGroup.add(shelf);
785
- }
786
-
787
- /* ═══════════════════════════════════════════════════════════════════════════════
788
- Water Cooler
789
- ═══════════════════════════════════════════════════════════════════════════════ */
790
- function buildWaterCooler() {
791
- const cooler = new THREE.Group();
792
- cooler.name = 'water-cooler';
793
-
794
- // Body
795
- const bodyGeo = new THREE.BoxGeometry(0.4, 1.2, 0.35);
796
- const body = new THREE.Mesh(bodyGeo, MAT.server);
797
- body.position.set(0, 0.8, 0);
798
- body.castShadow = true;
799
- cooler.add(body);
800
-
801
- // Water jug on top
802
- const jugGeo = new THREE.CylinderGeometry(0.14, 0.16, 0.5, 12);
803
- const jugMat = new THREE.MeshPhysicalMaterial({
804
- color: 0x93c5fd, transmission: 0.8, roughness: 0.1,
805
- thickness: 0.3, transparent: true, opacity: 0.5,
806
- });
807
- const jug = new THREE.Mesh(jugGeo, jugMat);
808
- jug.position.set(0, 1.65, 0);
809
- cooler.add(jug);
810
-
811
- cooler.position.set(11, 0.2, -4);
812
- officeGroup.add(cooler);
813
- }
814
-
815
- /* ═══════════════════════════════════════════════════════════════════════════════
816
- Decorative Elements
817
- ═══════════════════════════════════════════════════════════════════════════════ */
818
- function buildDecorativeElements() {
819
- // Wall clock
820
- const clockGroup = new THREE.Group();
821
- clockGroup.name = 'wall-clock';
822
- const clockFace = new THREE.Mesh(
823
- new THREE.CylinderGeometry(0.4, 0.4, 0.04, 24),
824
- MAT.whiteboard);
825
- clockFace.rotation.x = Math.PI / 2;
826
- clockFace.position.set(-6, 4.5, -9.9);
827
- clockGroup.add(clockFace);
828
-
829
- const clockRim = new THREE.Mesh(
830
- new THREE.TorusGeometry(0.4, 0.02, 8, 24),
831
- MAT.frame);
832
- clockRim.position.set(-6, 4.5, -9.88);
833
- clockGroup.add(clockRim);
834
-
835
- // Clock hands
836
- const hourGeo = new THREE.BoxGeometry(0.02, 0.2, 0.01);
837
- const hourHand = new THREE.Mesh(hourGeo, MAT.frame);
838
- hourHand.position.set(-6, 4.6, -9.86);
839
- hourHand.rotation.z = Math.PI / 4;
840
- clockGroup.add(hourHand);
841
-
842
- const minGeo = new THREE.BoxGeometry(0.015, 0.3, 0.01);
843
- const minHand = new THREE.Mesh(minGeo, MAT.accent);
844
- minHand.position.set(-6, 4.6, -9.84);
845
- minHand.rotation.z = -Math.PI / 6;
846
- clockGroup.add(minHand);
847
-
848
- officeGroup.add(clockGroup);
849
-
850
- // Ceiling fan
851
- const fan = new THREE.Group();
852
- fan.name = 'ceiling-fan';
853
- const fanHub = new THREE.Mesh(
854
- new THREE.CylinderGeometry(0.1, 0.1, 0.08, 12), MAT.metal);
855
- fanHub.position.set(0, 6.1, 0);
856
- fan.add(fanHub);
857
- const fanRod = new THREE.Mesh(
858
- new THREE.CylinderGeometry(0.02, 0.02, 0.4, 6), MAT.metal);
859
- fanRod.position.set(0, 6.3, 0);
860
- fan.add(fanRod);
861
-
862
- for (let i = 0; i < 4; i++) {
863
- const bladeGeo = new THREE.BoxGeometry(1.5, 0.02, 0.25);
864
- const blade = new THREE.Mesh(bladeGeo, MAT.desk);
865
- blade.position.set(0, 6.08, 0);
866
- blade.rotation.y = (i / 4) * Math.PI * 2;
867
- blade.translateX(0.75);
868
- fan.add(blade);
869
- }
870
-
871
- fan.position.set(2, 0, -2);
872
- officeGroup.add(fan);
873
-
874
- // Door frame on side wall
875
- const doorFrame = new THREE.Group();
876
- doorFrame.name = 'door-frame';
877
- const doorPost = new THREE.Mesh(
878
- new THREE.BoxGeometry(0.1, 3, 0.1), MAT.frame);
879
- doorPost.position.set(-13.95, 1.7, 5);
880
- doorFrame.add(doorPost);
881
- const doorPost2 = doorPost.clone();
882
- doorPost2.position.z = 7;
883
- doorFrame.add(doorPost2);
884
- const doorLintel = new THREE.Mesh(
885
- new THREE.BoxGeometry(0.1, 0.1, 2.1), MAT.frame);
886
- doorLintel.position.set(-13.95, 3.2, 6);
887
- doorFrame.add(doorLintel);
888
- officeGroup.add(doorFrame);
889
- }
890
-
891
- /* ═══════════════════════════════════════════════════════════════════════════════
892
- Floor Y position getter
893
- ═══════════════════════════════════════════════════════════════════════════════ */
894
- export function getFloorY() {
895
- return 0.2;
896
- }
897
-
898
- export function disposeOffice() {
899
- disposeOfficeGroup(getScene());
900
- }
901
-
902
- /* ═══════════════════════════════════════════════════════════════════════════════
903
- Theme Update
904
- ═══════════════════════════════════════════════════════════════════════════════ */
905
- export function updateOfficeLighting(theme) {
906
- currentTheme = theme;
907
- const scene = getScene();
908
- disposeOfficeGroup(scene);
909
- initMaterials(theme);
910
- officeGroup = new THREE.Group();
911
- officeGroup.name = 'office';
912
-
913
- buildFloor();
914
- buildWalls();
915
- buildGlassPartitions();
916
- buildPondZone();
917
- buildDesks(6);
918
- buildServerRack();
919
- buildCoffeeMachine();
920
- buildPlants();
921
- buildWhiteboard();
922
- buildPillars();
923
- buildCarpet();
924
- buildCeilingLights();
925
- buildNeonStrips();
926
- buildCenterPlatform();
927
- buildBookshelf();
928
- buildWaterCooler();
929
- buildDecorativeElements();
930
-
931
- scene.add(officeGroup);
932
- }