let-them-talk 4.2.0 → 4.3.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.
@@ -120,6 +120,56 @@ export function updateAgent(agent, dt, time) {
120
120
  agent.parts.head.rotation.x = -stPhase * 0.2;
121
121
  }
122
122
 
123
+ // Active typing animation — subtle hand/finger movement when working at desk
124
+ if (agent.isSitting && agent.state === 'active' && !agent.isListening && !isWalking && !isSleeping) {
125
+ var typeSpeed = 8 + Math.sin(time * 0.7 + agent.name.length) * 2; // vary per agent
126
+ var typeL = Math.sin(time * typeSpeed) * 0.06;
127
+ var typeR = Math.sin(time * typeSpeed + 1.5) * 0.06; // offset for alternating hands
128
+ agent.parts.leftForearm.rotation.x += typeL;
129
+ agent.parts.rightForearm.rotation.x += typeR;
130
+ // Subtle head bob while typing (looking at screen)
131
+ agent.parts.head.rotation.x += Math.sin(time * 1.2) * 0.015;
132
+ }
133
+
134
+ // Frustrated gesture — when task is blocked, head in hands periodically
135
+ if (agent.currentTask && agent.currentTask.status === 'blocked' && agent.isSitting && !isWalking) {
136
+ if (!agent.frustratedTimer) agent.frustratedTimer = 3 + Math.random() * 5;
137
+ agent.frustratedTimer -= dt;
138
+ if (agent.frustratedTimer <= 0 && agent.frustratedTimer > -2.5) {
139
+ // Head drops, arms come up to cradle head
140
+ var fT = Math.min(1, (-agent.frustratedTimer) / 0.5);
141
+ agent.parts.head.rotation.x += fT * 0.3;
142
+ agent.parts.leftArm.rotation.x = -fT * 1.2;
143
+ agent.parts.rightArm.rotation.x = -fT * 1.2;
144
+ agent.parts.leftForearm.rotation.x = -fT * 1.0;
145
+ agent.parts.rightForearm.rotation.x = -fT * 1.0;
146
+ }
147
+ if (agent.frustratedTimer < -2.5) {
148
+ agent.frustratedTimer = 8 + Math.random() * 10; // reset
149
+ }
150
+ } else {
151
+ agent.frustratedTimer = 0;
152
+ }
153
+
154
+ // Listening lean-forward — body leans toward screen when in listen mode
155
+ if (agent.isListening && agent.isSitting && !isWalking && !isSleeping) {
156
+ agent.parts.body.rotation.x += -0.08; // slight forward lean
157
+ agent.parts.head.rotation.x += -0.05; // looking up at screen
158
+ }
159
+
160
+ // Head nod when being talked to (another agent is visiting)
161
+ if (agent._listeningTo && !isWalking && !isSleeping) {
162
+ if (!agent._nodTimer) agent._nodTimer = 0;
163
+ agent._nodTimer += dt;
164
+ // Periodic nod: quick down-up every ~2s
165
+ var nodCycle = agent._nodTimer % 2.2;
166
+ if (nodCycle < 0.3) {
167
+ agent.parts.head.rotation.x += Math.sin(nodCycle / 0.3 * Math.PI) * 0.12;
168
+ }
169
+ } else {
170
+ agent._nodTimer = 0;
171
+ }
172
+
123
173
  // Idle gesture system — random gestures when sitting and idle
124
174
  if (!agent.idleGestureTimer) agent.idleGestureTimer = 5 + Math.random() * 10;
