morfeo 0.6.3 → 0.6.5

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,620 @@
1
+ // Piezas de ingeniería hechas en código: plomería, electricidad, electrónica y mecánica. Son las que no existen en
2
+ // las bibliotecas libres (una válvula, un interruptor, una placa madre) y hacen falta para explicar cómo funciona
3
+ // una casa, una ciudad o una consola.
4
+ // {tipo: 'modelo', forma: 'valvula'} {tipo: 'recurso', buscar: 'placa madre'} (el buscador las encuentra)
5
+ // Cada pieza mide ~2 unidades, viene centrada y solo depende de `t` (las que giran: engrane, turbina, ventiladores).
6
+ import React, {useMemo} from 'react';
7
+ import * as THREE from 'three';
8
+
9
+ export const OBJETOS_INGENIERIA = [
10
+ // plomería
11
+ 'valvula', 'grifo', 'tinaco', 'calentador', 'bomba', 'alcantarilla', 'codo',
12
+ // electricidad
13
+ 'interruptor', 'enchufe', 'tablero', 'medidor', 'transformador', 'poste', 'bateria', 'turbina', 'torre_electrica', 'aire',
14
+ // electrónica
15
+ 'chip', 'placa', 'resistor', 'capacitor', 'led', 'disco_duro', 'tarjeta_grafica', 'disipador', 'ram', 'router', 'servidor', 'fuente_poder',
16
+ // mecánica
17
+ 'engrane', 'tornillo', 'tuerca', 'motor', 'resorte', 'piston',
18
+ ] as const;
19
+
20
+ export type V3 = [number, number, number];
21
+ export type M = {color?: string; metal?: number; rugosidad?: number};
22
+ export type P = {p?: V3; c: string; m?: number; r?: number; e?: number; rot?: V3; o?: number};
23
+ export const Mat: React.FC<{c: string; m?: number; r?: number; e?: number; o?: number}> = ({c, m = 0.1, r = 0.5, e = 0, o}) => (
24
+ <meshStandardMaterial color={c} metalness={m} roughness={r} emissive={c} emissiveIntensity={e} transparent={o != null} opacity={o ?? 1} />
25
+ );
26
+ /** Caja. `s` = [ancho, alto, fondo]. */
27
+ export const C: React.FC<P & {s: V3}> = ({p = [0, 0, 0], s, rot, ...mat}) => (
28
+ <mesh position={p} rotation={rot}>
29
+ <boxGeometry args={s} />
30
+ <Mat {...mat} />
31
+ </mesh>
32
+ );
33
+ /** Cilindro de pie (eje Y). `eje: 'x' | 'z'` lo acuesta. */
34
+ export const Y: React.FC<P & {rad: number | [number, number]; h: number; seg?: number; eje?: 'x' | 'y' | 'z'}> = ({p = [0, 0, 0], rad, h, seg = 28, eje = 'y', rot, ...mat}) => (
35
+ <mesh position={p} rotation={rot ?? (eje === 'x' ? [0, 0, Math.PI / 2] : eje === 'z' ? [Math.PI / 2, 0, 0] : [0, 0, 0])}>
36
+ <cylinderGeometry args={[...(Array.isArray(rad) ? rad : [rad, rad]), h, seg] as [number, number, number, number]} />
37
+ <Mat {...mat} />
38
+ </mesh>
39
+ );
40
+ export const E: React.FC<P & {rad: number}> = ({p = [0, 0, 0], rad, ...mat}) => (
41
+ <mesh position={p}>
42
+ <sphereGeometry args={[rad, 24, 16]} />
43
+ <Mat {...mat} />
44
+ </mesh>
45
+ );
46
+ /** Aro (dona). Por defecto mira hacia +z. */
47
+ export const Aro: React.FC<P & {rad: number; tubo: number; arco?: number}> = ({p = [0, 0, 0], rad, tubo, arco = Math.PI * 2, rot, ...mat}) => (
48
+ <mesh position={p} rotation={rot}>
49
+ <torusGeometry args={[rad, tubo, 12, 36, arco]} />
50
+ <Mat {...mat} />
51
+ </mesh>
52
+ );
53
+
54
+ export const ACERO = {c: '#AEB6C2', m: 0.85, r: 0.3};
55
+ export const LATON = {c: '#C9A24B', m: 0.85, r: 0.3};
56
+ export const COBRE = {c: '#C8733A', m: 0.85, r: 0.3};
57
+ export const NEGRO = {c: '#1B1C22', r: 0.55};
58
+ export const PCB = {c: '#1F7A4D', r: 0.55};
59
+ export const ORO = {c: '#E0B64A', m: 0.9, r: 0.25};
60
+
61
+ /** Rueda dentada: `n` dientes, radio exterior `ext`. */
62
+ const figuraEngrane = (n: number, ext: number, int: number) => {
63
+ const s = new THREE.Shape();
64
+ const pasos = n * 4;
65
+ for (let i = 0; i < pasos; i++) {
66
+ const a = (i / pasos) * Math.PI * 2;
67
+ const r = i % 4 < 2 ? ext : int;
68
+ if (i === 0) s.moveTo(Math.cos(a) * r, Math.sin(a) * r);
69
+ else s.lineTo(Math.cos(a) * r, Math.sin(a) * r);
70
+ }
71
+ s.closePath();
72
+ const hoyo = new THREE.Path();
73
+ hoyo.absarc(0, 0, ext * 0.22, 0, Math.PI * 2, true);
74
+ s.holes.push(hoyo);
75
+ return s;
76
+ };
77
+ export const Engrane: React.FC<{n?: number; ext?: number; prof?: number; giro?: number} & P> = ({n = 12, ext = 1, prof = 0.3, giro = 0, p = [0, 0, 0], ...mat}) => {
78
+ const geo = useMemo(() => {
79
+ const g = new THREE.ExtrudeGeometry(figuraEngrane(n, ext, ext * 0.8), {depth: prof, bevelEnabled: true, bevelThickness: 0.03, bevelSize: 0.03, bevelSegments: 2, curveSegments: 20});
80
+ g.center();
81
+ return g;
82
+ }, [n, ext, prof]);
83
+ return (
84
+ <mesh geometry={geo} position={p} rotation={[0, 0, giro]}>
85
+ <Mat {...mat} />
86
+ </mesh>
87
+ );
88
+ };
89
+ /** Prisma de 6 lados (tuercas, cabezas de tornillo). */
90
+ export const Hex: React.FC<P & {rad: number; h: number}> = ({rad, h, ...r}) => <Y rad={rad} h={h} seg={6} {...r} />;
91
+ /** Aspas en el plano XY. */
92
+ export const Aspas: React.FC<{n: number; rad: number; giro: number; c: string; p?: V3; ancho?: number}> = ({n, rad, giro, c, p = [0, 0, 0], ancho = 0.3}) => (
93
+ <group position={p} rotation={[0, 0, giro]}>
94
+ {Array.from({length: n}, (_, k) => (
95
+ <group key={k} rotation={[0, 0, (k * Math.PI * 2) / n]}>
96
+ <C p={[rad / 2, 0, 0]} s={[rad, rad * ancho, 0.03]} rot={[0.35, 0, 0]} c={c} r={0.5} />
97
+ </group>
98
+ ))}
99
+ </group>
100
+ );
101
+ /** Tubo que sigue una curva. */
102
+ export const Tubo: React.FC<{pts: V3[]; rad: number} & P> = ({pts, rad, p = [0, 0, 0], ...mat}) => {
103
+ const geo = useMemo(() => new THREE.TubeGeometry(new THREE.CatmullRomCurve3(pts.map((q) => new THREE.Vector3(...q))), 60, rad, 14, false), [pts, rad]);
104
+ return (
105
+ <mesh geometry={geo} position={p}>
106
+ <Mat {...mat} />
107
+ </mesh>
108
+ );
109
+ };
110
+ /** Resorte: hélice de `vueltas` vueltas. */
111
+ export const Helice: React.FC<{vueltas: number; rad: number; alto: number; tubo: number} & P> = ({vueltas, rad, alto, tubo, ...mat}) => {
112
+ const pts = useMemo(() => Array.from({length: vueltas * 16 + 1}, (_, i) => [Math.cos((i / 16) * Math.PI * 2) * rad, -alto / 2 + (i / (vueltas * 16)) * alto, Math.sin((i / 16) * Math.PI * 2) * rad] as V3), [vueltas, rad, alto]);
113
+ return <Tubo pts={pts} rad={tubo} {...mat} />;
114
+ };
115
+
116
+ export const ObjetoIngenieria: React.FC<{forma: string; t: number; opciones?: Record<string, any>} & M> = ({forma, t, opciones: o = {}, color}) => {
117
+ const giro = t * (o.velocidad ?? 1) * Math.PI * 2;
118
+ // `color` cambia el color principal de la pieza (el cuerpo); los detalles conservan el suyo
119
+ const k = (c: string) => color ?? c;
120
+ switch (forma) {
121
+ // ------------------------------------------------------------ plomería
122
+ case 'valvula':
123
+ return (
124
+ <group>
125
+ <Y eje="x" rad={0.3} h={2.2} {...LATON} />
126
+ <Y eje="x" p={[-1.05, 0, 0]} rad={0.4} h={0.14} {...LATON} />
127
+ <Y eje="x" p={[1.05, 0, 0]} rad={0.4} h={0.14} {...LATON} />
128
+ <E rad={0.5} {...LATON} />
129
+ <Y p={[0, 0.62, 0]} rad={0.09} h={0.5} {...ACERO} />
130
+ <Aro p={[0, 0.9, 0]} rot={[Math.PI / 2, 0, giro * (o.abrir ?? 0)]} rad={0.5} tubo={0.07} c={k('#D7372F')} r={0.4} />
131
+ {[0, 1].map((i) => (
132
+ <C key={i} p={[0, 0.9, 0]} rot={[0, (i * Math.PI) / 2, 0]} s={[1, 0.06, 0.09]} c={k('#D7372F')} r={0.4} />
133
+ ))}
134
+ </group>
135
+ );
136
+ case 'grifo':
137
+ return (
138
+ <group position={[-0.15, -0.2, 0]}>
139
+ <Y p={[-0.2, -0.5, 0]} rad={[0.2, 0.26]} h={0.7} {...ACERO} c={k(ACERO.c)} />
140
+ <Tubo pts={[[-0.2, -0.15, 0], [-0.2, 0.75, 0], [0.2, 1.05, 0], [0.75, 0.9, 0], [0.85, 0.45, 0]]} rad={0.11} {...ACERO} c={k(ACERO.c)} />
141
+ <Y p={[0.85, 0.4, 0]} rad={0.14} h={0.14} {...ACERO} />
142
+ <C p={[-0.2, -0.05, 0.33]} s={[0.12, 0.1, 0.5]} {...ACERO} />
143
+ <E p={[-0.2, -0.05, 0.6]} rad={0.1} c="#2F7DD1" r={0.3} />
144
+ {o.gota !== false && <E p={[0.85, 0.2 - ((t * 1.2) % 1) * 0.9, 0]} rad={0.07} c="#4FB3FF" r={0.1} o={0.85} />}
145
+ </group>
146
+ );
147
+ case 'tinaco':
148
+ return (
149
+ <group>
150
+ <Y p={[0, -0.15, 0]} rad={[0.78, 0.7]} h={1.5} c={k('#1F1F24')} r={0.6} seg={36} />
151
+ {[-0.5, -0.15, 0.2].map((y) => (
152
+ <Aro key={y} p={[0, y, 0]} rot={[Math.PI / 2, 0, 0]} rad={0.76 - (0.2 - y) * 0.04} tubo={0.03} c="#2C2C33" r={0.6} />
153
+ ))}
154
+ <Y p={[0, 0.75, 0]} rad={[0.42, 0.78]} h={0.32} c={k('#1F1F24')} r={0.6} seg={36} />
155
+ <Y p={[0, 0.96, 0]} rad={0.42} h={0.12} c="#2C2C33" r={0.5} />
156
+ <Y eje="x" p={[0.95, -0.7, 0]} rad={0.07} h={0.5} c="#D9D9D9" r={0.5} />
157
+ </group>
158
+ );
159
+ case 'calentador':
160
+ return (
161
+ <group>
162
+ <Y rad={0.6} h={1.9} c={k('#E9E6E0')} r={0.45} seg={36} />
163
+ <Y p={[0, 0.98, 0]} rad={[0.3, 0.6]} h={0.12} c="#CFCBC3" r={0.5} />
164
+ <Y p={[0, -0.98, 0]} rad={0.56} h={0.1} c="#3A3A40" r={0.6} />
165
+ <Y p={[-0.25, 1.25, 0]} rad={0.07} h={0.5} {...COBRE} />
166
+ <Y p={[0.25, 1.25, 0]} rad={0.07} h={0.5} {...COBRE} />
167
+ <C p={[0, -0.45, 0.58]} s={[0.4, 0.3, 0.1]} c="#2A2A30" r={0.5} />
168
+ <E p={[0, -0.45, 0.64]} rad={0.07} c="#FF7A1A" e={0.9} />
169
+ <C p={[0, 0.3, 0.6]} s={[0.36, 0.2, 0.04]} c="#C73A32" r={0.5} />
170
+ </group>
171
+ );
172
+ case 'bomba':
173
+ return (
174
+ <group position={[0.1, 0, 0]}>
175
+ <Y eje="x" p={[0.35, 0, 0]} rad={0.5} h={1.2} c={k('#2F6DB5')} r={0.45} m={0.3} />
176
+ {[-0.1, 0.15, 0.4, 0.65].map((x) => (
177
+ <Y key={x} eje="x" p={[x, 0, 0]} rad={0.54} h={0.05} c="#285C99" r={0.5} m={0.3} />
178
+ ))}
179
+ <Y eje="x" p={[-0.6, 0, 0]} rad={0.62} h={0.5} {...ACERO} />
180
+ <Y eje="x" p={[-1.0, 0, 0]} rad={0.2} h={0.4} {...ACERO} />
181
+ <Y p={[-0.6, 0.8, 0]} rad={0.18} h={0.5} {...ACERO} />
182
+ <Y p={[-0.6, 1.05, 0]} rad={0.28} h={0.08} {...ACERO} />
183
+ <C p={[0.1, -0.62, 0]} s={[1.9, 0.1, 0.9]} c="#3A3A40" r={0.6} />
184
+ </group>
185
+ );
186
+ case 'alcantarilla':
187
+ return (
188
+ <group rotation={[-0.9, 0, 0]}>
189
+ <Y eje="z" rad={1} h={0.14} c={k('#4A4C52')} m={0.6} r={0.55} seg={40} />
190
+ <Aro p={[0, 0, 0.08]} rad={0.92} tubo={0.05} c="#3A3C42" m={0.6} r={0.5} />
191
+ {[-0.6, -0.3, 0, 0.3, 0.6].map((x) => (
192
+ <C key={x} p={[x, 0, 0.09]} s={[0.1, Math.sqrt(1 - x * x) * 1.5, 0.04]} c="#35373C" m={0.6} r={0.5} />
193
+ ))}
194
+ {[-0.45, 0.45].map((x) => (
195
+ <Y key={x} eje="z" p={[x, 0.55, 0.09]} rad={0.07} h={0.06} c="#1B1C22" />
196
+ ))}
197
+ </group>
198
+ );
199
+ case 'codo':
200
+ return (
201
+ <group position={[-0.35, -0.35, 0]}>
202
+ <Aro rad={0.8} tubo={0.3} arco={Math.PI / 2} c={k(COBRE.c)} m={0.85} r={0.3} />
203
+ <Y p={[0.8, -0.12, 0]} rad={0.36} h={0.24} {...COBRE} c={k(COBRE.c)} />
204
+ <Y eje="x" p={[-0.12, 0.8, 0]} rad={0.36} h={0.24} {...COBRE} c={k(COBRE.c)} />
205
+ </group>
206
+ );
207
+ // ------------------------------------------------------------ electricidad
208
+ case 'interruptor': {
209
+ const on = o.encendido ?? (o.en != null ? t > 0.3 : true);
210
+ return (
211
+ <group>
212
+ <C s={[1.3, 2, 0.12]} c={k('#F2F0EA')} r={0.4} />
213
+ <C p={[0, 0, 0.08]} s={[0.56, 0.95, 0.08]} c="#DEDBD3" r={0.4} />
214
+ <C p={[0, 0, 0.14]} rot={[on ? -0.28 : 0.28, 0, 0]} s={[0.46, 0.82, 0.12]} c="#FFFFFF" r={0.3} />
215
+ {[0.8, -0.8].map((y) => (
216
+ <Y key={y} eje="z" p={[0, y, 0.07]} rad={0.06} h={0.04} {...ACERO} />
217
+ ))}
218
+ {on && <E p={[0, -0.62, 0.1]} rad={0.045} c="#3DDC84" e={1} />}
219
+ </group>
220
+ );
221
+ }
222
+ case 'enchufe':
223
+ return (
224
+ <group>
225
+ <C s={[1.3, 2, 0.12]} c={k('#F2F0EA')} r={0.4} />
226
+ {[0.42, -0.42].map((y) => (
227
+ <group key={y} position={[0, y, 0.07]}>
228
+ <Y eje="z" rad={0.36} h={0.05} c="#E4E1D9" r={0.4} seg={32} />
229
+ <C p={[-0.13, 0.05, 0.03]} s={[0.05, 0.2, 0.03]} c="#18181C" />
230
+ <C p={[0.13, 0.05, 0.03]} s={[0.05, 0.2, 0.03]} c="#18181C" />
231
+ <Y eje="z" p={[0, -0.16, 0.03]} rad={0.045} h={0.03} c="#18181C" />
232
+ </group>
233
+ ))}
234
+ <Y eje="z" p={[0, 0, 0.07]} rad={0.05} h={0.04} {...ACERO} />
235
+ </group>
236
+ );
237
+ case 'tablero':
238
+ return (
239
+ <group>
240
+ <C s={[1.5, 2, 0.3]} c={k('#B9BEC6')} m={0.5} r={0.5} />
241
+ <C p={[0, 0, 0.16]} s={[1.3, 1.8, 0.03]} c="#2A2C33" r={0.6} />
242
+ {[0.5, 0, -0.5].map((y, f) =>
243
+ [-0.42, -0.14, 0.14, 0.42].map((x, i) => {
244
+ const arriba = !(o.botado === true && f === 1 && i === 2);
245
+ return (
246
+ <group key={`${y}${x}`} position={[x, y, 0.2]}>
247
+ <C s={[0.24, 0.42, 0.06]} c="#E6E3DC" r={0.5} />
248
+ <C p={[0, arriba ? 0.06 : -0.06, 0.05]} s={[0.1, 0.16, 0.06]} c={arriba ? '#22242A' : '#E0352B'} r={0.4} />
249
+ </group>
250
+ );
251
+ }),
252
+ )}
253
+ </group>
254
+ );
255
+ case 'medidor':
256
+ return (
257
+ <group>
258
+ <Y eje="z" rad={0.95} h={0.5} c={k('#9AA1AB')} m={0.6} r={0.45} seg={40} />
259
+ <Y eje="z" p={[0, 0, 0.27]} rad={0.85} h={0.06} c="#F4F2EC" r={0.5} seg={40} />
260
+ <Aro p={[0, 0, 0.3]} rad={0.88} tubo={0.05} c="#6B727C" m={0.6} r={0.4} />
261
+ <C p={[0, 0.3, 0.31]} s={[0.9, 0.24, 0.02]} c="#15161A" />
262
+ <group position={[0, -0.3, 0.31]} rotation={[0, 0, -giro]}>
263
+ <Y eje="z" rad={0.34} h={0.02} c="#C9CDD4" m={0.7} r={0.3} />
264
+ <C p={[0.28, 0, 0.012]} s={[0.1, 0.05, 0.01]} c="#E0352B" />
265
+ </group>
266
+ <C p={[0, -1.05, 0]} s={[0.7, 0.3, 0.4]} c="#6B727C" m={0.5} r={0.5} />
267
+ </group>
268
+ );
269
+ case 'transformador':
270
+ return (
271
+ <group>
272
+ <Y p={[0, -0.1, 0]} rad={0.62} h={1.5} c={k('#8E959F')} m={0.5} r={0.5} seg={32} />
273
+ <Y p={[0, 0.68, 0]} rad={0.66} h={0.08} c="#7A818B" m={0.5} r={0.5} />
274
+ {Array.from({length: 10}, (_, i) => (
275
+ <C key={i} p={[Math.cos((i / 10) * Math.PI * 2) * 0.66, -0.15, Math.sin((i / 10) * Math.PI * 2) * 0.66]} rot={[0, -(i / 10) * Math.PI * 2, 0]} s={[0.16, 1.1, 0.03]} c="#7A818B" m={0.5} r={0.5} />
276
+ ))}
277
+ {[-0.3, 0, 0.3].map((x) => (
278
+ <group key={x} position={[x, 0.72, 0]}>
279
+ {[0.1, 0.22, 0.34].map((y) => (
280
+ <Y key={y} p={[0, y, 0]} rad={[0.07, 0.12]} h={0.1} c="#8A4A2B" r={0.4} />
281
+ ))}
282
+ <Y p={[0, 0.46, 0]} rad={0.03} h={0.14} {...ACERO} />
283
+ </group>
284
+ ))}
285
+ </group>
286
+ );
287
+ case 'poste':
288
+ return (
289
+ <group>
290
+ <Y rad={[0.07, 0.1]} h={2.4} c={k('#7A5A3C')} r={0.9} />
291
+ <C p={[0, 0.85, 0]} s={[1.5, 0.09, 0.09]} c="#6A4C32" r={0.9} />
292
+ <C p={[0, 0.5, 0]} s={[1.0, 0.08, 0.08]} c="#6A4C32" r={0.9} />
293
+ {[-0.65, -0.3, 0.3, 0.65].map((x) => (
294
+ <Y key={x} p={[x, 0.95, 0]} rad={[0.03, 0.05]} h={0.14} c="#DDE3EA" r={0.3} />
295
+ ))}
296
+ {[-0.65, 0.65].map((x) => (
297
+ <Tubo key={x} pts={[[x, 1.02, 0], [x * 1.6, 0.9, 0], [x * 2.3, 0.98, 0]]} rad={0.012} c="#15161A" />
298
+ ))}
299
+ <Y p={[0.2, 0.2, 0.12]} rad={0.14} h={0.4} c="#8E959F" m={0.5} r={0.5} />
300
+ </group>
301
+ );
302
+ case 'bateria': {
303
+ const nivel = Math.max(0, Math.min(1, o.nivel ?? 1));
304
+ return (
305
+ <group>
306
+ <Y rad={0.5} h={1.8} c={k('#1E1F25')} r={0.4} m={0.3} seg={36} />
307
+ <Y p={[0, -0.9 + 0.75 * nivel, 0]} rad={0.505} h={1.5 * nivel} c={nivel > 0.25 ? '#3DDC84' : '#E0352B'} r={0.4} e={0.25} seg={36} />
308
+ <Y p={[0, 0.96, 0]} rad={0.2} h={0.14} {...ACERO} />
309
+ <Y p={[0, -0.92, 0]} rad={0.46} h={0.04} {...ACERO} />
310
+ </group>
311
+ );
312
+ }
313
+ case 'turbina':
314
+ return (
315
+ <group position={[0, -0.2, 0]}>
316
+ <Y p={[0, -0.3, 0]} rad={[0.05, 0.09]} h={1.8} c={k('#EEF0F3')} r={0.4} />
317
+ <C p={[0, 0.62, -0.08]} s={[0.16, 0.16, 0.42]} c="#E2E5EA" r={0.4} />
318
+ <Y eje="z" p={[0, 0.62, 0.16]} rad={[0.03, 0.09]} h={0.14} c="#E2E5EA" r={0.4} />
319
+ <group position={[0, 0.62, 0.2]} rotation={[0, 0, -giro * 0.4]}>
320
+ {[0, 1, 2].map((i) => (
321
+ <group key={i} rotation={[0, 0, (i * Math.PI * 2) / 3]}>
322
+ <C p={[0, 0.5, 0]} s={[0.1, 1.0, 0.025]} rot={[0, 0.25, 0]} c="#F6F7F9" r={0.35} />
323
+ </group>
324
+ ))}
325
+ </group>
326
+ </group>
327
+ );
328
+ case 'torre_electrica':
329
+ return (
330
+ <group>
331
+ {[-1, 1].map((l) => (
332
+ <C key={l} p={[l * 0.28, -0.1, 0]} rot={[0, 0, l * 0.16]} s={[0.05, 2.1, 0.05]} {...ACERO} c={k(ACERO.c)} />
333
+ ))}
334
+ {[-0.9, -0.5, -0.1, 0.3, 0.7].map((y, i) => (
335
+ <group key={y}>
336
+ <C p={[0, y, 0]} s={[0.9 - i * 0.14, 0.035, 0.04]} {...ACERO} />
337
+ <C p={[0, y + 0.2, 0]} rot={[0, 0, i % 2 ? 0.75 : -0.75]} s={[0.035, 0.6 - i * 0.07, 0.035]} {...ACERO} />
338
+ </group>
339
+ ))}
340
+ {[0.75, 0.45].map((y, i) => (
341
+ <group key={y}>
342
+ <C p={[0, y, 0]} s={[1.9 - i * 0.5, 0.05, 0.05]} {...ACERO} />
343
+ {[-1, 1].map((l) => (
344
+ <Y key={l} p={[l * (0.9 - i * 0.25), y - 0.1, 0]} rad={0.035} h={0.16} c="#7FB7E8" r={0.2} />
345
+ ))}
346
+ </group>
347
+ ))}
348
+ <C p={[0, 1.0, 0]} s={[0.05, 0.3, 0.05]} {...ACERO} />
349
+ </group>
350
+ );
351
+ case 'aire':
352
+ return (
353
+ <group>
354
+ <C s={[2.2, 0.75, 0.55]} c={k('#F4F5F7')} r={0.35} />
355
+ <C p={[0, -0.28, 0.25]} rot={[0.5, 0, 0]} s={[1.9, 0.05, 0.3]} c="#DDE0E5" r={0.4} />
356
+ <C p={[0, 0.2, 0.28]} s={[1.9, 0.03, 0.01]} c="#DDE0E5" />
357
+ <E p={[0.9, -0.05, 0.28]} rad={0.035} c="#3DDC84" e={1} />
358
+ {o.aire !== false &&
359
+ [-0.6, 0, 0.6].map((x, i) => {
360
+ const f = (t * 0.8 + i * 0.33) % 1;
361
+ return <C key={x} p={[x, -0.5 - f * 0.5, 0.45 + f * 0.25]} s={[0.35, 0.02, 0.02]} c="#9FD4FF" o={0.7 * (1 - f)} />;
362
+ })}
363
+ </group>
364
+ );
365
+ // ------------------------------------------------------------ electrónica
366
+ case 'chip':
367
+ return (
368
+ <group rotation={[-0.75, 0, 0]}>
369
+ <C s={[1.5, 1.5, 0.22]} c={k('#1A1B20')} r={0.5} />
370
+ <Y eje="z" p={[-0.55, 0.55, 0.12]} rad={0.06} h={0.01} c="#6B6F78" />
371
+ <C p={[0, 0, 0.115]} s={[0.8, 0.5, 0.005]} c="#3A3D45" r={0.4} />
372
+ {Array.from({length: 8}, (_, i) =>
373
+ [0, 1, 2, 3].map((lado) => (
374
+ <group key={`${i}${lado}`} rotation={[0, 0, (lado * Math.PI) / 2]}>
375
+ <C p={[-0.56 + i * 0.16, 0.86, -0.06]} s={[0.07, 0.24, 0.04]} {...ACERO} />
376
+ </group>
377
+ )),
378
+ )}
379
+ </group>
380
+ );
381
+ case 'placa':
382
+ return (
383
+ <group rotation={[-0.7, 0, 0]}>
384
+ <C s={[2.2, 2.2, 0.06]} c={k(PCB.c)} r={0.55} />
385
+ {/* procesador con su marco */}
386
+ <C p={[-0.3, 0.4, 0.06]} s={[0.75, 0.75, 0.06]} c="#C9CDD4" m={0.8} r={0.3} />
387
+ <C p={[-0.3, 0.4, 0.1]} s={[0.5, 0.5, 0.04]} c="#8E959F" m={0.9} r={0.25} />
388
+ {/* ranuras de memoria */}
389
+ {[0.45, 0.62, 0.79, 0.96].map((x) => (
390
+ <C key={x} p={[x, 0.3, 0.07]} s={[0.07, 1.3, 0.1]} c="#15161A" r={0.5} />
391
+ ))}
392
+ {/* ranuras de expansión */}
393
+ {[-0.45, -0.72].map((y, i) => (
394
+ <C key={y} p={[-0.15, y, 0.07]} s={[1.5, 0.09, 0.1]} c={i ? '#E6E3DC' : '#2B6FD6'} r={0.5} />
395
+ ))}
396
+ {/* capacitores */}
397
+ {[[-0.95, 0.9], [-0.78, 0.9], [-0.95, 0.72], [0.2, 0.95], [0.2, -0.2], [-0.9, -0.1]].map(([x, y]) => (
398
+ <Y key={`${x}${y}`} eje="z" p={[x, y, 0.13]} rad={0.07} h={0.2} c="#1E1F25" r={0.4} />
399
+ ))}
400
+ {/* pila, chipset y puertos */}
401
+ <Y eje="z" p={[0.7, -0.75, 0.06]} rad={0.2} h={0.06} {...ACERO} />
402
+ <C p={[-0.75, -0.3, 0.07]} s={[0.35, 0.35, 0.08]} c="#1A1B20" r={0.5} />
403
+ <C p={[-1.0, 0.3, 0.14]} s={[0.16, 0.9, 0.24]} c="#B9BEC6" m={0.8} r={0.35} />
404
+ {/* pistas */}
405
+ {[-0.2, -0.05, 0.1].map((y) => (
406
+ <C key={y} p={[0.1, y - 0.1, 0.032]} s={[1.0, 0.015, 0.005]} c="#57C48A" e={0.2} />
407
+ ))}
408
+ </group>
409
+ );
410
+ case 'resistor':
411
+ return (
412
+ <group>
413
+ <Y eje="x" rad={0.035} h={2.4} {...ACERO} />
414
+ <Y eje="x" rad={0.26} h={1.0} c={k('#D9B98A')} r={0.5} />
415
+ <E p={[-0.5, 0, 0]} rad={0.3} c={k('#D9B98A')} r={0.5} />
416
+ <E p={[0.5, 0, 0]} rad={0.3} c={k('#D9B98A')} r={0.5} />
417
+ {(o.bandas ?? ['#8A4A2B', '#15161A', '#E0352B', '#E0B64A']).map((c: string, i: number) => (
418
+ <Y key={i} eje="x" p={[-0.3 + i * 0.2 + (i === 3 ? 0.12 : 0), 0, 0]} rad={0.268} h={0.09} c={c} r={0.5} />
419
+ ))}
420
+ </group>
421
+ );
422
+ case 'capacitor':
423
+ return (
424
+ <group position={[0, 0.2, 0]}>
425
+ <Y rad={0.5} h={1.3} c={k('#1F3F8F')} r={0.4} seg={36} />
426
+ <Y p={[0, 0.66, 0]} rad={0.48} h={0.04} c="#C9CDD4" m={0.8} r={0.3} />
427
+ <C p={[0.495, 0, 0]} s={[0.02, 1.2, 0.3]} c="#DDE3EA" />
428
+ <Y p={[0, -0.68, 0]} rad={0.5} h={0.06} c="#15161A" />
429
+ {[-0.18, 0.18].map((x, i) => (
430
+ <Y key={x} p={[x, -1.05 + i * 0.08, 0]} rad={0.03} h={0.7 - i * 0.16} {...ACERO} />
431
+ ))}
432
+ </group>
433
+ );
434
+ case 'led': {
435
+ const c = k('#FF3B3B');
436
+ const on = o.encendido ?? true;
437
+ return (
438
+ <group position={[0, 0.3, 0]}>
439
+ <Y p={[0, -0.05, 0]} rad={0.42} h={0.7} c={c} r={0.15} e={on ? 0.9 : 0.05} o={0.9} />
440
+ <E p={[0, 0.3, 0]} rad={0.42} c={c} r={0.15} e={on ? 0.9 : 0.05} o={0.9} />
441
+ <Y p={[0, -0.42, 0]} rad={0.5} h={0.08} c={c} r={0.2} e={on ? 0.5 : 0} />
442
+ {[-0.15, 0.15].map((x, i) => (
443
+ <Y key={x} p={[x, -0.95 + i * 0.1, 0]} rad={0.03} h={1.0 - i * 0.2} {...ACERO} />
444
+ ))}
445
+ </group>
446
+ );
447
+ }
448
+ case 'disco_duro':
449
+ return (
450
+ <group rotation={[-0.7, 0, 0]}>
451
+ <C s={[1.6, 2.2, 0.3]} c={k('#B9BEC6')} m={0.8} r={0.35} />
452
+ <group position={[0, 0.3, 0.17]} rotation={[0, 0, -giro * 0.6]}>
453
+ <Y eje="z" rad={0.72} h={0.03} c="#DDE3EA" m={0.95} r={0.12} seg={48} />
454
+ <C p={[0.4, 0, 0.018]} s={[0.3, 0.02, 0.002]} c="#F4F6F8" />
455
+ </group>
456
+ <Y eje="z" p={[0, 0.3, 0.2]} rad={0.16} h={0.05} {...ACERO} />
457
+ <group position={[0.55, -0.75, 0.2]} rotation={[0, 0, 2.2 + Math.sin(t * 3) * 0.18]}>
458
+ <C p={[0.4, 0, 0]} s={[0.85, 0.09, 0.03]} {...ACERO} />
459
+ <Y eje="z" rad={0.13} h={0.06} {...ACERO} />
460
+ </group>
461
+ <C p={[0, -1.02, 0]} s={[1.0, 0.1, 0.1]} c="#1F7A4D" r={0.5} />
462
+ </group>
463
+ );
464
+ case 'tarjeta_grafica':
465
+ return (
466
+ <group rotation={[0.5, -0.3, 0]}>
467
+ <C s={[2.4, 1.0, 0.36]} c={k('#22242A')} r={0.45} />
468
+ <C p={[0, -0.02, -0.2]} s={[2.4, 1.06, 0.05]} {...PCB} />
469
+ {[-0.6, 0.6].map((x) => (
470
+ <group key={x} position={[x, 0, 0.19]}>
471
+ <Y eje="z" rad={0.44} h={0.03} c="#15161A" seg={36} />
472
+ <Aspas n={9} rad={0.4} giro={giro * 2} c="#3A3D45" p={[0, 0, 0.03]} ancho={0.22} />
473
+ <Y eje="z" p={[0, 0, 0.04]} rad={0.12} h={0.06} c="#2A2C33" />
474
+ </group>
475
+ ))}
476
+ <C p={[0, 0.45, 0.19]} s={[1.0, 0.05, 0.02]} c={o.luz ?? '#3DDC84'} e={0.9} />
477
+ <C p={[0.2, -0.6, -0.2]} s={[1.5, 0.16, 0.03]} {...ORO} />
478
+ <C p={[-1.24, 0, 0]} s={[0.05, 1.2, 0.5]} {...ACERO} />
479
+ </group>
480
+ );
481
+ case 'disipador':
482
+ return (
483
+ <group rotation={[0.35, -0.4, 0]}>
484
+ <C p={[0, -0.75, 0]} s={[1.5, 0.12, 1.5]} {...COBRE} />
485
+ {Array.from({length: 11}, (_, i) => (
486
+ <C key={i} p={[-0.7 + i * 0.14, -0.15, 0]} s={[0.04, 1.1, 1.5]} c={k('#C9CDD4')} m={0.85} r={0.3} />
487
+ ))}
488
+ <group position={[0, 0.5, 0]}>
489
+ <C s={[1.5, 0.22, 1.5]} c="#1E1F25" r={0.5} o={0.92} />
490
+ <group position={[0, 0.12, 0]} rotation={[-Math.PI / 2, 0, 0]}>
491
+ <Aspas n={7} rad={0.62} giro={giro * 2} c="#3A3D45" ancho={0.28} />
492
+ <Y eje="z" rad={0.16} h={0.08} c="#2A2C33" />
493
+ </group>
494
+ </group>
495
+ </group>
496
+ );
497
+ case 'ram':
498
+ return (
499
+ <group>
500
+ <C s={[2.4, 0.62, 0.05]} c={k(PCB.c)} r={0.55} />
501
+ {[-0.9, -0.45, 0, 0.45, 0.9].map((x) => (
502
+ <C key={x} p={[x, 0.06, 0.04]} s={[0.34, 0.3, 0.05]} c="#15161A" r={0.5} />
503
+ ))}
504
+ <C p={[-0.25, -0.33, 0]} s={[1.6, 0.1, 0.055]} {...ORO} />
505
+ <C p={[0.95, -0.33, 0]} s={[0.4, 0.1, 0.055]} {...ORO} />
506
+ </group>
507
+ );
508
+ case 'router':
509
+ return (
510
+ <group position={[0, -0.4, 0]}>
511
+ <C s={[2.0, 0.34, 1.2]} c={k('#F2F3F5')} r={0.4} />
512
+ {[-0.8, 0, 0.8].map((x, i) => (
513
+ <Y key={x} p={[x, 0.75, -0.5]} rot={[0, 0, (i - 1) * -0.22]} rad={0.045} h={1.3} c="#22242A" r={0.5} />
514
+ ))}
515
+ {[-0.6, -0.4, -0.2, 0].map((x, i) => (
516
+ <E key={x} p={[x, 0.02, 0.61]} rad={0.035} c="#3DDC84" e={Math.sin(t * 6 + i * 1.7) > 0 ? 1 : 0.15} />
517
+ ))}
518
+ </group>
519
+ );
520
+ case 'servidor':
521
+ return (
522
+ <group>
523
+ <C s={[1.3, 2.3, 1.1]} c={k('#1E1F25')} r={0.5} m={0.3} />
524
+ {Array.from({length: 7}, (_, i) => (
525
+ <group key={i} position={[0, 0.9 - i * 0.3, 0.56]}>
526
+ <C s={[1.1, 0.22, 0.03]} c="#2E3038" r={0.5} m={0.4} />
527
+ <E p={[-0.42, 0, 0.02]} rad={0.03} c="#3DDC84" e={Math.sin(t * 5 + i * 2.1) > -0.3 ? 1 : 0.1} />
528
+ <E p={[-0.32, 0, 0.02]} rad={0.03} c="#FFB020" e={Math.sin(t * 9 + i * 1.3) > 0.2 ? 1 : 0.1} />
529
+ {[0.05, 0.15, 0.25, 0.35].map((x) => (
530
+ <C key={x} p={[x, 0, 0.02]} s={[0.06, 0.12, 0.01]} c="#15161A" />
531
+ ))}
532
+ </group>
533
+ ))}
534
+ </group>
535
+ );
536
+ case 'fuente_poder':
537
+ return (
538
+ <group rotation={[0.3, -0.5, 0]}>
539
+ <C s={[1.9, 1.2, 1.6]} c={k('#2A2C33')} m={0.5} r={0.5} />
540
+ <group position={[0, 0.61, 0]} rotation={[-Math.PI / 2, 0, 0]}>
541
+ <Y eje="z" rad={0.62} h={0.02} c="#15161A" seg={36} />
542
+ <Aspas n={7} rad={0.56} giro={giro * 1.5} c="#4A4D56" p={[0, 0, 0.02]} ancho={0.28} />
543
+ <Y eje="z" p={[0, 0, 0.03]} rad={0.14} h={0.05} c="#E0B64A" />
544
+ </group>
545
+ <C p={[-0.5, 0, 0.81]} s={[0.4, 0.3, 0.02]} c="#15161A" />
546
+ <C p={[0.4, 0.1, 0.81]} s={[0.2, 0.14, 0.04]} c="#E0352B" />
547
+ {['#E0352B', '#F2C230', '#15161A'].map((c, i) => (
548
+ <Tubo key={c} pts={[[-0.96, -0.2 + i * 0.12, -0.3], [-1.3, -0.25 + i * 0.12, -0.2], [-1.55, -0.5 + i * 0.1, 0.1]]} rad={0.035} c={c} r={0.6} />
549
+ ))}
550
+ </group>
551
+ );
552
+ // ------------------------------------------------------------ mecánica
553
+ case 'engrane':
554
+ return o.par ? (
555
+ <group>
556
+ <Engrane p={[-0.52, 0, 0]} n={14} ext={0.85} giro={giro * 0.25} c={k(ACERO.c)} m={0.85} r={0.3} />
557
+ <Engrane p={[0.78, 0.1, 0]} n={8} ext={0.5} giro={-giro * 0.25 * (14 / 8) + 0.2} {...LATON} />
558
+ </group>
559
+ ) : (
560
+ <Engrane giro={giro * 0.25} c={k(ACERO.c)} m={0.85} r={0.3} />
561
+ );
562
+ case 'tornillo':
563
+ return (
564
+ <group rotation={[0, 0, 0.5]}>
565
+ <Y p={[0, -0.2, 0]} rad={0.2} h={1.7} {...ACERO} c={k(ACERO.c)} />
566
+ <Helice vueltas={9} rad={0.2} alto={1.3} tubo={0.045} p={[0, -0.4, 0]} {...ACERO} c={k(ACERO.c)} />
567
+ <Y p={[0, -1.12, 0]} rad={[0.2, 0.02]} h={0.2} {...ACERO} c={k(ACERO.c)} />
568
+ <Y p={[0, 0.78, 0]} rad={[0.5, 0.42]} h={0.26} {...ACERO} c={k(ACERO.c)} />
569
+ <C p={[0, 0.92, 0]} s={[0.6, 0.06, 0.09]} c="#3A3D45" m={0.8} />
570
+ <C p={[0, 0.92, 0]} s={[0.09, 0.06, 0.6]} c="#3A3D45" m={0.8} />
571
+ </group>
572
+ );
573
+ case 'tuerca': {
574
+ return (
575
+ <group rotation={[1.0, 0.3, 0]}>
576
+ <Hex rad={1} h={0.6} {...ACERO} c={k(ACERO.c)} />
577
+ <Y rad={0.5} h={0.62} c="#2A2C33" m={0.7} r={0.5} />
578
+ <Helice vueltas={4} rad={0.48} alto={0.5} tubo={0.04} {...ACERO} />
579
+ </group>
580
+ );
581
+ }
582
+ case 'motor':
583
+ return (
584
+ <group>
585
+ <Y eje="x" rad={0.62} h={1.5} c={k('#2F6DB5')} m={0.3} r={0.45} seg={36} />
586
+ {Array.from({length: 9}, (_, i) => (
587
+ <Y key={i} eje="x" p={[-0.6 + i * 0.15, 0, 0]} rad={0.67} h={0.04} c="#285C99" m={0.3} r={0.5} seg={36} />
588
+ ))}
589
+ <Y eje="x" p={[-0.82, 0, 0]} rad={0.64} h={0.14} {...ACERO} />
590
+ <Y eje="x" p={[0.82, 0, 0]} rad={0.64} h={0.14} {...ACERO} />
591
+ <group position={[1.15, 0, 0]} rotation={[giro, 0, 0]}>
592
+ <Y eje="x" rad={0.11} h={0.55} {...ACERO} />
593
+ <C p={[0.1, 0.11, 0]} s={[0.3, 0.05, 0.06]} c="#3A3D45" m={0.8} />
594
+ </group>
595
+ <C p={[0, 0.72, 0]} s={[0.5, 0.26, 0.5]} c="#3A3D45" r={0.5} />
596
+ <C p={[0, -0.7, 0]} s={[1.3, 0.1, 1.4]} c="#3A3D45" r={0.6} />
597
+ </group>
598
+ );
599
+ case 'resorte':
600
+ return <Helice vueltas={7} rad={0.5} alto={1.9 * (1 - 0.25 * Math.abs(Math.sin(t * (o.velocidad ?? 0) * Math.PI)))} tubo={0.07} {...ACERO} c={k(ACERO.c)} />;
601
+ case 'piston': {
602
+ const y = Math.sin(giro * 0.5) * 0.3;
603
+ return (
604
+ <group>
605
+ <Y p={[0, 0.2, 0]} rad={0.72} h={1.5} c="#C9CDD4" m={0.6} r={0.4} o={0.3} seg={36} />
606
+ <group position={[0, 0.35 + y, 0]}>
607
+ <Y rad={0.64} h={0.6} c={k(ACERO.c)} m={0.85} r={0.3} seg={36} />
608
+ {[0.2, 0.08].map((d) => (
609
+ <Aro key={d} p={[0, d, 0]} rot={[Math.PI / 2, 0, 0]} rad={0.64} tubo={0.025} c="#3A3D45" m={0.8} />
610
+ ))}
611
+ <C p={[0, -0.75, 0]} rot={[0, 0, -y * 0.4]} s={[0.16, 1.2, 0.12]} {...ACERO} />
612
+ </group>
613
+ <Y eje="z" p={[0, -1.0, 0]} rad={0.24} h={0.3} {...ACERO} />
614
+ </group>
615
+ );
616
+ }
617
+ default:
618
+ return null;
619
+ }
620
+ };
@@ -3,12 +3,18 @@
3
3
  // Cada objeto mide ~2 unidades y viene centrado. Materiales con brillo: se ven bien con los reflejos del entorno.
