morfeo 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bases/2d/Mundo.tsx +43 -0
- package/bases/2d/guion.mjs +32 -0
- package/bases/3d/Mundo.tsx +45 -0
- package/bases/3d/guion.mjs +26 -0
- package/bin/morfeo.mjs +20 -3
- package/index.d.ts +10 -3
- package/lib/config.mjs +45 -8
- package/lib/entorno.mjs +22 -2
- package/lib/guion.mjs +2 -2
- package/lib/hoja.mjs +3 -0
- package/lib/nuevo-video.mjs +65 -0
- package/lib/palabras.mjs +79 -0
- package/lib/render.mjs +81 -16
- package/lib/whisper.mjs +47 -3
- package/package.json +4 -3
- package/plantilla/morfeo.config.mjs +3 -1
- package/python/musica.py +40 -7
- package/python/preparar.py +10 -1
- package/remotion/kit/datos.tsx +180 -0
- package/remotion/kit/escenarios2d.tsx +250 -0
- package/remotion/kit/index.ts +9 -2
- package/remotion/kit/mundo3d.tsx +251 -0
- package/remotion/kit/personas.tsx +439 -0
- package/remotion/kit/tres.tsx +3 -3
- package/remotion/motor/capas.tsx +27 -2
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
// Escenarios 2D listos (van DENTRO de un <svg>, normalmente como fondo de un `mundo` de 2160×1920).
|
|
2
|
+
// Todos aceptan `ancho` (del mundo), `suelo` (altura del piso), `t` (para lo que se mueve) y `camara` (paralaje).
|
|
3
|
+
// Combínalos con Persona2D, Multitud y Globo (kit/personas.tsx) y con las anotaciones del motor.
|
|
4
|
+
//
|
|
5
|
+
// <CiudadNoche t={t} camara={camara} /> <Cuarto pared="#2E5A4C" /> <Campo t={t} /> <Calle t={t} />
|
|
6
|
+
// <Oficina /> <Mar t={t} profundo />
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import {Cielo, ConoLuz, Estrellas, Montanas, Paralaje, Pasto, Pinos, Suelo, alturaSuelo, azarN, type Camara} from './ilustrado';
|
|
9
|
+
|
|
10
|
+
type Base = {ancho?: number; suelo?: number; t?: number; camara?: Camara; semilla?: number};
|
|
11
|
+
|
|
12
|
+
/** Edificios con ventanas (algunas encendidas que parpadean poco a poco). */
|
|
13
|
+
const Edificios: React.FC<{x0: number; x1: number; base: number; alto: number; color: string; luz: string; t: number; semilla: number; n?: number}> = ({x0, x1, base, alto, color, luz, t, semilla, n = 9}) => {
|
|
14
|
+
const ancho = (x1 - x0) / n;
|
|
15
|
+
return (
|
|
16
|
+
<g>
|
|
17
|
+
{Array.from({length: n}, (_, i) => {
|
|
18
|
+
const s = semilla + i * 13;
|
|
19
|
+
const w = ancho * (0.7 + azarN(s) * 0.28);
|
|
20
|
+
const h = alto * (0.45 + azarN(s + 1) * 0.55);
|
|
21
|
+
const x = x0 + i * ancho + (ancho - w) / 2;
|
|
22
|
+
const cols = Math.max(2, Math.floor(w / 46));
|
|
23
|
+
const filas = Math.max(3, Math.floor(h / 62));
|
|
24
|
+
return (
|
|
25
|
+
<g key={i}>
|
|
26
|
+
<rect x={x} y={base - h} width={w} height={h + 400} fill={color} />
|
|
27
|
+
{Array.from({length: cols * filas}, (_, j) => {
|
|
28
|
+
const c = j % cols;
|
|
29
|
+
const f = Math.floor(j / cols);
|
|
30
|
+
const on = azarN(s + j * 3) > 0.55;
|
|
31
|
+
if (!on) return null;
|
|
32
|
+
const brillo = 0.75 + 0.25 * Math.sin(t * (0.3 + azarN(s + j) * 0.5) + j);
|
|
33
|
+
return <rect key={j} x={x + 14 + c * ((w - 28) / cols)} y={base - h + 22 + f * 62} width={(w - 28) / cols - 14} height={34} rx={3} fill={luz} opacity={brillo} />;
|
|
34
|
+
})}
|
|
35
|
+
</g>
|
|
36
|
+
);
|
|
37
|
+
})}
|
|
38
|
+
</g>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Ciudad de noche: cielo, estrellas, edificios en tres planos con ventanas, calle. */
|
|
43
|
+
export const CiudadNoche: React.FC<Base & {luz?: string}> = ({ancho = 2160, suelo = 1500, t = 0, camara, semilla = 0, luz = '#FFD27A'}) => (
|
|
44
|
+
<g>
|
|
45
|
+
<Paralaje camara={camara} profundidad={0.9}>
|
|
46
|
+
<Cielo id="cn-cielo" colores={['#070A1F', '#141B45', '#2B2A63', '#4A3A73']} />
|
|
47
|
+
<Estrellas t={t} n={80} y1={900} semilla={semilla} />
|
|
48
|
+
</Paralaje>
|
|
49
|
+
<Paralaje camara={camara} profundidad={0.55}>
|
|
50
|
+
<Edificios x0={-500} x1={ancho + 500} base={suelo - 60} alto={900} color="#1B2350" luz="#6C77C9" t={t} semilla={semilla + 1} n={14} />
|
|
51
|
+
</Paralaje>
|
|
52
|
+
<Paralaje camara={camara} profundidad={0.25}>
|
|
53
|
+
<Edificios x0={-400} x1={ancho + 400} base={suelo - 10} alto={1050} color="#141A3A" luz={luz} t={t} semilla={semilla + 2} n={10} />
|
|
54
|
+
</Paralaje>
|
|
55
|
+
<rect x={-800} y={suelo} width={ancho + 1600} height={800} fill="#20222C" />
|
|
56
|
+
<rect x={-800} y={suelo} width={ancho + 1600} height={14} fill="#3A3D4A" />
|
|
57
|
+
{Array.from({length: Math.ceil((ancho + 1600) / 240)}, (_, i) => (
|
|
58
|
+
<rect key={i} x={-800 + i * 240} y={suelo + 150} width={130} height={14} rx={7} fill="#E8C35A" opacity={0.7} />
|
|
59
|
+
))}
|
|
60
|
+
</g>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
/** Interior: pared con zoclo, piso de madera, ventana, cuadro y lámpara con su cono de luz. */
|
|
64
|
+
export const Cuarto: React.FC<Base & {pared?: string; piso?: string; noche?: boolean; ventana?: boolean; lampara?: boolean; cuadro?: boolean}> = ({ancho = 2160, suelo = 1500, t = 0, pared = '#2E5A4C', piso = '#7A4A2A', noche = true, ventana = true, lampara = true, cuadro = true}) => (
|
|
65
|
+
<g>
|
|
66
|
+
<rect x={-800} y={-800} width={ancho + 1600} height={suelo + 800} fill={pared} />
|
|
67
|
+
{/* papel tapiz: rombos sutiles */}
|
|
68
|
+
<g opacity={0.08} fill="#000">
|
|
69
|
+
{Array.from({length: 40 * 30}, (_, i) => {
|
|
70
|
+
const c = i % 40;
|
|
71
|
+
const f = Math.floor(i / 40);
|
|
72
|
+
return <path key={i} d={`M ${-800 + c * 110 + (f % 2) * 55} ${-800 + f * 80} l 18 26 l -18 26 l -18 -26 Z`} />;
|
|
73
|
+
})}
|
|
74
|
+
</g>
|
|
75
|
+
<rect x={-800} y={suelo - 140} width={ancho + 1600} height={140} fill="rgba(0,0,0,0.22)" />
|
|
76
|
+
<rect x={-800} y={suelo - 150} width={ancho + 1600} height={14} fill="rgba(255,255,255,0.12)" />
|
|
77
|
+
<rect x={-800} y={suelo} width={ancho + 1600} height={900} fill={piso} />
|
|
78
|
+
{Array.from({length: 30}, (_, i) => (
|
|
79
|
+
<rect key={i} x={-800} y={suelo + 30 + i * 46} width={ancho + 1600} height={3} fill="rgba(0,0,0,0.18)" />
|
|
80
|
+
))}
|
|
81
|
+
{ventana ? (
|
|
82
|
+
<g transform={`translate(${ancho * 0.72} ${suelo - 1000})`}>
|
|
83
|
+
<rect x={-20} y={-20} width={420} height={520} rx={10} fill="#E9E1D2" />
|
|
84
|
+
<rect width={380} height={480} fill={noche ? '#1A2350' : '#9FD4F5'} />
|
|
85
|
+
{noche ? <circle cx={270} cy={120} r={46} fill="#FFF4D6" /> : <circle cx={270} cy={120} r={54} fill="#FFE27A" />}
|
|
86
|
+
<rect x={186} width={8} height={480} fill="#E9E1D2" />
|
|
87
|
+
<rect y={236} width={380} height={8} fill="#E9E1D2" />
|
|
88
|
+
</g>
|
|
89
|
+
) : null}
|
|
90
|
+
{cuadro ? (
|
|
91
|
+
<g transform={`translate(${ancho * 0.3} ${suelo - 1050})`}>
|
|
92
|
+
<rect x={-16} y={-16} width={352} height={262} fill="#B8873A" />
|
|
93
|
+
<rect width={320} height={230} fill="#8FD0C4" />
|
|
94
|
+
<path d="M 0 230 L 90 120 L 170 190 L 240 110 L 320 230 Z" fill="#3E8A55" />
|
|
95
|
+
<circle cx={250} cy={60} r={26} fill="#F5D46A" />
|
|
96
|
+
</g>
|
|
97
|
+
) : null}
|
|
98
|
+
{lampara ? (
|
|
99
|
+
<g transform={`translate(${ancho * 0.12} ${suelo})`}>
|
|
100
|
+
<ConoLuz id="cuarto-cono" x={0} y={-640} ancho={720} largo={700} fuerza={0.4 + 0.03 * Math.sin(t * 2)} />
|
|
101
|
+
<rect x={-6} y={-600} width={12} height={600} fill="#2A2420" />
|
|
102
|
+
<ellipse cx={0} cy={0} rx={70} ry={14} fill="#2A2420" />
|
|
103
|
+
<path d="M -90 -640 L 90 -640 L 60 -760 L -60 -760 Z" fill="#F2D49B" />
|
|
104
|
+
</g>
|
|
105
|
+
) : null}
|
|
106
|
+
</g>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
/** Campo abierto de día: cielo, sol, nubes que avanzan, montañas, árboles y pasto. */
|
|
110
|
+
export const Campo: React.FC<Base & {tarde?: boolean}> = ({ancho = 2160, suelo = 1500, t = 0, camara, semilla = 0, tarde = false}) => (
|
|
111
|
+
<g>
|
|
112
|
+
<Paralaje camara={camara} profundidad={0.9}>
|
|
113
|
+
<Cielo id="campo-cielo" colores={tarde ? ['#F7B267', '#F4845F', '#F27059'] : ['#7EC8F2', '#B6E2F7', '#E8F6FC']} />
|
|
114
|
+
<circle cx={ancho * 0.75} cy={380} r={110} fill={tarde ? '#FFE3A3' : '#FFF1A8'} />
|
|
115
|
+
{[0, 1, 2, 3].map((i) => {
|
|
116
|
+
const x = ((azarN(semilla + i) * (ancho + 800) + t * (18 + i * 6)) % (ancho + 1200)) - 600;
|
|
117
|
+
const y = 220 + azarN(semilla + i + 9) * 380;
|
|
118
|
+
return (
|
|
119
|
+
<g key={i} transform={`translate(${x} ${y}) scale(${0.8 + azarN(i) * 0.6})`} fill="#fff" opacity={0.92}>
|
|
120
|
+
<circle cx={0} cy={0} r={60} />
|
|
121
|
+
<circle cx={70} cy={-20} r={75} />
|
|
122
|
+
<circle cx={150} cy={0} r={55} />
|
|
123
|
+
<rect x={0} y={0} width={150} height={55} />
|
|
124
|
+
</g>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
127
|
+
</Paralaje>
|
|
128
|
+
<Paralaje camara={camara} profundidad={0.6}>
|
|
129
|
+
<Montanas base={suelo - 220} alto={500} semilla={semilla + 3} color={tarde ? '#B5656B' : '#8DB7C9'} />
|
|
130
|
+
</Paralaje>
|
|
131
|
+
<Paralaje camara={camara} profundidad={0.3}>
|
|
132
|
+
<Pinos base={suelo - 60} alto={420} ancho={190} n={18} semilla={semilla + 7} color={tarde ? '#4E5B3A' : '#3E8A55'} />
|
|
133
|
+
</Paralaje>
|
|
134
|
+
<Suelo y={suelo} colores={tarde ? ['#8DA05A', '#55623A'] : ['#7CC45A', '#3E8A3E']} id="campo-suelo" />
|
|
135
|
+
<Pasto y={suelo} t={t} n={180} color={tarde ? '#6E7F44' : '#5AA845'} semilla={semilla + 11} />
|
|
136
|
+
</g>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
/** Calle de día con banqueta, casas al fondo y postes. */
|
|
140
|
+
export const Calle: React.FC<Base & {noche?: boolean}> = ({ancho = 2160, suelo = 1500, t = 0, camara, semilla = 0, noche = false}) => {
|
|
141
|
+
const casas = ['#F2E6D0', '#F7D9C4', '#DDE8F0', '#F4E3A1', '#E8D5F0'];
|
|
142
|
+
return (
|
|
143
|
+
<g>
|
|
144
|
+
<Paralaje camara={camara} profundidad={0.9}>
|
|
145
|
+
<Cielo id="calle-cielo" colores={noche ? ['#0B1033', '#232A63'] : ['#8FD0F5', '#DDF2FC']} />
|
|
146
|
+
</Paralaje>
|
|
147
|
+
<Paralaje camara={camara} profundidad={0.3}>
|
|
148
|
+
<g>
|
|
149
|
+
{Array.from({length: 8}, (_, i) => {
|
|
150
|
+
const x = -400 + i * ((ancho + 800) / 8);
|
|
151
|
+
const w = 300 + azarN(semilla + i) * 60;
|
|
152
|
+
const h = 320 + azarN(semilla + i + 3) * 140;
|
|
153
|
+
return (
|
|
154
|
+
<g key={i}>
|
|
155
|
+
<rect x={x} y={suelo - 120 - h} width={w} height={h} fill={casas[i % casas.length]} />
|
|
156
|
+
<path d={`M ${x - 20} ${suelo - 120 - h} L ${x + w / 2} ${suelo - 120 - h - 150} L ${x + w + 20} ${suelo - 120 - h} Z`} fill={['#D9533B', '#8A3B2A', '#4A5A7A'][i % 3]} />
|
|
157
|
+
<rect x={x + w * 0.4} y={suelo - 120 - 170} width={70} height={170} fill="#7A4E2D" />
|
|
158
|
+
<rect x={x + w * 0.1} y={suelo - 120 - h + 60} width={80} height={70} fill={noche ? '#FFD27A' : '#9AD4F5'} />
|
|
159
|
+
<rect x={x + w * 0.65} y={suelo - 120 - h + 60} width={80} height={70} fill={noche ? '#FFD27A' : '#9AD4F5'} />
|
|
160
|
+
</g>
|
|
161
|
+
);
|
|
162
|
+
})}
|
|
163
|
+
</g>
|
|
164
|
+
</Paralaje>
|
|
165
|
+
<rect x={-800} y={suelo - 120} width={ancho + 1600} height={120} fill="#C9C4BA" />
|
|
166
|
+
<rect x={-800} y={suelo - 124} width={ancho + 1600} height={10} fill="#E2DDD3" />
|
|
167
|
+
<rect x={-800} y={suelo} width={ancho + 1600} height={900} fill="#3E4250" />
|
|
168
|
+
{Array.from({length: Math.ceil((ancho + 1600) / 260)}, (_, i) => (
|
|
169
|
+
<rect key={i} x={-800 + i * 260 - ((t * 0) % 260)} y={suelo + 210} width={140} height={16} rx={8} fill="#F2D04A" />
|
|
170
|
+
))}
|
|
171
|
+
{[0.15, 0.55, 0.92].map((f, i) => (
|
|
172
|
+
<g key={i} transform={`translate(${ancho * f} ${suelo - 120})`}>
|
|
173
|
+
<rect x={-8} y={-620} width={16} height={620} fill="#5A5F6B" />
|
|
174
|
+
<rect x={-8} y={-620} width={110} height={12} fill="#5A5F6B" />
|
|
175
|
+
<ellipse cx={100} cy={-602} rx={24} ry={10} fill={noche ? '#FFE9A8' : '#C9CED6'} />
|
|
176
|
+
</g>
|
|
177
|
+
))}
|
|
178
|
+
</g>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/** Oficina: pared clara, escritorio con monitor, planta y repisa. */
|
|
183
|
+
export const Oficina: React.FC<Base> = ({ancho = 2160, suelo = 1500, t = 0}) => (
|
|
184
|
+
<g>
|
|
185
|
+
<rect x={-800} y={-800} width={ancho + 1600} height={suelo + 800} fill="#E8E4DC" />
|
|
186
|
+
<rect x={-800} y={suelo} width={ancho + 1600} height={900} fill="#B9A58A" />
|
|
187
|
+
<rect x={-800} y={suelo - 10} width={ancho + 1600} height={10} fill="#9C8A70" />
|
|
188
|
+
<g transform={`translate(${ancho * 0.62} ${suelo})`}>
|
|
189
|
+
<rect x={-360} y={-420} width={720} height={36} rx={8} fill="#8A5A3B" />
|
|
190
|
+
<rect x={-330} y={-384} width={30} height={384} fill="#6B4630" />
|
|
191
|
+
<rect x={300} y={-384} width={30} height={384} fill="#6B4630" />
|
|
192
|
+
<rect x={-150} y={-720} width={300} height={200} rx={14} fill="#22252E" />
|
|
193
|
+
<rect x={-136} y={-706} width={272} height={172} rx={8} fill="#3B7BE0" opacity={0.85 + 0.1 * Math.sin(t * 2)} />
|
|
194
|
+
<rect x={-20} y={-520} width={40} height={100} fill="#22252E" />
|
|
195
|
+
<rect x={-90} y={-430} width={180} height={14} rx={5} fill="#22252E" />
|
|
196
|
+
</g>
|
|
197
|
+
<g transform={`translate(${ancho * 0.2} ${suelo})`}>
|
|
198
|
+
<path d="M -70 0 L 70 0 L 55 -150 L -55 -150 Z" fill="#C9653E" />
|
|
199
|
+
{[-50, -20, 10, 40].map((x, i) => (
|
|
200
|
+
<ellipse key={i} cx={x} cy={-230 - (i % 2) * 40} rx={36} ry={90} fill="#3E8A55" transform={`rotate(${(i - 1.5) * 18} ${x} -160)`} />
|
|
201
|
+
))}
|
|
202
|
+
</g>
|
|
203
|
+
<g transform={`translate(${ancho * 0.3} ${suelo - 1100})`}>
|
|
204
|
+
<rect x={-200} y={0} width={400} height={20} fill="#8A5A3B" />
|
|
205
|
+
{['#E04A4A', '#2F6FE0', '#F2B632', '#2FA36B'].map((c, i) => (
|
|
206
|
+
<rect key={i} x={-180 + i * 50} y={-130 + (i % 2) * 20} width={40} height={130 - (i % 2) * 20} fill={c} />
|
|
207
|
+
))}
|
|
208
|
+
</g>
|
|
209
|
+
</g>
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
/** Mar: superficie con olas o fondo marino con rayos de luz y burbujas (`profundo`). */
|
|
213
|
+
export const Mar: React.FC<Base & {profundo?: boolean}> = ({ancho = 2160, suelo = 1500, t = 0, camara, semilla = 0, profundo = false}) => {
|
|
214
|
+
if (profundo)
|
|
215
|
+
return (
|
|
216
|
+
<g>
|
|
217
|
+
<Cielo id="mar-fondo" colores={['#0E5AA0', '#073A70', '#031A38']} />
|
|
218
|
+
<g style={{mixBlendMode: 'screen'}} opacity={0.18} filter="url(#kit-lejos)">
|
|
219
|
+
{Array.from({length: 9}, (_, i) => {
|
|
220
|
+
const x = -300 + i * ((ancho + 600) / 9) + Math.sin(t * 0.4 + i) * 40;
|
|
221
|
+
return <polygon key={i} points={`${x},-400 ${x + 90},-400 ${x + 260},${suelo + 200} ${x + 60},${suelo + 200}`} fill="#BFE3FF" />;
|
|
222
|
+
})}
|
|
223
|
+
</g>
|
|
224
|
+
{Array.from({length: 40}, (_, i) => {
|
|
225
|
+
const s = semilla + i;
|
|
226
|
+
const y = ((suelo + 300 - ((t * (40 + azarN(s) * 60) + azarN(s + 1) * 2000) % 2400)) as number);
|
|
227
|
+
return <circle key={i} cx={azarN(s + 2) * ancho + Math.sin(t + i) * 12} cy={y} r={3 + azarN(s + 3) * 7} fill="none" stroke="rgba(200,235,255,0.5)" strokeWidth={2} />;
|
|
228
|
+
})}
|
|
229
|
+
<path d={`M -800 ${suelo + 120} ${Array.from({length: 30}, (_, i) => `L ${-800 + i * 140} ${suelo + 100 + Math.sin(i * 1.3) * 30}`).join(' ')} L ${ancho + 800} ${suelo + 900} L -800 ${suelo + 900} Z`} fill="#0A2440" />
|
|
230
|
+
</g>
|
|
231
|
+
);
|
|
232
|
+
const ola = (y: number, amp: number, fase: number, color: string) => {
|
|
233
|
+
let d = `M -800 ${y}`;
|
|
234
|
+
for (let x = -800; x <= ancho + 800; x += 40) d += ` L ${x} ${y + Math.sin(x / 120 + t * 1.6 + fase) * amp}`;
|
|
235
|
+
return <path d={`${d} L ${ancho + 800} ${y + 1200} L -800 ${y + 1200} Z`} fill={color} />;
|
|
236
|
+
};
|
|
237
|
+
return (
|
|
238
|
+
<g>
|
|
239
|
+
<Paralaje camara={camara} profundidad={0.9}>
|
|
240
|
+
<Cielo id="mar-cielo" colores={['#7EC8F2', '#CDEBFA']} />
|
|
241
|
+
<circle cx={ancho * 0.25} cy={420} r={100} fill="#FFF1A8" />
|
|
242
|
+
</Paralaje>
|
|
243
|
+
{ola(suelo - 180, 10, 0, '#4FA7E0')}
|
|
244
|
+
{ola(suelo - 80, 16, 1.4, '#2E86C9')}
|
|
245
|
+
{ola(suelo + 40, 22, 2.6, '#1E6AA8')}
|
|
246
|
+
</g>
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export {alturaSuelo};
|
package/remotion/kit/index.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
// Kits de Morfeo: piezas para armar las escenas a medida de un video dentro de tus `componentes`.
|
|
2
2
|
// import {Cielo, Pinos, Paralaje} from 'morfeo/kit'; (2D ilustrado, dentro de un <svg>)
|
|
3
|
-
// import {
|
|
3
|
+
// import {Persona2D, Multitud, Globo, CiudadNoche, Cuarto} from 'morfeo/kit'; (personajes y escenarios 2D)
|
|
4
|
+
// import {Lienzo3D, Persona3D, Ciudad, Terreno, Tubo, Mesa, Pila} from 'morfeo/kit'; (3D: de día o con luz cálida)
|
|
5
|
+
// import {Maqueta, Modelo, Persona, Coche, Trafico, Corte} from 'morfeo/kit'; (diorama low-poly)
|
|
6
|
+
// import {GraficaBarras, GraficaLinea, Comparacion, Lupa} from 'morfeo/kit'; (datos 2D)
|
|
4
7
|
export * from './ilustrado';
|
|
8
|
+
export * from './personas';
|
|
9
|
+
export * from './escenarios2d';
|
|
5
10
|
export * from './tres';
|
|
6
|
-
export
|
|
11
|
+
export * from './mundo3d';
|
|
12
|
+
export * from './datos';
|
|
7
13
|
export * from './diorama';
|
|
14
|
+
export {clamp, easeIO, easeOut, easeOutExpo, backOut, resorte, ruido, lerp, mezclaColor} from '../estilo';
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// Más piezas 3D para `Lienzo3D` (kit/tres.tsx): personas low-poly con cara y expresiones (Persona3D), ciudad, cuarto y camino.
|
|
2
|
+
// `Persona` (kit/diorama.tsx) es la figura de maqueta (casco, chaleco); `Persona3D` es la de historias, con expresiones.
|
|
3
|
+
// Mismo estilo que Arbol/Casa: geometría facetada y materiales mate. Todo depende de props y de `t`.
|
|
4
|
+
//
|
|
5
|
+
// <Persona3D pos={[0, 0, 2]} t={t} ropa="#2F6FE0" expresion="sorpresa" pose="señalar" />
|
|
6
|
+
// <Ciudad t={t} noche /> <Cuarto3D /> <Camino largo={30} /> (el coche es `Coche` de kit/diorama.tsx)
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import * as THREE from 'three';
|
|
9
|
+
import {azarN} from './ilustrado';
|
|
10
|
+
import type {Expresion, Pose} from './personas';
|
|
11
|
+
import {grupo, type Pieza, type V3} from './tres';
|
|
12
|
+
|
|
13
|
+
const Mate: React.FC<{c: string; r?: number; m?: number; e?: number}> = ({c, r = 0.8, m = 0, e = 0}) => (
|
|
14
|
+
<meshStandardMaterial color={c} roughness={r} metalness={m} emissive={e ? c : '#000000'} emissiveIntensity={e} flatShading />
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
/** Ángulos (grados) de brazos y piernas por pose; los brazos rotan en el plano del cuerpo (z) y hacia adelante (x). */
|
|
18
|
+
function pose3d(pose: Pose, t: number, fase: number) {
|
|
19
|
+
switch (pose) {
|
|
20
|
+
case 'caminar': {
|
|
21
|
+
const p = Math.sin(t * 7 + fase);
|
|
22
|
+
return {bi: [p * 30, 8], bd: [-p * 30, 8], pi: -p * 28, pd: p * 28, sube: Math.abs(Math.cos(t * 7 + fase)) * 0.05};
|
|
23
|
+
}
|
|
24
|
+
case 'señalar':
|
|
25
|
+
return {bi: [0, 6], bd: [-80, 10], pi: 0, pd: 0, sube: 0};
|
|
26
|
+
case 'brazosArriba':
|
|
27
|
+
return {bi: [0, 160 + Math.sin(t * 6 + fase) * 10], bd: [0, 160 + Math.sin(t * 6 + fase + 1) * 10], pi: 0, pd: 0, sube: Math.abs(Math.sin(t * 6 + fase)) * 0.06};
|
|
28
|
+
case 'pensar':
|
|
29
|
+
return {bi: [-40, 12], bd: [-110, 20], pi: 0, pd: 0, sube: 0};
|
|
30
|
+
case 'cruzado':
|
|
31
|
+
return {bi: [-75, 35], bd: [-75, 35], pi: 0, pd: 0, sube: 0};
|
|
32
|
+
case 'sentado':
|
|
33
|
+
return {bi: [-45, 10], bd: [-45, 10], pi: -85, pd: -85, sube: -0.38};
|
|
34
|
+
default:
|
|
35
|
+
return {bi: [Math.sin(t * 1.5 + fase) * 3, 6], bd: [-Math.sin(t * 1.5 + fase) * 3, 6], pi: 0, pd: 0, sube: 0};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Persona low-poly de ~1.8 unidades de alto (pies en y = 0). Cara con ojos y boca según `expresion`;
|
|
41
|
+
* `habla` = intervalos [inicio, fin] en segundos en que mueve la boca.
|
|
42
|
+
*/
|
|
43
|
+
export const Persona3D: React.FC<
|
|
44
|
+
Pieza & {
|
|
45
|
+
t?: number;
|
|
46
|
+
piel?: string;
|
|
47
|
+
pelo?: string;
|
|
48
|
+
ropa?: string;
|
|
49
|
+
pantalon?: string;
|
|
50
|
+
expresion?: Expresion;
|
|
51
|
+
pose?: Pose;
|
|
52
|
+
estados?: {en: number; expresion?: Expresion; pose?: Pose}[];
|
|
53
|
+
habla?: [number, number][];
|
|
54
|
+
semilla?: number;
|
|
55
|
+
cabezaGrande?: boolean;
|
|
56
|
+
}
|
|
57
|
+
> = ({t = 0, piel = '#E8B48F', pelo = '#2B1B12', ropa = '#2F6FE0', pantalon = '#2E3A59', expresion: e0 = 'neutral', pose: p0 = 'parado', estados = [], habla = [], semilla = 0, cabezaGrande = true, ...p}) => {
|
|
58
|
+
let expresion = e0;
|
|
59
|
+
let pose = p0;
|
|
60
|
+
for (const e of [...estados].sort((a, b) => a.en - b.en))
|
|
61
|
+
if (t >= e.en) {
|
|
62
|
+
if (e.expresion) expresion = e.expresion;
|
|
63
|
+
if (e.pose) pose = e.pose;
|
|
64
|
+
}
|
|
65
|
+
const fase = azarN(semilla + 5) * 6.28;
|
|
66
|
+
const P = pose3d(pose, t, fase);
|
|
67
|
+
const R = Math.PI / 180;
|
|
68
|
+
const hc = cabezaGrande ? 0.36 : 0.26; // radio de la cabeza
|
|
69
|
+
const parpadea = (t + azarN(semilla) * 3) % 3.4 < 0.12 || expresion === 'dormido';
|
|
70
|
+
const hablando = habla.some(([a, b]) => t >= a && t <= b);
|
|
71
|
+
const boca = hablando ? 0.03 + 0.05 * Math.abs(Math.sin(t * 13)) : expresion === 'sorpresa' || expresion === 'miedo' ? 0.06 : 0.018;
|
|
72
|
+
const ancho = expresion === 'feliz' ? 0.13 : expresion === 'triste' || expresion === 'enojado' ? 0.09 : 0.08;
|
|
73
|
+
const curva = expresion === 'feliz' ? -0.02 : expresion === 'triste' ? 0.02 : 0;
|
|
74
|
+
const ceja = expresion === 'enojado' ? 0.35 : expresion === 'triste' || expresion === 'miedo' ? -0.35 : 0;
|
|
75
|
+
const brazo = (lado: 1 | -1, [adelante, afuera]: number[]) => (
|
|
76
|
+
<group position={[lado * 0.27, 1.12, 0]} rotation={[adelante * R, 0, lado * afuera * R]}>
|
|
77
|
+
<mesh position={[0, -0.27, 0]} castShadow>
|
|
78
|
+
<capsuleGeometry args={[0.075, 0.42, 4, 8]} />
|
|
79
|
+
<Mate c={ropa} />
|
|
80
|
+
</mesh>
|
|
81
|
+
<mesh position={[0, -0.55, 0]} castShadow>
|
|
82
|
+
<sphereGeometry args={[0.08, 10, 8]} />
|
|
83
|
+
<Mate c={piel} />
|
|
84
|
+
</mesh>
|
|
85
|
+
</group>
|
|
86
|
+
);
|
|
87
|
+
const pierna = (lado: 1 | -1, ang: number) => (
|
|
88
|
+
<group position={[lado * 0.11, 0.62, 0]} rotation={[ang * R, 0, 0]}>
|
|
89
|
+
<mesh position={[0, -0.3, 0]} castShadow>
|
|
90
|
+
<capsuleGeometry args={[0.09, 0.44, 4, 8]} />
|
|
91
|
+
<Mate c={pantalon} />
|
|
92
|
+
</mesh>
|
|
93
|
+
<mesh position={[0, -0.6, 0.06]} castShadow>
|
|
94
|
+
<boxGeometry args={[0.16, 0.08, 0.26]} />
|
|
95
|
+
<Mate c="#2A2420" />
|
|
96
|
+
</mesh>
|
|
97
|
+
</group>
|
|
98
|
+
);
|
|
99
|
+
return (
|
|
100
|
+
<group {...grupo(p)}>
|
|
101
|
+
<group position={[0, P.sube, 0]}>
|
|
102
|
+
{pierna(-1, P.pi)}
|
|
103
|
+
{pierna(1, P.pd)}
|
|
104
|
+
<mesh position={[0, 0.92, 0]} castShadow>
|
|
105
|
+
<capsuleGeometry args={[0.24, 0.4, 4, 10]} />
|
|
106
|
+
<Mate c={ropa} />
|
|
107
|
+
</mesh>
|
|
108
|
+
{brazo(-1, P.bi)}
|
|
109
|
+
{brazo(1, P.bd)}
|
|
110
|
+
<group position={[0, 1.38 + hc * 0.75, 0]} rotation={[0, Math.sin(t * 0.9 + fase) * 0.12, pose === 'pensar' ? -0.12 : 0]}>
|
|
111
|
+
<mesh castShadow>
|
|
112
|
+
<icosahedronGeometry args={[hc, 1]} />
|
|
113
|
+
<Mate c={piel} />
|
|
114
|
+
</mesh>
|
|
115
|
+
{/* pelo: casquete encima */}
|
|
116
|
+
<mesh position={[0, hc * 0.28, -hc * 0.06]} scale={[1.04, 0.72, 1.04]} castShadow>
|
|
117
|
+
<icosahedronGeometry args={[hc, 1]} />
|
|
118
|
+
<Mate c={pelo} />
|
|
119
|
+
</mesh>
|
|
120
|
+
{/* ojos */}
|
|
121
|
+
{[-1, 1].map((l) => (
|
|
122
|
+
<group key={l} position={[l * hc * 0.36, hc * 0.08, hc * 0.9]}>
|
|
123
|
+
{parpadea ? (
|
|
124
|
+
<mesh scale={[1, 0.18, 0.4]}>
|
|
125
|
+
<sphereGeometry args={[hc * 0.16, 10, 8]} />
|
|
126
|
+
<Mate c="#1E1410" />
|
|
127
|
+
</mesh>
|
|
128
|
+
) : (
|
|
129
|
+
<>
|
|
130
|
+
<mesh scale={[1, 1.15, 0.45]}>
|
|
131
|
+
<sphereGeometry args={[hc * 0.19, 12, 10]} />
|
|
132
|
+
<Mate c="#FFFFFF" r={0.4} />
|
|
133
|
+
</mesh>
|
|
134
|
+
<mesh position={[0, -hc * 0.01, hc * 0.07]} scale={[1, 1, 0.5]}>
|
|
135
|
+
<sphereGeometry args={[hc * (expresion === 'sorpresa' || expresion === 'miedo' ? 0.07 : 0.1), 10, 8]} />
|
|
136
|
+
<Mate c="#1E1410" r={0.3} />
|
|
137
|
+
</mesh>
|
|
138
|
+
</>
|
|
139
|
+
)}
|
|
140
|
+
<mesh position={[0, hc * 0.27, hc * 0.03]} rotation={[0, 0, -l * ceja]}>
|
|
141
|
+
<boxGeometry args={[hc * 0.32, hc * 0.06, hc * 0.06]} />
|
|
142
|
+
<Mate c={pelo} />
|
|
143
|
+
</mesh>
|
|
144
|
+
</group>
|
|
145
|
+
))}
|
|
146
|
+
{/* boca */}
|
|
147
|
+
<mesh position={[0, -hc * 0.38 + curva, hc * 0.92]} rotation={[0, 0, 0]}>
|
|
148
|
+
<boxGeometry args={[ancho * (hc / 0.36), boca * (hc / 0.36) * 1.4, 0.03]} />
|
|
149
|
+
<Mate c="#5A1E1E" />
|
|
150
|
+
</mesh>
|
|
151
|
+
</group>
|
|
152
|
+
</group>
|
|
153
|
+
</group>
|
|
154
|
+
);
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/** Ciudad: manzanas de edificios con ventanas (encendidas de noche), en una cuadrícula alrededor del centro. */
|
|
158
|
+
export const Ciudad: React.FC<Pieza & {t?: number; noche?: boolean; filas?: number; columnas?: number; separacion?: number; semilla?: number; hueco?: number}> = ({t = 0, noche = false, filas = 4, columnas = 6, separacion = 4.2, semilla = 0, hueco = 1, ...p}) => {
|
|
159
|
+
const colores = noche ? ['#2A2F55', '#323A66', '#262B4A'] : ['#E8DCC8', '#CFD8E3', '#F2E6D0', '#D9C7B0'];
|
|
160
|
+
return (
|
|
161
|
+
<group {...grupo(p)}>
|
|
162
|
+
{Array.from({length: filas * columnas}, (_, i) => {
|
|
163
|
+
const f = Math.floor(i / columnas);
|
|
164
|
+
const c = i % columnas;
|
|
165
|
+
// deja libre la calle del centro (columna central) para la acción
|
|
166
|
+
if (Math.abs(c - (columnas - 1) / 2) < hueco * 0.5) return null;
|
|
167
|
+
const s = semilla + i * 7;
|
|
168
|
+
const alto = 2 + azarN(s) * 6;
|
|
169
|
+
const w = 2.2 + azarN(s + 1) * 1.2;
|
|
170
|
+
const x = (c - (columnas - 1) / 2) * separacion;
|
|
171
|
+
const z = -f * separacion - 2;
|
|
172
|
+
const luz = noche ? 0.5 + 0.5 * Math.sin(t * 0.4 + i) : 0;
|
|
173
|
+
return (
|
|
174
|
+
<group key={i} position={[x, 0, z]}>
|
|
175
|
+
<mesh position={[0, alto / 2, 0]} castShadow receiveShadow>
|
|
176
|
+
<boxGeometry args={[w, alto, w]} />
|
|
177
|
+
<Mate c={colores[i % colores.length]} />
|
|
178
|
+
</mesh>
|
|
179
|
+
{Array.from({length: Math.floor(alto / 0.7)}, (_, k) => (
|
|
180
|
+
<mesh key={k} position={[0, 0.5 + k * 0.7, w / 2 + 0.01]}>
|
|
181
|
+
<boxGeometry args={[w * 0.75, 0.28, 0.02]} />
|
|
182
|
+
<Mate c={noche ? (azarN(s + k) > 0.45 ? '#FFD27A' : '#1A1E36') : '#8EC5E8'} e={noche && azarN(s + k) > 0.45 ? 0.6 + luz * 0.3 : 0} r={0.3} />
|
|
183
|
+
</mesh>
|
|
184
|
+
))}
|
|
185
|
+
</group>
|
|
186
|
+
);
|
|
187
|
+
})}
|
|
188
|
+
<mesh position={[0, 0, -filas * separacion * 0.5]} rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
|
|
189
|
+
<planeGeometry args={[columnas * separacion + 20, filas * separacion + 30]} />
|
|
190
|
+
<Mate c={noche ? '#1E2030' : '#9AA0A8'} />
|
|
191
|
+
</mesh>
|
|
192
|
+
</group>
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/** Calle recta a lo largo de z, con líneas pintadas y banquetas. */
|
|
197
|
+
export const Camino: React.FC<Pieza & {largo?: number; ancho?: number; color?: string}> = ({largo = 30, ancho = 4, color = '#3E4250', ...p}) => (
|
|
198
|
+
<group {...grupo(p)}>
|
|
199
|
+
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.01, -largo / 2 + 4]} receiveShadow>
|
|
200
|
+
<planeGeometry args={[ancho, largo]} />
|
|
201
|
+
<Mate c={color} />
|
|
202
|
+
</mesh>
|
|
203
|
+
{Array.from({length: Math.floor(largo / 2.2)}, (_, i) => (
|
|
204
|
+
<mesh key={i} rotation={[-Math.PI / 2, 0, 0]} position={[0, 0.02, 3 - i * 2.2]}>
|
|
205
|
+
<planeGeometry args={[0.14, 1.1]} />
|
|
206
|
+
<Mate c="#F2D04A" />
|
|
207
|
+
</mesh>
|
|
208
|
+
))}
|
|
209
|
+
{[-1, 1].map((l) => (
|
|
210
|
+
<mesh key={l} position={[l * (ancho / 2 + 0.5), 0.08, -largo / 2 + 4]} receiveShadow castShadow>
|
|
211
|
+
<boxGeometry args={[1, 0.16, largo]} />
|
|
212
|
+
<Mate c="#C9C4BA" />
|
|
213
|
+
</mesh>
|
|
214
|
+
))}
|
|
215
|
+
</group>
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
/** Cuarto (interior abierto hacia la cámara): piso, dos paredes, ventana y lámpara de techo. */
|
|
219
|
+
export const Cuarto3D: React.FC<Pieza & {ancho?: number; fondo?: number; alto?: number; pared?: string; piso?: string; noche?: boolean}> = ({ancho = 10, fondo = 8, alto = 4.5, pared = '#2E5A4C', piso = '#7A4A2A', noche = true, ...p}) => (
|
|
220
|
+
<group {...grupo(p)}>
|
|
221
|
+
<mesh rotation={[-Math.PI / 2, 0, 0]} receiveShadow>
|
|
222
|
+
<planeGeometry args={[ancho, fondo]} />
|
|
223
|
+
<Mate c={piso} />
|
|
224
|
+
</mesh>
|
|
225
|
+
<mesh position={[0, alto / 2, -fondo / 2]} receiveShadow>
|
|
226
|
+
<boxGeometry args={[ancho, alto, 0.2]} />
|
|
227
|
+
<Mate c={pared} />
|
|
228
|
+
</mesh>
|
|
229
|
+
<mesh position={[-ancho / 2, alto / 2, 0]} receiveShadow>
|
|
230
|
+
<boxGeometry args={[0.2, alto, fondo]} />
|
|
231
|
+
<Mate c={new THREE.Color(pared).multiplyScalar(0.85).getStyle()} />
|
|
232
|
+
</mesh>
|
|
233
|
+
<group position={[ancho * 0.2, alto * 0.55, -fondo / 2 + 0.12]}>
|
|
234
|
+
<mesh>
|
|
235
|
+
<boxGeometry args={[1.8, 1.5, 0.05]} />
|
|
236
|
+
<Mate c="#E9E1D2" />
|
|
237
|
+
</mesh>
|
|
238
|
+
<mesh position={[0, 0, 0.03]}>
|
|
239
|
+
<boxGeometry args={[1.6, 1.3, 0.02]} />
|
|
240
|
+
<Mate c={noche ? '#1A2350' : '#9FD4F5'} e={noche ? 0.1 : 0.3} />
|
|
241
|
+
</mesh>
|
|
242
|
+
</group>
|
|
243
|
+
<mesh position={[0, alto - 0.1, 0]}>
|
|
244
|
+
<cylinderGeometry args={[0.35, 0.5, 0.25, 16]} />
|
|
245
|
+
<Mate c="#F2D49B" e={0.5} />
|
|
246
|
+
</mesh>
|
|
247
|
+
<pointLight position={[0, alto - 0.5, 0]} intensity={noche ? 6 : 2} distance={12} color="#FFD9A0" castShadow />
|
|
248
|
+
</group>
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
export type {V3};
|