125
175
  if (agent.isSitting && agent.state === 'active' && !isWalking && !agent.isListening) {
@@ -205,6 +255,24 @@ export function updateAgent(agent, dt, time) {
205
255
  agent.parts.rightForearm.rotation.x *= 0.9;
206
256
  }
207
257
 
258
+ // Glance at nearby speaking agent — head turns slightly toward speaker
259
+ if (agent._glanceTarget && agent.isSitting && !isWalking && !isSleeping) {
260
+ if (!agent._glanceTimer) agent._glanceTimer = 0;
261
+ agent._glanceTimer += dt;
262
+ if (agent._glanceTimer < 2.5) {
263
+ var glanceT = Math.min(1, agent._glanceTimer / 0.4);
264
+ agent.parts.head.rotation.y = agent._glanceDirection * 0.25 * glanceT;
265
+ } else {
266
+ // Fade back
267
+ agent.parts.head.rotation.y *= 0.9;
268
+ if (Math.abs(agent.parts.head.rotation.y) < 0.01) {
269
+ agent._glanceTarget = null;
270
+ agent._glanceTimer = 0;
271
+ agent.parts.head.rotation.y = 0;
272
+ }
273
+ }
274
+ }
275
+
208
276
  // Idle breathing
209
277
  if (!isWalking && !isSleeping) {
210
278
  var breatheSpeed = S.conversationVelocity === -1 ? 1.2 : 2;
@@ -0,0 +1,431 @@
1
+ // Asset registry for the World Builder
2
+ // Each asset is a factory function that returns a THREE.Group
3
+ import * as THREE from 'three';
4
+
5
+ // ===================== ASSET DEFINITIONS =====================
6
+ // Each asset: { name, category, icon, width, depth, height, factory }
7
+ // width/depth are footprint in grid units (for snap), height is visual
8
+
9
+ export const ASSET_CATEGORIES = [
10
+ { id: 'structural', label: 'Structural', icon: 'S' },
11
+ { id: 'furniture', label: 'Furniture', icon: 'F' },
12
+ { id: 'decor', label: 'Decor', icon: 'D' },
13
+ { id: 'tech', label: 'Tech', icon: 'T' },
14
+ { id: 'lighting', label: 'Lighting', icon: 'L' },
15
+ ];
16
+
17
+ var _matCache = {};
18
+ function mat(color, opts) {
19
+ var key = color + JSON.stringify(opts || {});
20
+ if (!_matCache[key]) {
21
+ _matCache[key] = new THREE.MeshStandardMaterial(Object.assign({ color: color }, opts || {}));
22
+ }
23
+ return _matCache[key];
24
+ }
25
+
26
+ export const ASSETS = [
27
+ // ===== STRUCTURAL =====
28
+ {
29
+ id: 'wall',
30
+ name: 'Wall',
31
+ category: 'structural',
32
+ icon: 'W',
33
+ gridW: 2, gridD: 1, height: 3,
34
+ factory: function() {
35
+ var g = new THREE.Group();
36
+ var wall = new THREE.Mesh(new THREE.BoxGeometry(2, 3, 0.12), mat(0x2a2d35, { roughness: 0.8 }));
37
+ wall.position.y = 1.5;
38
+ wall.castShadow = true; wall.receiveShadow = true;
39
+ g.add(wall);
40
+ return g;
41
+ }
42
+ },
43
+ {
44
+ id: 'glass_wall',
45
+ name: 'Glass Wall',
46
+ category: 'structural',
47
+ icon: 'G',
48
+ gridW: 2, gridD: 1, height: 3,
49
+ factory: function() {
50
+ var g = new THREE.Group();
51
+ var glass = new THREE.Mesh(new THREE.BoxGeometry(2, 3, 0.06), mat(0xaaccee, { transparent: true, opacity: 0.25, roughness: 0.05 }));
52
+ glass.position.y = 1.5;
53
+ g.add(glass);
54
+ // Chrome frame top
55
+ var frame = new THREE.Mesh(new THREE.BoxGeometry(2.05, 0.04, 0.08), mat(0xcccccc, { metalness: 0.8, roughness: 0.2 }));
56
+ frame.position.y = 3;
57
+ g.add(frame);
58
+ return g;
59
+ }
60
+ },
61
+ {
62
+ id: 'floor_tile',
63
+ name: 'Floor Tile',
64
+ category: 'structural',
65
+ icon: '\u2B1B',
66
+ gridW: 2, gridD: 2, height: 0.02,
67
+ factory: function() {
68
+ var g = new THREE.Group();
69
+ var tile = new THREE.Mesh(new THREE.BoxGeometry(2, 0.02, 2), mat(0x3a3d45, { roughness: 0.85 }));
70
+ tile.position.y = 0.01;
71
+ tile.receiveShadow = true;
72
+ g.add(tile);
73
+ return g;
74
+ }
75
+ },
76
+ {
77
+ id: 'window',
78
+ name: 'Window',
79
+ category: 'structural',
80
+ icon: 'G',
81
+ gridW: 2, gridD: 1, height: 2,
82
+ factory: function() {
83
+ var g = new THREE.Group();
84
+ var glass = new THREE.Mesh(new THREE.PlaneGeometry(1.8, 2), mat(0x87CEEB, { emissive: 0x87CEEB, emissiveIntensity: 0.15, transparent: true, opacity: 0.6 }));
85
+ glass.position.y = 2;
86
+ g.add(glass);
87
+ // Frame
88
+ var frameM = mat(0xcccccc, { metalness: 0.8, roughness: 0.2 });
89
+ var top = new THREE.Mesh(new THREE.BoxGeometry(1.9, 0.04, 0.04), frameM);
90
+ top.position.y = 3; g.add(top);
91
+ var bot = new THREE.Mesh(new THREE.BoxGeometry(1.9, 0.04, 0.04), frameM);
92
+ bot.position.y = 1; g.add(bot);
93
+ return g;
94
+ }
95
+ },
96
+ {
97
+ id: 'door',
98
+ name: 'Door',
99
+ category: 'structural',
100
+ icon: 'Dr',
101
+ gridW: 1, gridD: 1, height: 2.5,
102
+ factory: function() {
103
+ var g = new THREE.Group();
104
+ var door = new THREE.Mesh(new THREE.BoxGeometry(1, 2.5, 0.08), mat(0x5c3a1e, { roughness: 0.6 }));
105
+ door.position.y = 1.25;
106
+ door.castShadow = true;
107
+ g.add(door);
108
+ // Handle
109
+ var handle = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.15, 0.06), mat(0xcccccc, { metalness: 0.8 }));
110
+ handle.position.set(0.35, 1.1, 0.06);
111
+ g.add(handle);
112
+ return g;
113
+ }
114
+ },
115
+
116
+ // ===== FURNITURE =====
117
+ {
118
+ id: 'desk',
119
+ name: 'Desk',
120
+ category: 'furniture',
121
+ icon: 'Dk',
122
+ gridW: 2, gridD: 1, height: 0.8,
123
+ factory: function() {
124
+ var g = new THREE.Group();
125
+ var deskMat = mat(0x1a1a2e, { roughness: 0.3, metalness: 0.1 });
126
+ var legMat = mat(0x111111, { roughness: 0.4, metalness: 0.2 });
127
+ // Top
128
+ var top = new THREE.Mesh(new THREE.BoxGeometry(2, 0.05, 0.9), deskMat);
129
+ top.position.y = 0.76; top.castShadow = true;
130
+ g.add(top);
131
+ // Legs
132
+ var positions = [[-0.85, -0.35], [-0.85, 0.35], [0.85, -0.35], [0.85, 0.35]];
133
+ for (var i = 0; i < 4; i++) {
134
+ var leg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.76, 0.06), legMat);
135
+ leg.position.set(positions[i][0], 0.38, positions[i][1]);
136
+ g.add(leg);
137
+ }
138
+ return g;
139
+ }
140
+ },
141
+ {
142
+ id: 'chair',
143
+ name: 'Chair',
144
+ category: 'furniture',
145
+ icon: 'Ch',
146
+ gridW: 1, gridD: 1, height: 1.1,
147
+ factory: function() {
148
+ var g = new THREE.Group();
149
+ var seatM = mat(0x333340, { roughness: 0.65 });
150
+ var chromeM = mat(0x888888, { metalness: 0.6, roughness: 0.3 });
151
+ // Seat
152
+ var seat = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.05, 0.4), seatM);
153
+ seat.position.y = 0.45; g.add(seat);
154
+ // Back
155
+ var back = new THREE.Mesh(new THREE.BoxGeometry(0.38, 0.4, 0.04), seatM);
156
+ back.position.set(0, 0.7, 0.18); g.add(back);
157
+ // Post
158
+ var post = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 0.4, 6), chromeM);
159
+ post.position.y = 0.22; g.add(post);
160
+ // Base
161
+ var base = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.17, 0.03, 12), chromeM);
162
+ base.position.y = 0.02; g.add(base);
163
+ return g;
164
+ }
165
+ },
166
+ {
167
+ id: 'sofa',
168
+ name: 'Sofa',
169
+ category: 'furniture',
170
+ icon: 'So',
171
+ gridW: 3, gridD: 1, height: 0.9,
172
+ factory: function() {
173
+ var g = new THREE.Group();
174
+ var sofaM = mat(0x2a2a3e, { roughness: 0.75 });
175
+ // Base
176
+ var base = new THREE.Mesh(new THREE.BoxGeometry(3, 0.35, 0.9), sofaM);
177
+ base.position.y = 0.2; g.add(base);
178
+ // Back
179
+ var back = new THREE.Mesh(new THREE.BoxGeometry(3, 0.5, 0.2), sofaM);
180
+ back.position.set(0, 0.55, -0.35); g.add(back);
181
+ // Arms
182
+ var armL = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 0.9), sofaM);
183
+ armL.position.set(-1.4, 0.4, 0); g.add(armL);
184
+ var armR = new THREE.Mesh(new THREE.BoxGeometry(0.2, 0.3, 0.9), sofaM);
185
+ armR.position.set(1.4, 0.4, 0); g.add(armR);
186
+ return g;
187
+ }
188
+ },
189
+ {
190
+ id: 'bookshelf',
191
+ name: 'Bookshelf',
192
+ category: 'furniture',
193
+ icon: 'Bk',
194
+ gridW: 1, gridD: 1, height: 2.2,
195
+ factory: function() {
196
+ var g = new THREE.Group();
197
+ var woodM = mat(0x5a3e28, { roughness: 0.7 });
198
+ // Back
199
+ var back = new THREE.Mesh(new THREE.BoxGeometry(0.08, 2.2, 1.2), woodM);
200
+ back.position.y = 1.1; g.add(back);
201
+ // Sides
202
+ var sideL = new THREE.Mesh(new THREE.BoxGeometry(0.35, 2.2, 0.04), woodM);
203
+ sideL.position.set(0.14, 1.1, -0.58); g.add(sideL);
204
+ var sideR = new THREE.Mesh(new THREE.BoxGeometry(0.35, 2.2, 0.04), woodM);
205
+ sideR.position.set(0.14, 1.1, 0.58); g.add(sideR);
206
+ // Shelves
207
+ for (var i = 0; i < 5; i++) {
208
+ var shelf = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.04, 1.2), woodM);
209
+ shelf.position.set(0.14, 0.05 + i * 0.55, 0);
210
+ g.add(shelf);
211
+ }
212
+ return g;
213
+ }
214
+ },
215
+ {
216
+ id: 'coffee_table',
217
+ name: 'Coffee Table',
218
+ category: 'furniture',
219
+ icon: '\u2615',
220
+ gridW: 1, gridD: 1, height: 0.45,
221
+ factory: function() {
222
+ var g = new THREE.Group();
223
+ var glassM = mat(0xccddee, { transparent: true, opacity: 0.35, roughness: 0.05 });
224
+ var chromeM = mat(0xcccccc, { metalness: 0.8, roughness: 0.2 });
225
+ var top = new THREE.Mesh(new THREE.BoxGeometry(1, 0.03, 0.5), glassM);
226
+ top.position.y = 0.45; g.add(top);
227
+ for (var i = 0; i < 4; i++) {
228
+ var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.42, 6), chromeM);
229
+ leg.position.set(((i & 1) ? 0.4 : -0.4), 0.21, ((i & 2) ? 0.2 : -0.2));
230
+ g.add(leg);
231
+ }
232
+ return g;
233
+ }
234
+ },
235
+ {
236
+ id: 'bar_stool',
237
+ name: 'Bar Stool',
238
+ category: 'furniture',
239
+ icon: 'Ch',
240
+ gridW: 1, gridD: 1, height: 0.8,
241
+ factory: function() {
242
+ var g = new THREE.Group();
243
+ var seatM = mat(0x333340, { roughness: 0.65 });
244
+ var chromeM = mat(0x888888, { metalness: 0.6, roughness: 0.3 });
245
+ var seat = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.18, 0.06, 12), seatM);
246
+ seat.position.y = 0.75; g.add(seat);
247
+ var post = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.7, 8), chromeM);
248
+ post.position.y = 0.38; g.add(post);
249
+ var base = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.2, 0.04, 12), chromeM);
250
+ base.position.y = 0.04; g.add(base);
251
+ return g;
252
+ }
253
+ },
254
+
255
+ // ===== DECOR =====
256
+ {
257
+ id: 'plant',
258
+ name: 'Plant',
259
+ category: 'decor',
260
+ icon: 'Pl',
261
+ gridW: 1, gridD: 1, height: 0.8,
262
+ factory: function() {
263
+ var g = new THREE.Group();
264
+ var potM = mat(0x4a4a5a, { roughness: 0.8 });
265
+ var leafM = mat(0x2d8a4e, { roughness: 0.8 });
266
+ var pot = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.2, 0.5, 12), potM);
267
+ pot.position.y = 0.25; g.add(pot);
268
+ for (var i = 0; i < 5; i++) {
269
+ var angle = (i / 5) * Math.PI * 2;
270
+ var leaf = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 6), leafM);
271
+ leaf.position.set(Math.cos(angle) * 0.12, 0.6, Math.sin(angle) * 0.12);
272
+ leaf.scale.set(1, 0.7, 1);
273
+ g.add(leaf);
274
+ }
275
+ var topLeaf = new THREE.Mesh(new THREE.SphereGeometry(0.12, 8, 6), leafM);
276
+ topLeaf.position.y = 0.75; g.add(topLeaf);
277
+ return g;
278
+ }
279
+ },
280
+ {
281
+ id: 'indoor_tree',
282
+ name: 'Indoor Tree',
283
+ category: 'decor',
284
+ icon: 'Tr',
285
+ gridW: 1, gridD: 1, height: 3.5,
286
+ factory: function() {
287
+ var g = new THREE.Group();
288
+ var planterM = mat(0x3a3a4a, { roughness: 0.8 });
289
+ var trunkM = mat(0x5c3a1e, { roughness: 0.8 });
290
+ var leafM = mat(0x228B22, { roughness: 0.7 });
291
+ var planter = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.35, 0.6, 12), planterM);
292
+ planter.position.y = 0.3; g.add(planter);
293
+ var trunk = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.1, 2.5, 8), trunkM);
294
+ trunk.position.y = 1.85; g.add(trunk);
295
+ var sizes = [0.6, 0.5, 0.35];
296
+ var ys = [2.8, 3.2, 3.5];
297
+ for (var i = 0; i < 3; i++) {
298
+ var canopy = new THREE.Mesh(new THREE.SphereGeometry(sizes[i], 12, 10), leafM);
299
+ canopy.position.y = ys[i]; g.add(canopy);
300
+ }
301
+ return g;
302
+ }
303
+ },
304
+ {
305
+ id: 'beanbag',
306
+ name: 'Beanbag',
307
+ category: 'decor',
308
+ icon: 'So',
309
+ gridW: 1, gridD: 1, height: 0.5,
310
+ factory: function() {
311
+ var g = new THREE.Group();
312
+ var colors = [0xe53e3e, 0x3b82f6, 0x22c55e, 0xa855f7];
313
+ var color = colors[Math.floor(Math.random() * colors.length)];
314
+ var botM = mat(color, { roughness: 0.9 });
315
+ var bot = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 12), botM);
316
+ bot.position.y = 0.2; bot.scale.set(1, 0.5, 1); g.add(bot);
317
+ var darkColor = (color & 0xfefefe) >> 1; // darken
318
+ var topM = new THREE.MeshStandardMaterial({ color: darkColor, roughness: 0.9 });
319
+ var topBag = new THREE.Mesh(new THREE.SphereGeometry(0.35, 16, 12), topM);
320
+ topBag.position.set(-0.05, 0.4, 0); topBag.scale.set(1, 0.6, 1); g.add(topBag);
321
+ return g;
322
+ }
323
+ },
324
+
325
+ // ===== TECH =====
326
+ {
327
+ id: 'monitor',
328
+ name: 'Monitor',
329
+ category: 'tech',
330
+ icon: 'Mo',
331
+ gridW: 1, gridD: 1, height: 0.5,
332
+ factory: function() {
333
+ var g = new THREE.Group();
334
+ var body = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.35, 0.04), mat(0x1a1a2e, { roughness: 0.3 }));
335
+ body.position.y = 0.35; g.add(body);
336
+ var screen = new THREE.Mesh(new THREE.PlaneGeometry(0.44, 0.28), mat(0x333333, { emissive: 0x111122, emissiveIntensity: 0.3 }));
337
+ screen.position.set(0, 0.35, 0.025); g.add(screen);
338
+ var stand = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.18, 0.06), mat(0x4a5568, { roughness: 0.7 }));
339
+ stand.position.y = 0.09; g.add(stand);
340
+ return g;
341
+ }
342
+ },
343
+ {
344
+ id: 'pc_tower',
345
+ name: 'PC Tower',
346
+ category: 'tech',
347
+ icon: 'PC',
348
+ gridW: 1, gridD: 1, height: 0.45,
349
+ factory: function() {
350
+ var g = new THREE.Group();
351
+ var caseMesh = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.45, 0.45), mat(0x111111, { roughness: 0.4 }));
352
+ caseMesh.position.y = 0.23; g.add(caseMesh);
353
+ var panel = new THREE.Mesh(new THREE.PlaneGeometry(0.18, 0.4), mat(0x58a6ff, { emissive: 0x58a6ff, emissiveIntensity: 0.3, transparent: true, opacity: 0.4 }));
354
+ panel.position.set(0.115, 0.23, 0); panel.rotation.y = Math.PI / 2; g.add(panel);
355
+ return g;
356
+ }
357
+ },
358
+
359
+ // ===== LIGHTING =====
360
+ {
361
+ id: 'floor_lamp',
362
+ name: 'Floor Lamp',
363
+ category: 'lighting',
364
+ icon: 'Lt',
365
+ gridW: 1, gridD: 1, height: 1.8,
366
+ factory: function() {
367
+ var g = new THREE.Group();
368
+ var metalM = mat(0x333333, { metalness: 0.3, roughness: 0.5 });
369
+ var base = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.18, 0.04, 12), metalM);
370
+ base.position.y = 0.02; g.add(base);
371
+ var pole = new THREE.Mesh(new THREE.CylinderGeometry(0.02, 0.02, 1.6, 8), metalM);
372
+ pole.position.y = 0.84; g.add(pole);
373
+ var shade = new THREE.Mesh(new THREE.ConeGeometry(0.18, 0.25, 12, 1, true), mat(0xddd5c0, { roughness: 0.8, side: THREE.DoubleSide }));
374
+ shade.position.y = 1.72; shade.rotation.x = Math.PI; g.add(shade);
375
+ var light = new THREE.PointLight(0xffeedd, 0.3, 4);
376
+ light.position.y = 1.6; g.add(light);
377
+ return g;
378
+ }
379
+ },
380
+ {
381
+ id: 'pendant_light',
382
+ name: 'Pendant Light',
383
+ category: 'lighting',
384
+ icon: 'Lt',
385
+ gridW: 1, gridD: 1, height: 2,
386
+ factory: function() {
387
+ var g = new THREE.Group();
388
+ var wire = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.008, 1.5, 4), mat(0x333333, { roughness: 0.5 }));
389
+ wire.position.y = 2.25; g.add(wire);
390
+ var shade = new THREE.Mesh(new THREE.SphereGeometry(0.12, 12, 10), mat(0xffeedd, { emissive: 0xffeedd, emissiveIntensity: 0.4, transparent: true, opacity: 0.8 }));
391
+ shade.position.y = 1.5; g.add(shade);
392
+ var light = new THREE.PointLight(0xffeedd, 0.25, 6);
393
+ light.position.y = 1.4; g.add(light);
394
+ return g;
395
+ }
396
+ },
397
+ ];
398
+
399
+ // Get asset by ID
400
+ export function getAsset(id) {
401
+ for (var i = 0; i < ASSETS.length; i++) {
402
+ if (ASSETS[i].id === id) return ASSETS[i];
403
+ }
404
+ return null;
405
+ }
406
+
407
+ // Get assets by category
408
+ export function getAssetsByCategory(cat) {
409
+ return ASSETS.filter(function(a) { return a.category === cat; });
410
+ }
411
+
412
+ // Create a ghost (transparent preview) of an asset
413
+ export function createGhost(assetId) {
414
+ var asset = getAsset(assetId);
415
+ if (!asset) return null;
416
+ var group = asset.factory();
417
+ // Make all children transparent green
418
+ group.traverse(function(child) {
419
+ if (child.isMesh) {
420
+ child.material = new THREE.MeshStandardMaterial({
421
+ color: 0x44ff88,
422
+ transparent: true,
423
+ opacity: 0.35,
424
+ depthWrite: false,
425
+ });
426
+ }
427
+ });
428
+ group.userData.isGhost = true;
429
+ group.userData.assetId = assetId;
430
+ return group;
431
+ }