let-them-talk 3.5.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,337 @@
1
+ import { S } from './state.js';
2
+ import { updateMonitorScreen, setMonitorDim } from './monitors.js';
3
+
4
+ export function easeInOutQuad(t) {
5
+ return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
6
+ }
7
+
8
+ export function updateAgent(agent, dt, time) {
9
+ var isWalking = agent.target !== null;
10
+ var isSleeping = agent.state === 'sleeping';
11
+ var isDead = agent.state === 'dead';
12
+
13
+ // Death removal
14
+ if (agent.dying) {
15
+ agent.deathOpacity = Math.max(0, agent.deathOpacity - dt * 2);
16
+ var s = Math.max(0.01, agent.deathOpacity);
17
+ agent.parts.group.scale.set(s, s, s);
18
+ if (agent.deathOpacity <= 0) {
19
+ S.scene.remove(agent.parts.group);
20
+ disposeAgent(agent);
21
+ delete S.agents3d[agent.name];
22
+ return;
23
+ }
24
+ }
25
+
26
+ // Walk
27
+ if (agent.target && agent.walkDuration > 0) {
28
+ var speedMult = S.conversationVelocity === 1 ? 1.5 : (S.conversationVelocity === -1 ? 0.8 : 1);
29
+ agent.walkProgress += (dt / agent.walkDuration) * speedMult;
30
+ if (agent.walkProgress >= 1) {
31
+ agent.pos.x = agent.target.x;
32
+ agent.pos.z = agent.target.z;
33
+ var cb = agent.target.cb;
34
+ agent.target = null;
35
+ agent.walkProgress = 0;
36
+ if (agent.walkQueue && agent.walkQueue.length > 0) {
37
+ var next = agent.walkQueue.shift();
38
+ walkTo(agent, next.x, next.z, next.cb);
39
+ } else if (cb) {
40
+ cb();
41
+ }
42
+ } else {
43
+ var t = easeInOutQuad(agent.walkProgress);
44
+ agent.pos.x = agent.walkStart.x + (agent.target.x - agent.walkStart.x) * t;
45
+ agent.pos.z = agent.walkStart.z + (agent.target.z - agent.walkStart.z) * t;
46
+ }
47
+ }
48
+
49
+ agent.parts.group.position.x = agent.pos.x;
50
+ agent.parts.group.position.z = agent.pos.z;
51
+
52
+ if (isDead && !agent.dying) {
53
+ agent.parts.group.visible = false;
54
+ return;
55
+ }
56
+ agent.parts.group.visible = true;
57
+
58
+ // Hand-raise gesture
59
+ if (agent.handRaiseTimer > 0) {
60
+ agent.handRaiseTimer -= dt;
61
+ var raiseT = agent.handRaiseTimer / 0.4;
62
+ agent.parts.rightArm.rotation.z = -Math.sin(raiseT * Math.PI) * 1.2;
63
+ agent.parts.rightArm.rotation.x = -Math.sin(raiseT * Math.PI) * 0.3;
64
+ }
65
+
66
+ // Wave gesture (both arms up, friendly wave)
67
+ if (agent.waveTimer > 0) {
68
+ agent.waveTimer -= dt;
69
+ var wT = agent.waveTimer / 0.8;
70
+ agent.parts.rightArm.rotation.z = -Math.sin(wT * Math.PI) * 1.4;
71
+ agent.parts.rightArm.rotation.x = Math.sin(time * 12) * 0.3 * wT;
72
+ agent.parts.leftArm.rotation.z = Math.sin(wT * Math.PI) * 0.3;
73
+ }
74
+
75
+ // Thinking gesture (hand on chin, head tilted)
76
+ if (agent.thinkTimer > 0) {
77
+ agent.thinkTimer -= dt;
78
+ var thT = Math.min(1, agent.thinkTimer / 1.5);
79
+ agent.parts.rightArm.rotation.x = -1.0 * thT;
80
+ agent.parts.rightForearm.rotation.x = -1.5 * thT;
81
+ agent.parts.head.rotation.z = 0.1 * thT;
82
+ agent.parts.head.rotation.x = -0.08 * thT;
83
+ }
84
+
85
+ // Pointing gesture (right arm extended forward)
86
+ if (agent.pointTimer > 0) {
87
+ agent.pointTimer -= dt;
88
+ var ptT = agent.pointTimer / 0.6;
89
+ agent.parts.rightArm.rotation.x = -Math.sin(ptT * Math.PI) * 1.4;
90
+ agent.parts.rightForearm.rotation.x = -0.1 * Math.sin(ptT * Math.PI);
91
+ }
92
+
93
+ // Celebrate gesture (both arms up, bounce)
94
+ if (agent.celebrateTimer > 0) {
95
+ agent.celebrateTimer -= dt;
96
+ var celT = agent.celebrateTimer / 1.5;
97
+ agent.parts.leftArm.rotation.z = Math.sin(celT * Math.PI) * 1.6;
98
+ agent.parts.rightArm.rotation.z = -Math.sin(celT * Math.PI) * 1.6;
99
+ agent.parts.leftArm.rotation.x = -0.2 * celT;
100
+ agent.parts.rightArm.rotation.x = -0.2 * celT;
101
+ agent.parts.group.position.y += Math.abs(Math.sin(time * 10)) * 0.04 * celT;
102
+ }
103
+
104
+ // Stretch gesture (arms wide, body arches back)
105
+ if (agent.stretchTimer > 0) {
106
+ agent.stretchTimer -= dt;
107
+ var stT = agent.stretchTimer / 2;
108
+ var stPhase = Math.sin(stT * Math.PI);
109
+ agent.parts.leftArm.rotation.z = stPhase * 1.3;
110
+ agent.parts.rightArm.rotation.z = -stPhase * 1.3;
111
+ agent.parts.leftArm.rotation.x = -stPhase * 0.5;
112
+ agent.parts.rightArm.rotation.x = -stPhase * 0.5;
113
+ agent.parts.body.rotation.x = -stPhase * 0.15;
114
+ agent.parts.head.rotation.x = -stPhase * 0.2;
115
+ }
116
+
117
+ // Idle gesture system — random gestures when sitting and idle
118
+ if (!agent.idleGestureTimer) agent.idleGestureTimer = 5 + Math.random() * 10;
119
+ if (agent.isSitting && agent.state === 'active' && !isWalking && !agent.isListening) {
120
+ agent.idleGestureTimer -= dt;
121
+ if (agent.idleGestureTimer <= 0) {
122
+ agent.idleGestureTimer = 8 + Math.random() * 15;
123
+ var gestures = ['stretch', 'think', 'none', 'none', 'none'];
124
+ var gesture = gestures[Math.floor(Math.random() * gestures.length)];
125
+ if (gesture === 'stretch') agent.stretchTimer = 2;
126
+ else if (gesture === 'think') agent.thinkTimer = 1.5;
127
+ }
128
+ }
129
+
130
+ // Face walk direction
131
+ if (isWalking && agent.target) {
132
+ var dx = agent.target.x - agent.pos.x;
133
+ var dz = agent.target.z - agent.pos.z;
134
+ if (Math.abs(dx) > 0.01 || Math.abs(dz) > 0.01) {
135
+ agent.facingTarget = Math.atan2(dx, dz);
136
+ }
137
+ }
138
+
139
+ // Sitting logic
140
+ var atDesk = !agent.location || agent.location === 'desk';
141
+ var shouldSit = !isWalking && agent.registered && !isSleeping && !isDead && agent.handRaiseTimer <= 0 && atDesk;
142
+ if (shouldSit && !agent.isSitting) {
143
+ agent.isSitting = true;
144
+ } else if (!shouldSit && agent.isSitting) {
145
+ agent.isSitting = false;
146
+ }
147
+
148
+ var sittingTarget = agent.isSitting ? 1 : 0;
149
+ agent.sittingLerp += (sittingTarget - agent.sittingLerp) * Math.min(1, dt * 5);
150
+
151
+ agent.parts.group.position.y = agent.sittingLerp * 0.06;
152
+ var sitHip = -1.5 * agent.sittingLerp;
153
+ agent.parts.leftLeg.rotation.x = agent.parts.leftLeg.rotation.x * (1 - agent.sittingLerp) + sitHip * agent.sittingLerp;
154
+ agent.parts.rightLeg.rotation.x = agent.parts.rightLeg.rotation.x * (1 - agent.sittingLerp) + sitHip * agent.sittingLerp;
155
+ var sitKnee = 1.5 * agent.sittingLerp;
156
+ agent.parts.leftLowerLeg.rotation.x = sitKnee;
157
+ agent.parts.rightLowerLeg.rotation.x = sitKnee;
158
+ agent.parts.leftForearm.rotation.x = -0.4 * agent.sittingLerp;
159
+ agent.parts.rightForearm.rotation.x = -0.4 * agent.sittingLerp;
160
+
161
+ // Facing at desk
162
+ if (agent.isSitting && agent.sittingLerp > 0.5) {
163
+ agent.facingTarget = Math.PI;
164
+ }
165
+
166
+ // Idle look-around
167
+ if (!isWalking && !agent.isSitting && !isSleeping && agent.registered) {
168
+ agent.facingTarget = Math.sin(time * 0.3 + agent.name.length) * 0.4;
169
+ }
170
+
171
+ // Smooth rotation
172
+ var currentRot = agent.parts.group.rotation.y;
173
+ var diff = agent.facingTarget - currentRot;
174
+ while (diff > Math.PI) diff -= Math.PI * 2;
175
+ while (diff < -Math.PI) diff += Math.PI * 2;
176
+ agent.parts.group.rotation.y += diff * Math.min(1, dt * 4);
177
+
178
+ // Leg/arm swing
179
+ if (isWalking) {
180
+ var swingSpeed = S.conversationVelocity === 1 ? 14 : (S.conversationVelocity === -1 ? 7 : 10);
181
+ var swingAmplitude = S.conversationVelocity === 1 ? 0.7 : (S.conversationVelocity === -1 ? 0.35 : 0.5);
182
+ var swing = Math.sin(time * swingSpeed) * swingAmplitude;
183
+ agent.parts.leftLeg.rotation.x = swing;
184
+ agent.parts.rightLeg.rotation.x = -swing;
185
+ agent.parts.leftLowerLeg.rotation.x = Math.max(0, -swing) * 0.8;
186
+ agent.parts.rightLowerLeg.rotation.x = Math.max(0, swing) * 0.8;
187
+ agent.parts.leftArm.rotation.x = -swing * 0.7;
188
+ agent.parts.rightArm.rotation.x = swing * 0.7;
189
+ agent.parts.leftForearm.rotation.x = -0.3 - Math.abs(swing) * 0.3;
190
+ agent.parts.rightForearm.rotation.x = -0.3 - Math.abs(swing) * 0.3;
191
+ } else if (!agent.isSitting) {
192
+ agent.parts.leftLeg.rotation.x *= 0.9;
193
+ agent.parts.rightLeg.rotation.x *= 0.9;
194
+ agent.parts.leftArm.rotation.x *= 0.9;
195
+ agent.parts.rightArm.rotation.x *= 0.9;
196
+ agent.parts.leftLowerLeg.rotation.x *= 0.9;
197
+ agent.parts.rightLowerLeg.rotation.x *= 0.9;
198
+ agent.parts.leftForearm.rotation.x *= 0.9;
199
+ agent.parts.rightForearm.rotation.x *= 0.9;
200
+ }
201
+
202
+ // Idle breathing
203
+ if (!isWalking && !isSleeping) {
204
+ var breatheSpeed = S.conversationVelocity === -1 ? 1.2 : 2;
205
+ var breathe = 1 + Math.sin(time * breatheSpeed) * 0.02;
206
+ agent.parts.body.scale.y = breathe;
207
+ if (!agent.isSitting) {
208
+ agent.parts.head.rotation.z = Math.sin(time * 0.5) * 0.03;
209
+ }
210
+ }
211
+
212
+ // Sleep transition
213
+ var sleepTarget = isSleeping ? 1 : 0;
214
+ agent.sleepTransition += (sleepTarget - agent.sleepTransition) * Math.min(1, dt * (isSleeping ? 1 : 4));
215
+ agent.parts.head.rotation.x = agent.sleepTransition * 0.35;
216
+ agent.parts.body.rotation.x = agent.sleepTransition * 0.18;
217
+
218
+ // Wake-up bounce
219
+ if (agent.prevState === 'sleeping' && agent.state === 'active') {
220
+ if (agent.sleepTransition < 0.05) {
221
+ agent.prevState = null;
222
+ agent.parts.group.position.y = 0.08;
223
+ }
224
+ }
225
+
226
+ // ZZZ floating sprites
227
+ if (isSleeping && agent.sleepTransition > 0.5) {
228
+ if (!agent.zzzActive) {
229
+ agent.zzzActive = true;
230
+ agent.parts.zzzObjects.forEach(function(z) { z.obj.visible = true; });
231
+ }
232
+ agent.parts.zzzObjects.forEach(function(z) {
233
+ var phase = time * 1.5 + z.index * 1.2;
234
+ var cycleT = (phase % 3) / 3;
235
+ var yOff = cycleT * 0.5;
236
+ var xOff = Math.sin(phase * 2) * 0.08;
237
+ z.obj.position.set(0.2 + z.index * 0.1 + xOff, z.baseY + yOff, 0);
238
+ var opacity = cycleT < 0.2 ? cycleT / 0.2 : (cycleT > 0.7 ? (1 - cycleT) / 0.3 : 1);
239
+ z.div.style.opacity = String(Math.max(0, opacity));
240
+ });
241
+ } else if (agent.zzzActive) {
242
+ agent.zzzActive = false;
243
+ agent.parts.zzzObjects.forEach(function(z) {
244
+ z.obj.visible = false;
245
+ z.div.style.opacity = '0';
246
+ });
247
+ }
248
+
249
+ // Listening head-tilt
250
+ if (agent.isListening && !isWalking && !isSleeping) {
251
+ agent.parts.head.rotation.z = Math.sin(time * 1.5) * 0.08;
252
+ }
253
+
254
+ // Typing dots
255
+ var showTyping = agent.state === 'active' && !agent.isListening && !isWalking && !isSleeping && agent.registered && agent.isSitting;
256
+ agent.parts.typingLabel.visible = showTyping;
257
+
258
+ // Task indicator
259
+ var task = agent.currentTask;
260
+ if (agent.taskCelebration > 0) {
261
+ agent.taskCelebration -= dt;
262
+ agent.parts.taskLabel.visible = true;
263
+ agent.parts.taskDiv.className = 'office3d-task-indicator done';
264
+ agent.parts.taskDiv.textContent = '\u2714 Done!';
265
+ var bounceT = agent.taskCelebration / 2;
266
+ agent.parts.group.position.y += Math.abs(Math.sin(bounceT * Math.PI * 4)) * 0.05;
267
+ if (agent.taskCelebration <= 0) {
268
+ agent.parts.taskLabel.visible = false;
269
+ agent.taskCelebration = 0;
270
+ }
271
+ } else if (task) {
272
+ var taskStatus = task.status || '';
273
+ if (taskStatus === 'in_progress' || taskStatus === 'in-progress') {
274
+ agent.parts.taskLabel.visible = true;
275
+ agent.parts.taskDiv.className = 'office3d-task-indicator working';
276
+ agent.parts.taskDiv.textContent = '\u2699 Working';
277
+ } else if (taskStatus === 'blocked') {
278
+ agent.parts.taskLabel.visible = true;
279
+ agent.parts.taskDiv.className = 'office3d-task-indicator blocked';
280
+ agent.parts.taskDiv.textContent = '\u2757 Blocked';
281
+ } else {
282
+ agent.parts.taskLabel.visible = false;
283
+ }
284
+ } else {
285
+ agent.parts.taskLabel.visible = false;
286
+ }
287
+
288
+ // Monitor screen content
289
+ if (agent.registered && agent.isSitting && agent.state === 'active') {
290
+ agent.monitorTimer += dt;
291
+ if (agent.monitorTimer >= 0.5) {
292
+ agent.monitorTimer = 0;
293
+ updateMonitorScreen(agent.deskIdx, agent.name, time);
294
+ }
295
+ } else if (agent.registered && isSleeping) {
296
+ setMonitorDim(agent.deskIdx);
297
+ }
298
+
299
+ // Bubble timer
300
+ if (agent.bubbleTimer > 0) {
301
+ agent.bubbleTimer -= dt;
302
+ if (agent.bubbleTimer <= 1) {
303
+ agent.parts.bubbleDiv.style.opacity = String(Math.max(0, agent.bubbleTimer));
304
+ }
305
+ if (agent.bubbleTimer <= 0) {
306
+ agent.parts.bubbleDiv.style.display = 'none';
307
+ agent.bubbleTimer = 0;
308
+ }
309
+ }
310
+ }
311
+
312
+ function walkTo(agent, tx, tz, callback) {
313
+ var dx = tx - agent.pos.x;
314
+ var dz = tz - agent.pos.z;
315
+ var dist = Math.sqrt(dx * dx + dz * dz);
316
+ agent.walkStart = { x: agent.pos.x, z: agent.pos.z };
317
+ agent.target = { x: tx, z: tz, cb: callback || null };
318
+ agent.walkProgress = 0;
319
+ agent.walkDuration = Math.max(dist * 0.4, 0.3);
320
+ }
321
+
322
+ function disposeAgent(agent) {
323
+ agent.parts.group.traverse(function(child) {
324
+ if (child.geometry) child.geometry.dispose();
325
+ if (child.material) {
326
+ if (child.material.map) child.material.map.dispose();
327
+ child.material.dispose();
328
+ }
329
+ });
330
+ if (agent.parts.labelDiv.parentElement) agent.parts.labelDiv.remove();
331
+ if (agent.parts.bubbleDiv.parentElement) agent.parts.bubbleDiv.remove();
332
+ if (agent.parts.taskDiv && agent.parts.taskDiv.parentElement) agent.parts.taskDiv.remove();
333
+ if (agent.parts.typingDiv && agent.parts.typingDiv.parentElement) agent.parts.typingDiv.remove();
334
+ if (agent.parts.zzzObjects) {
335
+ agent.parts.zzzObjects.forEach(function(z) { if (z.div.parentElement) z.div.remove(); });
336
+ }
337
+ }
@@ -0,0 +1,56 @@
1
+ import * as THREE from 'three';
2
+ import { DEFAULT_APPEARANCE, AGENT_PALETTES } from './constants.js';
3
+
4
+ // Deterministic appearance resolution from agent name + stored appearance
5
+ export function resolveAppearance(name, appearance) {
6
+ var app = appearance || {};
7
+ var h = 0;
8
+ for (var i = 0; i < name.length; i++) h = ((h << 5) - h + name.charCodeAt(i)) | 0;
9
+ h = Math.abs(h);
10
+ var palette = AGENT_PALETTES[h % AGENT_PALETTES.length];
11
+
12
+ // Accessory pools — deterministic based on name hash
13
+ var glassesPool = [null, null, null, 'round', 'square', 'sunglasses', null, null];
14
+ var headwearPool = [null, null, null, null, 'headphones', 'beanie', 'cap', null, null, 'headband'];
15
+ var neckwearPool = [null, null, null, 'tie', 'bowtie', 'lanyard', null, null];
16
+ var accColorPool = ['#555555', '#8B4513', '#333333', '#c0392b', '#1a5276', '#7d3c98', '#2e4053'];
17
+
18
+ // Outfit and body type pools
19
+ var outfitPool = [null, null, null, null, 'hoodie', 'suit', 'jacket', 'vest', null, null, 'labcoat', null];
20
+ var bodyTypePool = ['default', 'default', 'default', 'stocky', 'slim', 'default', 'default'];
21
+ var eyePool = ['dots', 'dots', 'anime', 'confident', 'happy', 'dots', 'wink', 'dots'];
22
+ var mouthPool = ['smile', 'smile', 'neutral', 'grin', 'smile', 'smirk', 'smile'];
23
+
24
+ var resolved = {
25
+ head_color: app.head_color || DEFAULT_APPEARANCE.head_color,
26
+ hair_style: app.hair_style || ['short', 'spiky', 'bob', 'ponytail', 'curly', 'afro', 'bun', 'braids', 'mohawk', 'wavy', 'long'][h % 11],
27
+ hair_color: app.hair_color || palette.hair_color,
28
+ eye_style: app.eye_style || eyePool[h % eyePool.length],
29
+ mouth_style: app.mouth_style || mouthPool[(h >> 2) % mouthPool.length],
30
+ shirt_color: app.shirt_color || palette.shirt_color,
31
+ pants_color: app.pants_color || palette.pants_color,
32
+ shoe_color: app.shoe_color || DEFAULT_APPEARANCE.shoe_color,
33
+ glasses: app.glasses !== undefined ? app.glasses : glassesPool[(h >> 3) % glassesPool.length],
34
+ glasses_color: app.glasses_color || accColorPool[(h >> 4) % accColorPool.length],
35
+ headwear: app.headwear !== undefined ? app.headwear : headwearPool[(h >> 5) % headwearPool.length],
36
+ headwear_color: app.headwear_color || accColorPool[(h >> 6) % accColorPool.length],
37
+ neckwear: app.neckwear !== undefined ? app.neckwear : neckwearPool[(h >> 7) % neckwearPool.length],
38
+ neckwear_color: app.neckwear_color || accColorPool[(h >> 8) % accColorPool.length],
39
+ outfit: app.outfit !== undefined ? app.outfit : outfitPool[(h >> 9) % outfitPool.length],
40
+ body_type: app.body_type || bodyTypePool[(h >> 10) % bodyTypePool.length],
41
+ };
42
+
43
+ // Add Three.js hex values
44
+ resolved.head_hex = new THREE.Color(resolved.head_color).getHex();
45
+ resolved.shirt_hex = new THREE.Color(resolved.shirt_color).getHex();
46
+ resolved.pants_hex = new THREE.Color(resolved.pants_color).getHex();
47
+ resolved.shoe_hex = new THREE.Color(resolved.shoe_color).getHex();
48
+ resolved.hair_hex = new THREE.Color(resolved.hair_color).getHex();
49
+
50
+ return resolved;
51
+ }
52
+
53
+ // Expose for legacy callers (profile editor, 2D compat)
54
+ window.officeGetAppearance = function(agent) {
55
+ return resolveAppearance(agent.displayName || 'agent', agent.appearance || {});
56
+ };
@@ -0,0 +1,208 @@
1
+ import * as THREE from 'three';
2
+ import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
3
+ import { resolveAppearance } from './appearance.js';
4
+ import { buildHair } from './hair.js';
5
+ import { buildFaceSprite } from './face.js';
6
+ import { buildGlasses, buildHeadwear, buildNeckwear } from './accessories.js';
7
+ import { buildOutfit } from './outfits.js';
8
+
9
+ // Body type scale multipliers (all keep the chibi oversized head)
10
+ var BODY_TYPES = {
11
+ default: { torsoW: 1, torsoH: 1, torsoD: 1, legW: 1, legH: 1, armW: 1, armH: 1, legSpread: 1, armSpread: 1, headY: 0 },
12
+ stocky: { torsoW: 1.3, torsoH: 0.95, torsoD: 1.25, legW: 1.25, legH: 0.9, armW: 1.2, armH: 0.95, legSpread: 1.2, armSpread: 1.15, headY: -0.02 },
13
+ slim: { torsoW: 0.82, torsoH: 1.1, torsoD: 0.85, legW: 0.8, legH: 1.08, armW: 0.8, armH: 1.05, legSpread: 0.85, armSpread: 0.9, headY: 0.04 },
14
+ };
15
+
16
+ export function createCharacter(name, appearance) {
17
+ var a = resolveAppearance(name, appearance);
18
+ var bt = BODY_TYPES[a.body_type] || BODY_TYPES.default;
19
+ var group = new THREE.Group();
20
+ group.userData.agentName = name;
21
+
22
+ // Shadow
23
+ var shadowGeo = new THREE.PlaneGeometry(0.5, 0.5);
24
+ var shadowMat = new THREE.MeshBasicMaterial({ color: 0x000000, transparent: true, opacity: 0.25 });
25
+ var shadow = new THREE.Mesh(shadowGeo, shadowMat);
26
+ shadow.rotation.x = -Math.PI / 2;
27
+ shadow.position.y = 0.01;
28
+ shadow.userData.isShadow = true;
29
+ group.add(shadow);
30
+
31
+ // Materials
32
+ var bodyMat = new THREE.MeshStandardMaterial({ color: a.shirt_hex, roughness: 0.7 });
33
+ var legMat = new THREE.MeshStandardMaterial({ color: a.pants_hex, roughness: 0.7 });
34
+ var shoeMat = new THREE.MeshStandardMaterial({ color: a.shoe_hex, roughness: 0.6 });
35
+ var armMat = new THREE.MeshStandardMaterial({ color: a.shirt_hex, roughness: 0.7 });
36
+ var handMat = new THREE.MeshStandardMaterial({ color: a.head_hex, roughness: 0.7 });
37
+
38
+ // Torso (scaled by body type)
39
+ var body = new THREE.Mesh(new THREE.BoxGeometry(0.3 * bt.torsoW, 0.32 * bt.torsoH, 0.18 * bt.torsoD), bodyMat);
40
+ body.position.y = 0.58; body.castShadow = true;
41
+ group.add(body);
42
+
43
+ // Left Leg
44
+ var legXOffset = 0.08 * bt.legSpread;
45
+ var leftLeg = new THREE.Group();
46
+ leftLeg.position.set(-legXOffset, 0.42, 0);
47
+ group.add(leftLeg);
48
+ var leftUpperLeg = new THREE.Mesh(new THREE.BoxGeometry(0.1 * bt.legW, 0.18 * bt.legH, 0.1 * bt.legW), legMat);
49
+ leftUpperLeg.position.y = -0.09 * bt.legH; leftUpperLeg.castShadow = true;
50
+ leftLeg.add(leftUpperLeg);
51
+ var leftLowerLeg = new THREE.Group();
52
+ leftLowerLeg.position.set(0, -0.18 * bt.legH, 0);
53
+ leftLeg.add(leftLowerLeg);
54
+ var leftShin = new THREE.Mesh(new THREE.BoxGeometry(0.09 * bt.legW, 0.16 * bt.legH, 0.09 * bt.legW), legMat);
55
+ leftShin.position.y = -0.08 * bt.legH; leftShin.castShadow = true;
56
+ leftLowerLeg.add(leftShin);
57
+ var leftShoe = new THREE.Mesh(new THREE.BoxGeometry(0.1 * bt.legW, 0.05, 0.14), shoeMat);
58
+ leftShoe.position.set(0, -0.18 * bt.legH, 0.02); leftShoe.castShadow = true;
59
+ leftLowerLeg.add(leftShoe);
60
+
61
+ // Right Leg
62
+ var rightLeg = new THREE.Group();
63
+ rightLeg.position.set(legXOffset, 0.42, 0);
64
+ group.add(rightLeg);
65
+ var rightUpperLeg = new THREE.Mesh(new THREE.BoxGeometry(0.1 * bt.legW, 0.18 * bt.legH, 0.1 * bt.legW), legMat);
66
+ rightUpperLeg.position.y = -0.09 * bt.legH; rightUpperLeg.castShadow = true;
67
+ rightLeg.add(rightUpperLeg);
68
+ var rightLowerLeg = new THREE.Group();
69
+ rightLowerLeg.position.set(0, -0.18 * bt.legH, 0);
70
+ rightLeg.add(rightLowerLeg);
71
+ var rightShin = new THREE.Mesh(new THREE.BoxGeometry(0.09 * bt.legW, 0.16 * bt.legH, 0.09 * bt.legW), legMat);
72
+ rightShin.position.y = -0.08 * bt.legH; rightShin.castShadow = true;
73
+ rightLowerLeg.add(rightShin);
74
+ var rightShoe = new THREE.Mesh(new THREE.BoxGeometry(0.1 * bt.legW, 0.05, 0.14), shoeMat);
75
+ rightShoe.position.set(0, -0.18 * bt.legH, 0.02); rightShoe.castShadow = true;
76
+ rightLowerLeg.add(rightShoe);
77
+
78
+ // Left Arm
79
+ var armXOffset = 0.21 * bt.armSpread;
80
+ var leftArm = new THREE.Group();
81
+ leftArm.position.set(-armXOffset, 0.7, 0);
82
+ group.add(leftArm);
83
+ var leftUpperArm = new THREE.Mesh(new THREE.BoxGeometry(0.08 * bt.armW, 0.16 * bt.armH, 0.08 * bt.armW), armMat);
84
+ leftUpperArm.position.y = -0.08 * bt.armH; leftUpperArm.castShadow = true;
85
+ leftArm.add(leftUpperArm);
86
+ var leftForearm = new THREE.Group();
87
+ leftForearm.position.set(0, -0.16 * bt.armH, 0);
88
+ leftArm.add(leftForearm);
89
+ var leftForearmMesh = new THREE.Mesh(new THREE.BoxGeometry(0.07 * bt.armW, 0.14 * bt.armH, 0.07 * bt.armW), armMat);
90
+ leftForearmMesh.position.y = -0.07 * bt.armH; leftForearmMesh.castShadow = true;
91
+ leftForearm.add(leftForearmMesh);
92
+ var leftHand = new THREE.Mesh(new THREE.SphereGeometry(0.04, 8, 6), handMat);
93
+ leftHand.position.y = -0.16 * bt.armH;
94
+ leftForearm.add(leftHand);
95
+
96
+ // Right Arm
97
+ var rightArm = new THREE.Group();
98
+ rightArm.position.set(armXOffset, 0.7, 0);
99
+ group.add(rightArm);
100
+ var rightUpperArm = new THREE.Mesh(new THREE.BoxGeometry(0.08 * bt.armW, 0.16 * bt.armH, 0.08 * bt.armW), armMat);
101
+ rightUpperArm.position.y = -0.08 * bt.armH; rightUpperArm.castShadow = true;
102
+ rightArm.add(rightUpperArm);
103
+ var rightForearm = new THREE.Group();
104
+ rightForearm.position.set(0, -0.16 * bt.armH, 0);
105
+ rightArm.add(rightForearm);
106
+ var rightForearmMesh = new THREE.Mesh(new THREE.BoxGeometry(0.07 * bt.armW, 0.14 * bt.armH, 0.07 * bt.armW), armMat);
107
+ rightForearmMesh.position.y = -0.07 * bt.armH; rightForearmMesh.castShadow = true;
108
+ rightForearm.add(rightForearmMesh);
109
+ var rightHand = new THREE.Mesh(new THREE.SphereGeometry(0.04, 8, 6), handMat);
110
+ rightHand.position.y = -0.16 * bt.armH;
111
+ rightForearm.add(rightHand);
112
+
113
+ // Head (always same chibi size regardless of body type)
114
+ var headGeo = new THREE.SphereGeometry(0.25, 20, 16);
115
+ var headMat = new THREE.MeshStandardMaterial({ color: a.head_hex, roughness: 0.6 });
116
+ var head = new THREE.Mesh(headGeo, headMat);
117
+ head.position.y = 1.05 + bt.headY; head.castShadow = true;
118
+ group.add(head);
119
+
120
+ // Hair
121
+ var hairGroup = buildHair(a.hair_style, a.hair_hex);
122
+ hairGroup.position.y = 1.05 + bt.headY;
123
+ group.add(hairGroup);
124
+
125
+ // Face
126
+ var faceSprite = buildFaceSprite(a.eye_style, a.mouth_style, false);
127
+ faceSprite.position.set(0, 0, 0.251);
128
+ head.add(faceSprite);
129
+
130
+ // Outfit (layered on top of body)
131
+ var outfitGroup = null;
132
+ if (a.outfit) {
133
+ outfitGroup = buildOutfit(a.outfit, { shirt_color: a.shirt_color, pants_color: a.pants_color }, group);
134
+ }
135
+
136
+ // Accessories
137
+ if (a.glasses) buildGlasses(a.glasses, a.glasses_color || '#555555', head);
138
+ if (a.headwear) buildHeadwear(a.headwear, a.headwear_color || '#333333', head);
139
+ if (a.neckwear && !a.outfit) buildNeckwear(a.neckwear, a.neckwear_color || '#c0392b', group);
140
+
141
+ // Name label
142
+ var labelDiv = document.createElement('div');
143
+ labelDiv.className = 'office3d-label';
144
+ labelDiv.innerHTML = '<span class="office3d-label-name"></span><span class="office3d-label-dot"></span>';
145
+ var label = new CSS2DObject(labelDiv);
146
+ label.position.set(0, 1.55, 0);
147
+ group.add(label);
148
+
149
+ // Bubble
150
+ var bubbleDiv = document.createElement('div');
151
+ bubbleDiv.className = 'office3d-bubble';
152
+ bubbleDiv.style.display = 'none';
153
+ var bubble = new CSS2DObject(bubbleDiv);
154
+ bubble.position.set(0, 1.8, 0);
155
+ group.add(bubble);
156
+
157
+ // ZZZ sprites
158
+ var zzzObjects = [];
159
+ ['z', 'Z', 'Z'].forEach(function(letter, i) {
160
+ var zDiv = document.createElement('div');
161
+ zDiv.className = 'office3d-zzz';
162
+ zDiv.textContent = letter;
163
+ zDiv.style.fontSize = (10 + i * 4) + 'px';
164
+ var zObj = new CSS2DObject(zDiv);
165
+ zObj.position.set(0.15 + i * 0.1, 1.4 + i * 0.15, 0);
166
+ zObj.visible = false;
167
+ group.add(zObj);
168
+ zzzObjects.push({ obj: zObj, div: zDiv, baseY: 1.4 + i * 0.15, index: i });
169
+ });
170
+
171
+ // Task indicator
172
+ var taskDiv = document.createElement('div');
173
+ taskDiv.className = 'office3d-task-indicator working';
174
+ var taskLabel = new CSS2DObject(taskDiv);
175
+ taskLabel.position.set(0, 1.7, 0);
176
+ taskLabel.visible = false;
177
+ group.add(taskLabel);
178
+
179
+ // Typing dots
180
+ var typingDiv = document.createElement('div');
181
+ typingDiv.className = 'office3d-typing';
182
+ typingDiv.innerHTML = '<span class="office3d-typing-dot"></span><span class="office3d-typing-dot"></span><span class="office3d-typing-dot"></span>';
183
+ var typingLabel = new CSS2DObject(typingDiv);
184
+ typingLabel.position.set(0, 1.65, 0);
185
+ typingLabel.visible = false;
186
+ group.add(typingLabel);
187
+
188
+ return {
189
+ group: group,
190
+ body: body, head: head,
191
+ leftLeg: leftLeg, rightLeg: rightLeg,
192
+ leftLowerLeg: leftLowerLeg, rightLowerLeg: rightLowerLeg,
193
+ leftArm: leftArm, rightArm: rightArm,
194
+ leftForearm: leftForearm, rightForearm: rightForearm,
195
+ leftHand: leftHand, rightHand: rightHand,
196
+ leftShoe: leftShoe, rightShoe: rightShoe,
197
+ faceSprite: faceSprite, hairGroup: hairGroup,
198
+ outfitGroup: outfitGroup,
199
+ label: label, labelDiv: labelDiv,
200
+ bubble: bubble, bubbleDiv: bubbleDiv,
201
+ shadow: shadow,
202
+ bodyMat: bodyMat, legMat: legMat, headMat: headMat,
203
+ armMat: armMat, handMat: handMat, shoeMat: shoeMat,
204
+ zzzObjects: zzzObjects,
205
+ taskDiv: taskDiv, taskLabel: taskLabel,
206
+ typingDiv: typingDiv, typingLabel: typingLabel
207
+ };
208
+ }
@@ -0,0 +1,62 @@
1
+ export const FLOOR_W = 28;
2
+ export const FLOOR_D = 16;
3
+
4
+ export const DESK_POSITIONS = [
5
+ { x: -4.5, z: 1.5 }, { x: -1.5, z: 1.5 }, { x: 1.5, z: 1.5 }, { x: 4.5, z: 1.5 },
6
+ { x: -4.5, z: -1 }, { x: -1.5, z: -1 }, { x: 1.5, z: -1 }, { x: 4.5, z: -1 },
7
+ { x: -4.5, z: -3.5 },{ x: -1.5, z: -3.5 },{ x: 1.5, z: -3.5 },{ x: 4.5, z: -3.5 },
8
+ ];
9
+
10
+ export const RECEPTION_POS = { x: 0, z: 6 };
11
+ export const SPAWN_POS = { x: 0, z: 7.5 };
12
+
13
+ export const ENVS = {
14
+ modern: {
15
+ floor1: 0x2a2d35, floor2: 0x323640,
16
+ wall: 0x1e2028,
17
+ desk: 0x5a6a80, deskLegs: 0x4a5568,
18
+ chair: 0x374151, chairSeat: 0x2d3748,
19
+ accent: 0x58a6ff,
20
+ },
21
+ startup: {
22
+ floor1: 0x2c2520, floor2: 0x362f28,
23
+ wall: 0x1a1512,
24
+ desk: 0xA67B1D, deskLegs: 0x8B6914,
25
+ chair: 0x5a3e28, chairSeat: 0x3d2b1f,
26
+ accent: 0xf97316,
27
+ }
28
+ };
29
+
30
+ export const HEAD_R = 0.25;
31
+
32
+ // Dressing room — right wing
33
+ export const DRESSING_ROOM_POS = { x: 10, z: -1.5 }; // platform center
34
+ export const DRESSING_ROOM_ENTRANCE = { x: 7.5, z: -1.5 }; // walk target
35
+
36
+ // Rest area — right wing, further back
37
+ export const REST_AREA_POS = { x: 10, z: -5.5 }; // beanbag center
38
+ export const REST_AREA_ENTRANCE = { x: 7.5, z: -5.5 }; // walk target
39
+
40
+ export const DEFAULT_APPEARANCE = {
41
+ head_color: '#FFD5B8',
42
+ hair_style: 'short',
43
+ hair_color: '#4A3728',
44
+ eye_style: 'dots',
45
+ mouth_style: 'smile',
46
+ shirt_color: '#58a6ff',
47
+ pants_color: '#2d3748',
48
+ shoe_color: '#1a1a2e',
49
+ outfit: null,
50
+ body_type: 'default',
51
+ };
52
+
53
+ export const AGENT_PALETTES = [
54
+ { shirt_color: '#58a6ff', pants_color: '#2d3748', hair_color: '#4A3728' },
55
+ { shirt_color: '#f97316', pants_color: '#3d2b1f', hair_color: '#1a1a1a' },
56
+ { shirt_color: '#a855f7', pants_color: '#1e1b2e', hair_color: '#8B4513' },
57
+ { shirt_color: '#22c55e', pants_color: '#1a2e1a', hair_color: '#D4A574' },
58
+ { shirt_color: '#ef4444', pants_color: '#2e1a1a', hair_color: '#333' },
59
+ { shirt_color: '#eab308', pants_color: '#2e2a1a', hair_color: '#8B0000' },
60
+ { shirt_color: '#06b6d4', pants_color: '#1a2e2e', hair_color: '#F5DEB3' },
61
+ { shirt_color: '#ec4899', pants_color: '#2e1a28', hair_color: '#FFD700' },
62
+ ];