4
4
  import React, {useMemo} from 'react';
5
5
  import * as THREE from 'three';
6
+ import {OBJETOS_INGENIERIA, ObjetoIngenieria} from './objetos-ingenieria';
7
+ import {OBJETOS_CIENCIA, ObjetoCiencia} from './objetos-ciencia';
6
8
 
7
9
  export const OBJETOS_3D = [
8
10
  'corazon', 'estrella', 'rayo', 'diamante', 'trofeo', 'cohete', 'foco', 'reloj', 'planeta', 'arbol',
9
11
  'nube', 'regalo', 'llave', 'candado', 'casa', 'libro', 'globo', 'medalla', 'escudo', 'pin',
10
12
  // construcción (aceptan opciones: ver más abajo)
11
13
  'tuberia', 'muro', 'viga', 'columna', 'escalera', 'losa', 'tanque', 'suelo',
14
+ // plomería, electricidad, electrónica y mecánica (objetos-ingenieria.tsx)
15
+ ...OBJETOS_INGENIERIA,
16
+ // ciencia y cuerpo, consola, casa y ciudad, transporte, escuela (objetos-ciencia.tsx)
17
+ ...OBJETOS_CIENCIA,
12
18
  ] as const;
13
19
 
14
20
  type M = {color?: string; metal?: number; rugosidad?: number};
@@ -543,6 +549,7 @@ export const Objeto3D: React.FC<{forma: string; t: number; opciones?: Record<str
543
549
  );
544
550
  }
545
551
  default:
546
- return null;
552
+ if ((OBJETOS_INGENIERIA as readonly string[]).includes(forma)) return <ObjetoIngenieria forma={forma} t={t} opciones={o} {...m} />;
553
+ return (OBJETOS_CIENCIA as readonly string[]).includes(forma) ? <ObjetoCiencia forma={forma} t={t} opciones={o} {...m} /> : null;
547
554
  }
548
555
  };