primocode 11.3.3 → 11.5.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.
- package/LICENSES/edit2-terceiros.md +31 -0
- package/LICENSES/models-dev-MIT.txt +25 -0
- package/README.md +2 -2
- package/editor/SKILL.md +24 -0
- package/editor/THIRD_PARTY_NOTICES.md +1 -0
- package/editor/assets/baixar.json +9 -1
- package/editor/helpers/df_limpar.py +29 -0
- package/editor/helpers/edit2.py +155 -0
- package/editor/helpers/enquadrar_camera.py +77 -0
- package/editor/helpers/face_layout.py +20 -0
- package/editor/helpers/face_mediapipe.py +147 -0
- package/editor/helpers/instalar_df.sh +7 -0
- package/editor/helpers/legenda_ass.py +124 -0
- package/editor/helpers/look_ffmpeg.py +105 -0
- package/editor/helpers/motor_palavras.py +157 -0
- package/editor/helpers/primeiro_passe.py +214 -0
- package/editor/helpers/som_edit2.py +119 -0
- package/editor/helpers/transcribe.py +31 -2
- package/editor/pyproject.toml +9 -0
- package/editor/references/edit2.md +22 -0
- package/editor/uv.lock +3342 -214
- package/kits/3d/LICENSES/README.md +11 -0
- package/kits/3d/animacoes.js +87 -0
- package/kits/3d/base.js +44 -0
- package/kits/3d/catalogo.js +38 -0
- package/kits/3d/cenario.js +162 -0
- package/kits/3d/exemplos/asfalto.mjs +91 -0
- package/kits/3d/maquinas.js +97 -0
- package/kits/3d/montador.js +142 -0
- package/kits/3d/package.json +1 -0
- package/kits/3d/personagem.js +86 -0
- package/kits/3d/remotion/Maquete.jsx +42 -0
- package/kits/3d/remotion/Root.jsx +10 -0
- package/kits/3d/render.mjs +73 -0
- package/lib/cli/comando-api.js +30 -8
- package/lib/cli/modelos-lista.js +58 -0
- package/lib/cli/sessao.js +11 -0
- package/lib/cli/subcomandos.js +3 -2
- package/lib/outra-pasta.js +1 -1
- package/lib/permissao-sistema.js +60 -0
- package/lib/permission/perigo.js +2 -1
- package/lib/permission/permissao.js +1 -0
- package/lib/permission/sem-perguntar.js +29 -0
- package/lib/plugin/desktop.js +5 -4
- package/lib/plugin/estudio/cota.js +4 -2
- package/lib/plugin/estudio/dialogo3d.js +71 -0
- package/lib/plugin/estudio/effectcraft.js +1 -1
- package/lib/plugin/estudio/glifos.js +2 -0
- package/lib/plugin/estudio/gsap-local.js +43 -0
- package/lib/plugin/estudio/index.js +63 -10
- package/lib/plugin/estudio/kit-ae/base.jsx +1 -1
- package/lib/plugin/estudio/kit-ae/cenas.jsx +7 -4
- package/lib/plugin/estudio/kit-ae/destrava.jsx +211 -0
- package/lib/plugin/estudio/maquina-diretor.js +1 -1
- package/lib/plugin/estudio/maquina-elementos.js +2 -2
- package/lib/plugin/estudio/maquina.js +26 -8
- package/lib/plugin/estudio/motion-plano/ajustar.js +139 -0
- package/lib/plugin/estudio/motion-plano/critico.js +59 -0
- package/lib/plugin/estudio/motion-plano/estrutura.js +111 -0
- package/lib/plugin/estudio/motion-plano/fatos.js +137 -0
- package/lib/plugin/estudio/motion-plano/index.js +75 -0
- package/lib/plugin/estudio/motion-plano/llm.js +59 -0
- package/lib/plugin/estudio/motion-plano/motor.js +50 -0
- package/lib/plugin/estudio/motion-plano/passos.js +38 -0
- package/lib/plugin/estudio/motion-plano/pele.js +36 -0
- package/lib/plugin/estudio/motion-plano/plano.js +152 -0
- package/lib/plugin/estudio/motion-plano/regua.js +100 -0
- package/lib/plugin/estudio/personagem.js +3 -1
- package/lib/plugin/estudio/referencias.js +1 -1
- package/lib/provider/catalogo.js +161 -0
- package/lib/provider/local/catalogo-embutido.json.gz +0 -0
- package/lib/provider/local/cerebro.json +1 -1
- package/lib/provider/local/entrada.js +10 -0
- package/lib/provider/local/motor.js +140 -27
- package/lib/provider/local/orcamento.js +193 -0
- package/lib/provider/local/provedor.js +127 -14
- package/lib/provider/local/recorte.js +170 -0
- package/lib/provider/local/revezamento.js +121 -0
- package/lib/session/runner/act.js +28 -3
- package/lib/skill/catalogo.js +9 -1
- package/lib/tool/reparar-entrada.js +1 -0
- package/lib/tool/tools.js +58 -1
- package/lib/tui/ui.js +1 -1
- package/package.json +2 -2
- package/vendor/fontes/ttf/ORIGEM.txt +1 -0
- package/vendor/fontes/ttf/inter-800.ttf +0 -0
- package/vendor/gsap/ORIGEM.txt +6 -0
- package/vendor/gsap/gsap.min.js +11 -0
- package/voz/primo_voz/chat.html +206 -5
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Licenças das peças 3D (kits/3d)
|
|
2
|
+
|
|
3
|
+
Todas as peças desta biblioteca são **código nosso** (Three.js, formas procedurais): personagem,
|
|
4
|
+
animações, cenário e máquinas. Não há modelo, textura nem imagem de terceiros.
|
|
5
|
+
|
|
6
|
+
Pacotes CC0 (Kenney, Quaternius) **ainda não foram usados**. Se um entrar, o arquivo de licença dele
|
|
7
|
+
fica nesta pasta, com o link de origem, antes do commit.
|
|
8
|
+
|
|
9
|
+
O pack do vídeo de referência (de outra pessoa) **não entra**.
|
|
10
|
+
|
|
11
|
+
Dependência de código: three (MIT), em `LICENSES/three-MIT.txt` na raiz do repo.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/* animacoes.js — as animações universais do boneco. Cada uma é (p, t, op) e só
|
|
2
|
+
* escreve rotações: nada guarda estado. Serve em qualquer personagem.
|
|
3
|
+
* parado · andar · correr · apontar · pensar · comemorar · surpreso · falar */
|
|
4
|
+
import { clamp } from './base.js';
|
|
5
|
+
|
|
6
|
+
const S = Math.sin;
|
|
7
|
+
export const piscar = (t) => { const c = t % 3.1; return c < 0.12 ? 0.1 : 1; };
|
|
8
|
+
|
|
9
|
+
function respirar(p, t) {
|
|
10
|
+
p.torso.rotation.x = S(t * 2) * 0.015; p.torso.position.y = S(t * 2) * 0.006;
|
|
11
|
+
p.cabeca.rotation.y = S(t * 0.7) * 0.08;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ciclo(p, t, vel, amp, bracoAmp, inclina) {
|
|
15
|
+
const f = t * vel;
|
|
16
|
+
p.bracoE.coxa.rotation.x = S(f) * amp; p.bracoD.coxa.rotation.x = -S(f) * amp;
|
|
17
|
+
p.bracoE.joelho.rotation.x = Math.max(0, -S(f)) * amp * 1.2; p.bracoD.joelho.rotation.x = Math.max(0, S(f)) * amp * 1.2;
|
|
18
|
+
p.bracoE.ombro.rotation.x = -S(f) * bracoAmp; p.bracoD.ombro.rotation.x = S(f) * bracoAmp;
|
|
19
|
+
p.bracoE.cot.rotation.x = -0.4 - Math.max(0, S(f)) * bracoAmp * 0.6; p.bracoD.cot.rotation.x = -0.4 - Math.max(0, -S(f)) * bracoAmp * 0.6;
|
|
20
|
+
p.quadril.position.y = p.brancoY + Math.abs(S(f)) * 0.05 * (amp / 0.6);
|
|
21
|
+
p.torso.rotation.x = inclina; p.torso.rotation.y = S(f) * 0.12;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const ANIMACOES = {
|
|
25
|
+
parado(p, t) { respirar(p, t); },
|
|
26
|
+
|
|
27
|
+
andar(p, t, op) { ciclo(p, t, (op.vel || 1) * 7, 0.55, 0.5, 0.05); p.cabeca.rotation.x = -0.03; },
|
|
28
|
+
|
|
29
|
+
correr(p, t, op) { ciclo(p, t, (op.vel || 1) * 11, 0.95, 1.0, 0.28); p.cabeca.rotation.x = -0.2; },
|
|
30
|
+
|
|
31
|
+
/** Braço direito estica para frente (o corpo já está virado para o alvo). */
|
|
32
|
+
apontar(p, t) {
|
|
33
|
+
respirar(p, t);
|
|
34
|
+
p.bracoD.ombro.rotation.x = -1.45 + S(t * 3) * 0.04; p.bracoD.ombro.rotation.z = -0.1; p.bracoD.cot.rotation.x = -0.1;
|
|
35
|
+
p.bracoE.ombro.rotation.x = 0.1; p.torso.rotation.y = -0.18; p.cabeca.rotation.y = 0.2;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
/** Mão no queixo, cabeça de lado, balança de leve. */
|
|
39
|
+
pensar(p, t) {
|
|
40
|
+
respirar(p, t);
|
|
41
|
+
p.bracoD.ombro.rotation.x = -0.9; p.bracoD.ombro.rotation.z = 0.5; p.bracoD.cot.rotation.x = -1.9;
|
|
42
|
+
p.cabeca.rotation.z = 0.14 + S(t * 1.4) * 0.03; p.cabeca.rotation.x = -0.1; p.torso.rotation.y = 0.1;
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
/** Pula com os dois braços para cima. */
|
|
46
|
+
comemorar(p, t) {
|
|
47
|
+
const s = Math.abs(S(t * 6));
|
|
48
|
+
p.quadril.position.y = p.brancoY + s * 0.22;
|
|
49
|
+
p.bracoE.ombro.rotation.z = 2.5 + S(t * 12) * 0.2; p.bracoD.ombro.rotation.z = -2.5 - S(t * 12) * 0.2;
|
|
50
|
+
p.bracoE.cot.rotation.z = -0.15; p.bracoD.cot.rotation.z = 0.15;
|
|
51
|
+
p.bracoE.coxa.rotation.x = -s * 0.5; p.bracoD.coxa.rotation.x = -s * 0.5; p.bracoE.joelho.rotation.x = s * 0.8; p.bracoD.joelho.rotation.x = s * 0.8;
|
|
52
|
+
p.cabeca.rotation.x = -0.25; p.torso.rotation.y = S(t * 6) * 0.12;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/** Dá um pulinho para trás, mãos erguidas, boca em "o". */
|
|
56
|
+
surpreso(p, t) {
|
|
57
|
+
const k = clamp(t * 6), tremor = S(t * 40) * 0.01 * (1 - k * 0.5);
|
|
58
|
+
p.quadril.position.y = p.brancoY + S(k * Math.PI) * 0.15; p.quadril.position.z = -0.12 * k + tremor;
|
|
59
|
+
p.torso.rotation.x = -0.2 * k;
|
|
60
|
+
p.bracoE.ombro.rotation.x = -0.9 * k; p.bracoD.ombro.rotation.x = -0.9 * k; p.bracoE.ombro.rotation.z = 0.5 * k; p.bracoD.ombro.rotation.z = -0.5 * k;
|
|
61
|
+
p.bracoE.cot.rotation.x = -0.8 * k; p.bracoD.cot.rotation.x = -0.8 * k;
|
|
62
|
+
p.boca.scale.set(0.8, 0.9, 0.6); p.cabeca.rotation.x = -0.12 * k;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/** Corpo de quem conversa: gesto nas mãos e cabeça viva. A boca vem de `op.boca` (voz) ou de uma sílaba falsa. */
|
|
66
|
+
falar(p, t, op) {
|
|
67
|
+
respirar(p, t);
|
|
68
|
+
const g = S(t * 2.3), g2 = S(t * 3.1 + 1);
|
|
69
|
+
p.bracoD.ombro.rotation.x = -0.5 + g * 0.35; p.bracoD.cot.rotation.x = -0.9 + g2 * 0.4;
|
|
70
|
+
p.bracoE.ombro.rotation.x = -0.25 + g2 * 0.2; p.bracoE.cot.rotation.x = -0.6;
|
|
71
|
+
p.cabeca.rotation.x = S(t * 4.7) * 0.05; p.cabeca.rotation.y = S(t * 1.1) * 0.12;
|
|
72
|
+
if (op.boca == null) p.boca.scale.set(1.1, 0.12 + Math.abs(S(t * 13) * S(t * 5.3)) * 0.8, 0.5);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** Abertura da boca (0..1) num instante, dadas as palavras [{de, ate}] (s). Cada palavra abre e fecha em sílabas. */
|
|
77
|
+
export function bocaDe(t, palavras) {
|
|
78
|
+
for (const w of palavras || []) {
|
|
79
|
+
if (t >= w.de && t <= w.ate) {
|
|
80
|
+
const u = (t - w.de) / Math.max(0.05, w.ate - w.de), n = Math.max(1, Math.round((w.ate - w.de) / 0.17));
|
|
81
|
+
return 0.25 + 0.75 * Math.abs(S(u * Math.PI * n));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const NOMES_ANIMACAO = Object.keys(ANIMACOES);
|
package/kits/3d/base.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* base.js — ferramentas comuns da biblioteca 3D (Three.js puro, peça 100% nossa).
|
|
2
|
+
* Estilo: low-poly fofinho. Formas arredondadas, cor chapada, sem textura. */
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
|
|
5
|
+
|
|
6
|
+
export { THREE };
|
|
7
|
+
|
|
8
|
+
const mats = new Map();
|
|
9
|
+
/** Material chapado, guardado por cor: 200 peças dividem poucos materiais. */
|
|
10
|
+
export function mat(cor, { rough = 0.85, emissive = 0 } = {}) {
|
|
11
|
+
const k = `${cor}|${rough}|${emissive}`;
|
|
12
|
+
if (!mats.has(k)) {
|
|
13
|
+
const m = new THREE.MeshStandardMaterial({ color: cor, roughness: rough, metalness: 0.02, flatShading: true });
|
|
14
|
+
if (emissive) { m.emissive = new THREE.Color(cor); m.emissiveIntensity = emissive; }
|
|
15
|
+
mats.set(k, m);
|
|
16
|
+
}
|
|
17
|
+
return mats.get(k);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function malha(geo, cor, opts) {
|
|
21
|
+
const m = new THREE.Mesh(geo, cor && cor.isMaterial ? cor : mat(cor, opts));
|
|
22
|
+
m.castShadow = true; m.receiveShadow = true;
|
|
23
|
+
return m;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Caixa de cantos arredondados (o "fofinho"). */
|
|
27
|
+
export const caixa = (w, h, d, cor, r = Math.min(w, h, d) * 0.22, opts) => malha(new RoundedBoxGeometry(w, h, d, 2, r), cor, opts);
|
|
28
|
+
export const esfera = (r, cor, opts) => malha(new THREE.SphereGeometry(r, 14, 10), cor, opts);
|
|
29
|
+
export const cilindro = (rt, rb, h, cor, seg = 14, opts) => malha(new THREE.CylinderGeometry(rt, rb, h, seg), cor, opts);
|
|
30
|
+
export const cone = (r, h, cor, seg = 8) => malha(new THREE.ConeGeometry(r, h, seg), cor);
|
|
31
|
+
export const capsula = (r, len, cor) => malha(new THREE.CapsuleGeometry(r, len, 3, 8), cor);
|
|
32
|
+
|
|
33
|
+
/** Põe `m` dentro de um grupo e o desloca; devolve o grupo (pivô na origem). */
|
|
34
|
+
export function em(m, x = 0, y = 0, z = 0) { m.position.set(x, y, z); return m; }
|
|
35
|
+
export const grupo = (...filhos) => { const g = new THREE.Group(); filhos.forEach((f) => f && g.add(f)); return g; };
|
|
36
|
+
|
|
37
|
+
export const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
38
|
+
export const lerp = (a, b, k) => a + (b - a) * k;
|
|
39
|
+
/** Suavização padrão: começa e termina devagar. */
|
|
40
|
+
export const suave = (x) => { const t = clamp(x); return t * t * (3 - 2 * t); };
|
|
41
|
+
/** Saída com um leve estouro, para peça que "pula" na cena. */
|
|
42
|
+
export const pop = (x) => { const t = clamp(x); return t >= 1 ? 1 : 1 - Math.pow(1 - t, 3) * Math.cos(t * 9) ; };
|
|
43
|
+
/** Ruído determinístico 0..1 (a cena é função do tempo: nada de Math.random). */
|
|
44
|
+
export const ruido = (n) => { const s = Math.sin(n * 127.1 + 311.7) * 43758.5453; return s - Math.floor(s); };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* catalogo.js — o cardápio de peças. A IA escolhe daqui por `tipo`; o montador
|
|
2
|
+
* chama `criar(tipo, params)`. Peça nova entra aqui e some do "não existe". */
|
|
3
|
+
import { criarPersonagem } from './personagem.js';
|
|
4
|
+
import { NOMES_ANIMACAO } from './animacoes.js';
|
|
5
|
+
import { terreno, arvore, casa, predio, rua, cruzamento, cone_, brita, pista } from './cenario.js';
|
|
6
|
+
import { caminhao, guindaste, rolo } from './maquinas.js';
|
|
7
|
+
|
|
8
|
+
export const CATALOGO = {
|
|
9
|
+
personagem: { fabrica: criarPersonagem, categoria: 'personagem', params: 'pele roupa calca capacete cabelo escala', api: 'animar(nome,t) falar(0..1)', desc: 'boneco articulado, qualquer papel' },
|
|
10
|
+
terreno: { fabrica: terreno, categoria: 'cenario', params: 'w d cor', desc: 'chão de terra' },
|
|
11
|
+
arvore: { fabrica: arvore, categoria: 'cenario', params: 'forma(redonda|pinheiro) escala cor', desc: 'árvore' },
|
|
12
|
+
casa: { fabrica: casa, categoria: 'cenario', params: 'cor telhado escala', desc: 'casa com telhado' },
|
|
13
|
+
predio: { fabrica: predio, categoria: 'cenario', params: 'andares cor larg escala', api: 'subir(0..1)', desc: 'prédio que pode subir andar por andar' },
|
|
14
|
+
rua: { fabrica: rua, categoria: 'cenario', params: 'comp larg faixa', desc: 'pedaço de rua (encaixa pelas pontas, eixo z)' },
|
|
15
|
+
cruzamento: { fabrica: cruzamento, categoria: 'cenario', params: 'larg braco', desc: 'cruzamento com faixas de pedestre' },
|
|
16
|
+
cone: { fabrica: cone_, categoria: 'cenario', params: 'escala', desc: 'cone de obra' },
|
|
17
|
+
brita: { fabrica: brita, categoria: 'processo', params: 'cor raio', api: 'encher(0..1)', desc: 'pilha de brita que cresce' },
|
|
18
|
+
pista: { fabrica: pista, categoria: 'processo', params: 'comp larg', api: 'base(k) massa(k) compactar(0..2) faixas(k)', desc: 'pista em obra com as camadas do asfalto' },
|
|
19
|
+
caminhao: { fabrica: caminhao, categoria: 'maquina', params: 'cor carga cargaCheia', api: 'despejar(0..1) rolar(m)', desc: 'caminhão basculante: a caçamba levanta e a carga cai' },
|
|
20
|
+
guindaste: { fabrica: guindaste, categoria: 'maquina', params: 'cor escala', api: 'girar(rad) lanca(0..1) cabo(m) carga(0|1)', desc: 'guindaste de esteira' },
|
|
21
|
+
rolo: { fabrica: rolo, categoria: 'maquina', params: 'cor', api: 'rolar(m)', desc: 'rolo compactador' },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const ANIMACOES_UNIVERSAIS = NOMES_ANIMACAO;
|
|
25
|
+
|
|
26
|
+
export function criar(tipo, params = {}) {
|
|
27
|
+
const c = CATALOGO[tipo];
|
|
28
|
+
if (!c) throw new Error(`peça "${tipo}" não existe no catálogo (${Object.keys(CATALOGO).join(', ')})`);
|
|
29
|
+
const o = c.fabrica(params);
|
|
30
|
+
o.userData.tipo = tipo; o.userData.categoria = c.categoria;
|
|
31
|
+
return o;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** O cardápio em texto curto, para o prompt da IA que monta a cena. */
|
|
35
|
+
export function cardapio() {
|
|
36
|
+
return Object.entries(CATALOGO).map(([k, c]) => `- ${k} [${c.categoria}] (${c.params})${c.api ? ` api: ${c.api}` : ''}: ${c.desc}`).join('\n')
|
|
37
|
+
+ `\nanimações de personagem: ${NOMES_ANIMACAO.join(', ')}`;
|
|
38
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/* cenario.js — o cenário que encaixa: terreno, casa, prédio, árvore, rua em
|
|
2
|
+
* pedaços, cruzamento, a pista de obra (com as camadas do asfalto) e a pilha de
|
|
3
|
+
* brita. Tudo feito em código, tudo no mesmo estilo fofinho.
|
|
4
|
+
*
|
|
5
|
+
* Convenção: chão em y=0, eixo da rua em z, peças centradas na origem.
|
|
6
|
+
* Peça que tem processo (prédio subindo, massa espalhando) expõe `userData.api`. */
|
|
7
|
+
import { THREE, mat, caixa, esfera, cilindro, cone, grupo, em, clamp, lerp, suave, ruido } from './base.js';
|
|
8
|
+
|
|
9
|
+
export function terreno({ w = 60, d = 60, cor = '#b99362' } = {}) {
|
|
10
|
+
const g = grupo(em(caixa(w, 0.6, d, cor, 0.05), 0, -0.3, 0));
|
|
11
|
+
for (let i = 0; i < 14; i++) { // moitas de terra e pedrinhas, só para não ficar um prato liso
|
|
12
|
+
const m = esfera(0.25 + ruido(i) * 0.3, i % 3 ? '#a98253' : '#9b9b9b'); m.scale.y = 0.45;
|
|
13
|
+
g.add(em(m, (ruido(i + 40) - 0.5) * w * 0.9, 0.04, (ruido(i + 90) - 0.5) * d * 0.9));
|
|
14
|
+
}
|
|
15
|
+
return g;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function arvore({ forma = 'redonda', escala = 1, cor = '#4caf50' } = {}) {
|
|
19
|
+
const g = grupo(em(cilindro(0.14, 0.2, 1.2, '#7b5233', 8), 0, 0.6, 0));
|
|
20
|
+
if (forma === 'pinheiro') {
|
|
21
|
+
[0, 1, 2].forEach((i) => g.add(em(cone(1.0 - i * 0.28, 1.3, '#2e7d4f', 8), 0, 1.5 + i * 0.75, 0)));
|
|
22
|
+
} else {
|
|
23
|
+
g.add(em(esfera(0.95, cor), 0, 1.9, 0)); g.add(em(esfera(0.65, cor), 0.55, 1.5, 0.2)); g.add(em(esfera(0.6, '#5cc060'), -0.5, 1.55, -0.2));
|
|
24
|
+
}
|
|
25
|
+
g.scale.setScalar(escala);
|
|
26
|
+
return g;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function casa({ cor = '#f4d9a8', telhado = '#d1493f', escala = 1 } = {}) {
|
|
30
|
+
const g = grupo(em(caixa(3.2, 2.0, 2.8, cor, 0.08), 0, 1.0, 0));
|
|
31
|
+
const t = cone(2.6, 1.5, telhado, 4); t.rotation.y = Math.PI / 4; t.scale.set(1, 1, 0.88);
|
|
32
|
+
g.add(em(t, 0, 2.7, 0));
|
|
33
|
+
g.add(em(caixa(0.7, 1.2, 0.08, '#7b5233', 0.03), -0.6, 0.6, 1.42));
|
|
34
|
+
[0.9, 2.2].forEach((x) => g.add(em(caixa(0.7, 0.6, 0.08, '#9ed8f5', 0.03), x - 0.5, 1.2, 1.42)));
|
|
35
|
+
g.add(em(caixa(0.4, 0.9, 0.4, '#a9553f', 0.04), 1.0, 3.0, -0.5));
|
|
36
|
+
g.scale.setScalar(escala);
|
|
37
|
+
return g;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Prédio de andares. `api.subir(k)`: de 0 a 1, os andares aparecem de baixo para cima (a obra sobe). */
|
|
41
|
+
export function predio({ andares = 6, cor = '#cfd8e3', larg = 3.4, escala = 1 } = {}) {
|
|
42
|
+
const g = new THREE.Group(), fl = [];
|
|
43
|
+
for (let i = 0; i < andares; i++) {
|
|
44
|
+
const f = grupo(em(caixa(larg, 1.0, larg, i % 2 ? cor : '#bcc8d6', 0.05), 0, 0.5, 0));
|
|
45
|
+
for (let j = -1; j <= 1; j++) {
|
|
46
|
+
f.add(em(caixa(0.7, 0.5, 0.06, '#8fd3ff', 0.02), j * 1.0, 0.55, larg / 2 + 0.01));
|
|
47
|
+
f.add(em(caixa(0.7, 0.5, 0.06, '#8fd3ff', 0.02), j * 1.0, 0.55, -larg / 2 - 0.01));
|
|
48
|
+
}
|
|
49
|
+
f.position.y = i; g.add(f); fl.push(f);
|
|
50
|
+
}
|
|
51
|
+
g.add(em(caixa(larg + 0.3, 0.18, larg + 0.3, '#8895a7', 0.04), 0, andares + 0.09, 0));
|
|
52
|
+
const topo = g.children[g.children.length - 1];
|
|
53
|
+
const api = {
|
|
54
|
+
subir(k) {
|
|
55
|
+
const n = k * andares;
|
|
56
|
+
fl.forEach((f, i) => { const a = clamp(n - i); f.visible = a > 0; f.scale.set(1, Math.max(a, 0.001), 1); });
|
|
57
|
+
topo.position.y = Math.min(n, andares) + 0.09; topo.visible = k > 0;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
api.subir(1);
|
|
61
|
+
g.userData.api = api; g.scale.setScalar(escala);
|
|
62
|
+
return g;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ASFALTO = '#3a3d44', CALCADA = '#c9c6bd';
|
|
66
|
+
|
|
67
|
+
/** Um pedaço de rua reto (eixo z). Encaixa pelas pontas: `comp` de comprimento. */
|
|
68
|
+
export function rua({ comp = 10, larg = 4, faixa = true } = {}) {
|
|
69
|
+
const g = grupo(em(caixa(larg, 0.12, comp, ASFALTO, 0.02), 0, 0.06, 0));
|
|
70
|
+
[-1, 1].forEach((s) => g.add(em(caixa(0.9, 0.2, comp, CALCADA, 0.03), s * (larg / 2 + 0.45), 0.1, 0)));
|
|
71
|
+
if (faixa) for (let z = -comp / 2 + 0.8; z < comp / 2 - 0.4; z += 1.6) g.add(em(caixa(0.14, 0.02, 0.8, '#f3d34a', 0.005), 0, 0.125, z));
|
|
72
|
+
return g;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Cruzamento em cruz: duas pistas e as faixas de pedestre. */
|
|
76
|
+
export function cruzamento({ larg = 4, braco = 5 } = {}) {
|
|
77
|
+
const g = grupo(em(caixa(larg, 0.12, larg + braco * 2, ASFALTO, 0.02), 0, 0.06, 0), em(caixa(larg + braco * 2, 0.12, larg, ASFALTO, 0.02), 0, 0.065, 0));
|
|
78
|
+
for (const [sx, sz] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) {
|
|
79
|
+
for (let i = -3; i <= 3; i++) {
|
|
80
|
+
const f = caixa(sx ? 0.5 : 0.35, 0.02, sx ? 0.35 : 0.5, '#f4f4f0', 0.005);
|
|
81
|
+
g.add(em(f, sx * (larg / 2 + 0.7) + (sz ? i * 0.5 : 0), 0.13, sz * (larg / 2 + 0.7) + (sx ? i * 0.5 : 0)));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
[[1, 1], [1, -1], [-1, 1], [-1, -1]].forEach(([x, z]) => g.add(em(caixa(braco, 0.2, braco, CALCADA, 0.03), x * (larg / 2 + braco / 2), 0.1, z * (larg / 2 + braco / 2))));
|
|
85
|
+
return g;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Cone de obra. */
|
|
89
|
+
export function cone_({ escala = 1 } = {}) {
|
|
90
|
+
const g = grupo(em(caixa(0.45, 0.06, 0.45, '#2b2b2b', 0.02), 0, 0.03, 0), em(cone(0.17, 0.6, '#ff7a1a', 12), 0, 0.36, 0), em(cilindro(0.1, 0.12, 0.08, '#ffffff', 12), 0, 0.33, 0));
|
|
91
|
+
g.scale.setScalar(escala);
|
|
92
|
+
return g;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Pilha de brita. `api.encher(k)`: a pilha cresce de 0 a 1. */
|
|
96
|
+
export function brita({ cor = '#8d8a86', raio = 1.3 } = {}) {
|
|
97
|
+
const g = new THREE.Group(), pedras = [];
|
|
98
|
+
for (let i = 0; i < 26; i++) {
|
|
99
|
+
const a = ruido(i) * Math.PI * 2, r = Math.sqrt(ruido(i + 7)) * raio * 0.9;
|
|
100
|
+
const m = esfera(0.18 + ruido(i + 3) * 0.14, ruido(i + 5) > 0.5 ? cor : '#a09c97'); m.scale.y = 0.75;
|
|
101
|
+
m.position.set(Math.cos(a) * r, Math.max(0.08, (1 - r / raio) * raio * 0.55) , Math.sin(a) * r);
|
|
102
|
+
g.add(m); pedras.push(m);
|
|
103
|
+
}
|
|
104
|
+
g.userData.api = { encher(k) { g.scale.set(lerp(0.2, 1, suave(k)), Math.max(0.001, suave(k)), lerp(0.2, 1, suave(k))); g.visible = k > 0.001; } };
|
|
105
|
+
g.userData.api.encher(1);
|
|
106
|
+
return g;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A pista em obra, de ponta a ponta no eixo z (de -comp/2 a +comp/2), com as
|
|
111
|
+
* camadas do asfalto, cada uma controlada por uma api de 0 a 1:
|
|
112
|
+
* leito (terra batida, sempre) · base(k) brita · massa(k) a massa quente
|
|
113
|
+
* espalhando de -z para +z · compactar(p) o rolo: p 0..1 vai, p 1..2 volta ·
|
|
114
|
+
* faixas(k) pintura. A massa solta vapor enquanto está quente.
|
|
115
|
+
*/
|
|
116
|
+
export function pista({ comp = 16, larg = 4 } = {}) {
|
|
117
|
+
const N = 20, g = new THREE.Group(), lw = comp / N;
|
|
118
|
+
g.add(em(caixa(larg + 0.4, 0.06, comp, '#8a6a45', 0.02), 0, 0.03, 0));
|
|
119
|
+
const camadaBase = caixa(larg, 0.1, comp, '#9a9690', 0.02); camadaBase.position.set(0, 0.11, 0); g.add(camadaBase);
|
|
120
|
+
const fatias = [];
|
|
121
|
+
for (let i = 0; i < N; i++) {
|
|
122
|
+
const m = new THREE.Mesh(new THREE.BoxGeometry(larg, 1, lw * 1.01), new THREE.MeshStandardMaterial({ color: '#4a3b2f', roughness: 0.95, flatShading: true }));
|
|
123
|
+
m.castShadow = m.receiveShadow = true; g.add(m); fatias.push(m);
|
|
124
|
+
}
|
|
125
|
+
const dashes = [];
|
|
126
|
+
for (let i = 0; i < 10; i++) { const d = caixa(0.14, 0.02, 0.9, '#f3d34a', 0.005); d.position.set(0, 0.3, -comp / 2 + (i + 0.5) * comp / 10); g.add(d); dashes.push(d); }
|
|
127
|
+
const borda = [-1, 1].map((s) => { const b = caixa(0.1, 0.02, comp, '#f4f4f0', 0.005); b.position.set(s * (larg / 2 - 0.2), 0.3, 0); g.add(b); return b; });
|
|
128
|
+
const vapor = []; for (let i = 0; i < 16; i++) { const v = esfera(0.3, new THREE.MeshBasicMaterial({ color: '#ffffff', transparent: true, opacity: 0, depthWrite: false })); v.castShadow = false; g.add(v); vapor.push(v); }
|
|
129
|
+
|
|
130
|
+
const est = { massa: 0, comp: 0 };
|
|
131
|
+
const cor0 = new THREE.Color('#4a3b2f'), cor1 = new THREE.Color('#25272c');
|
|
132
|
+
const aplicar = () => {
|
|
133
|
+
fatias.forEach((m, i) => {
|
|
134
|
+
const zn = (i + 0.5) / N;
|
|
135
|
+
const cobre = clamp((est.massa - i / N) * N);
|
|
136
|
+
const c = clamp(clamp((est.comp - zn) * 6) * 0.6 + clamp((est.comp - 1 - (1 - zn)) * 6) * 0.4);
|
|
137
|
+
const h = lerp(0.24, 0.1, c);
|
|
138
|
+
m.visible = cobre > 0; m.scale.y = Math.max(0.001, h * Math.min(1, cobre * 2));
|
|
139
|
+
m.position.set(0, 0.16 + m.scale.y / 2, -comp / 2 + zn * comp);
|
|
140
|
+
m.material.color.copy(cor0).lerp(cor1, c);
|
|
141
|
+
m.userData.c = c;
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
const api = {
|
|
145
|
+
base(k) { k = clamp(k); camadaBase.scale.z = Math.max(0.001, k); camadaBase.position.z = -comp / 2 + comp * k / 2; camadaBase.visible = k > 0; },
|
|
146
|
+
massa(k) { est.massa = clamp(k); aplicar(); },
|
|
147
|
+
compactar(p) { est.comp = clamp(p, 0, 2); aplicar(); },
|
|
148
|
+
faixas(k) { dashes.forEach((d, i) => { d.visible = k * 10 > i; }); borda.forEach((b) => { b.visible = k > 0.05; b.scale.z = Math.max(0.001, clamp(k * 1.2)); }); },
|
|
149
|
+
atualizar(t) {
|
|
150
|
+
vapor.forEach((v, i) => {
|
|
151
|
+
const zn = ruido(i * 3.1), u = (t * 0.35 + ruido(i)) % 1;
|
|
152
|
+
const idx = Math.min(N - 1, Math.floor(zn * N)), f = fatias[idx];
|
|
153
|
+
const quente = f.visible ? 1 - (f.userData.c || 0) : 0;
|
|
154
|
+
v.position.set((ruido(i + 20) - 0.5) * larg * 0.8, 0.4 + u * 1.6, -comp / 2 + zn * comp);
|
|
155
|
+
v.scale.setScalar(0.4 + u * 1.1); v.material.opacity = quente * (1 - u) * 0.38;
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
api.base(0); api.massa(0); api.faixas(0);
|
|
160
|
+
g.userData.api = api; g.userData.comp = comp;
|
|
161
|
+
return g;
|
|
162
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Maquete "como se faz asfalto": 6 etapas, cada uma mostra o processo. Peças do catálogo, nada mais.
|
|
2
|
+
* A pista corre no eixo z, de -9 a +9. Caminhão e rolo andam EM CIMA dela, na velocidade em que a camada
|
|
3
|
+
* se forma, por isso mover e api usam a mesma duração e ease linear. */
|
|
4
|
+
const VOZ = [[0.0, 4.625], [4.745, 9.82], [9.94, 16.39], [16.51, 21.81], [21.93, 26.33], [26.45, 32.35]];
|
|
5
|
+
const TEXTOS = [
|
|
6
|
+
'Toda rua começa assim: terra nivelada e firme.',
|
|
7
|
+
'Primeiro, a brita. Ela é a base que segura o peso.',
|
|
8
|
+
'A caçamba levanta e a massa quente, a mais de 100 graus, escorre.',
|
|
9
|
+
'O rolo compactador passa por cima e aperta a massa.',
|
|
10
|
+
'Cada passada deixa a pista mais lisa e mais escura.',
|
|
11
|
+
'Esfriou, pintou as faixas: a rua está pronta.',
|
|
12
|
+
];
|
|
13
|
+
const TITULOS = ['Terreno', 'Base de brita', 'Massa quente', 'Rolo compactador', 'Mais uma passada', 'Faixas e pronto'];
|
|
14
|
+
const dur = VOZ.map(([a, b]) => Math.round((b - a + 0.9) * 10) / 10);
|
|
15
|
+
const COMP = 18, Z0 = -COMP / 2; // início da pista
|
|
16
|
+
const zCacamba = (k) => Z0 + COMP * k + 2.4; // a traseira fica na borda da camada que nasce
|
|
17
|
+
const gente = { eng: { roupa: '#ff7a1a', capacete: '#ffd23f', pele: '#e8b48a' }, op: { roupa: '#7bd13a', capacete: '#f4f4f4', pele: '#b9825a', calca: '#22385c' } };
|
|
18
|
+
|
|
19
|
+
export const roteiro = {
|
|
20
|
+
ceu: ['#8fd0ff', '#d8efff'],
|
|
21
|
+
etapas: [
|
|
22
|
+
{ id: 'terreno', dur: dur[0], titulo: TITULOS[0], legenda: TEXTOS[0], voz: { de: VOZ[0][0], ate: VOZ[0][1] },
|
|
23
|
+
camera: { de: [7, 7, -25], para: [5, 3.6, -15], alvo: [0, 1, 0], alvoPara: [0, 1.4, -2], fov: 46 },
|
|
24
|
+
pecas: [
|
|
25
|
+
{ id: 'chao', tipo: 'terreno', w: 70, d: 70 },
|
|
26
|
+
{ id: 'pista', tipo: 'pista', comp: COMP, larg: 4.2 },
|
|
27
|
+
{ id: 'casa1', tipo: 'casa', pos: [-23, 0, -4], giro: 0.7, escala: 1.2 },
|
|
28
|
+
{ id: 'casa2', tipo: 'casa', pos: [23, 0, 2], giro: -0.8, escala: 1.1, cor: '#bfe3f2', telhado: '#6b7bd1' },
|
|
29
|
+
{ id: 'predio', tipo: 'predio', andares: 8, pos: [-21, 0, 11] },
|
|
30
|
+
{ id: 'guind', tipo: 'guindaste', pos: [21, 0, 12], giro: -2.3, escala: 0.8 },
|
|
31
|
+
{ id: 'a1', tipo: 'arvore', pos: [15, 0, -10] }, { id: 'a2', tipo: 'arvore', pos: [-14, 0, 4], escala: 1.2 },
|
|
32
|
+
{ id: 'a3', tipo: 'arvore', pos: [14, 0, 7], escala: 0.9 }, { id: 'a4', tipo: 'arvore', pos: [-13, 0, -10], escala: 1.1 },
|
|
33
|
+
{ id: 'cone1', tipo: 'cone', pos: [-2.6, 0, -9.6], escala: 1.3 }, { id: 'cone2', tipo: 'cone', pos: [2.6, 0, -9.6], escala: 1.3 },
|
|
34
|
+
{ id: 'eng', tipo: 'personagem', ...gente.eng, pos: [3.6, 0, -6], giro: -1.1 },
|
|
35
|
+
{ id: 'op', tipo: 'personagem', ...gente.op, pos: [-3.6, 0, -6], giro: 1.2 },
|
|
36
|
+
],
|
|
37
|
+
acoes: [
|
|
38
|
+
{ em: 0.8, dur: 3.6, quem: 'eng', faz: 'anim', nome: 'apontar' },
|
|
39
|
+
{ em: 0, dur: dur[0], quem: 'op', faz: 'anim', nome: 'pensar' },
|
|
40
|
+
{ em: 0, dur: dur[0], quem: 'guind', faz: 'api', metodo: 'girar', de: -2.3, para: -1.2, ease: 'linear' },
|
|
41
|
+
{ em: 0, dur: dur[0], quem: 'guind', faz: 'api', metodo: 'cabo', de: 2.5, para: 6 },
|
|
42
|
+
{ em: 0, dur: dur[0], quem: 'predio', faz: 'api', metodo: 'subir', de: 0.85, para: 1, ease: 'linear' },
|
|
43
|
+
] },
|
|
44
|
+
{ id: 'brita', dur: dur[1], titulo: TITULOS[1], legenda: TEXTOS[1], voz: { de: VOZ[1][0], ate: VOZ[1][1] },
|
|
45
|
+
camera: { de: [-17, 7.5, -14], para: [-17, 7.8, 6], alvo: 'cb', fov: 44 },
|
|
46
|
+
pecas: [{ id: 'cb', tipo: 'caminhao', cor: '#e8b100', carga: '#8d8a86', pos: [0, 0, -34], sai: dur[1] }],
|
|
47
|
+
acoes: [
|
|
48
|
+
{ em: 0, dur: 2.0, quem: 'cb', faz: 'mover', para: [0, zCacamba(0)] },
|
|
49
|
+
{ em: 2.1, dur: 3.4, quem: 'cb', faz: 'mover', para: [0, zCacamba(1)], ease: 'linear' },
|
|
50
|
+
{ em: 2.1, dur: 3.4, quem: 'cb', faz: 'api', metodo: 'despejar', de: 0, para: 1, ease: 'linear' },
|
|
51
|
+
{ em: 2.1, dur: 3.4, quem: 'pista', faz: 'api', metodo: 'base', de: 0, para: 1, ease: 'linear' },
|
|
52
|
+
] },
|
|
53
|
+
{ id: 'massa', dur: dur[2], titulo: TITULOS[2], legenda: TEXTOS[2], voz: { de: VOZ[2][0], ate: VOZ[2][1] },
|
|
54
|
+
camera: { de: [16, 6.5, -13], para: [16, 7, 7], alvo: 'cm', fov: 44 },
|
|
55
|
+
pecas: [{ id: 'cm', tipo: 'caminhao', cor: '#e8590c', carga: '#1f1a16', pos: [0, 0, -34], sai: dur[2] }],
|
|
56
|
+
acoes: [
|
|
57
|
+
{ em: 0, dur: 2.2, quem: 'cm', faz: 'mover', para: [0, zCacamba(0)] },
|
|
58
|
+
{ em: 2.4, dur: 4.2, quem: 'cm', faz: 'mover', para: [0, zCacamba(1)], ease: 'linear' },
|
|
59
|
+
{ em: 2.4, dur: 4.2, quem: 'cm', faz: 'api', metodo: 'despejar', de: 0, para: 1, ease: 'linear' },
|
|
60
|
+
{ em: 2.4, dur: 4.2, quem: 'pista', faz: 'api', metodo: 'massa', de: 0, para: 1, ease: 'linear' },
|
|
61
|
+
{ em: 0, dur: dur[2], quem: 'eng', faz: 'anim', nome: 'apontar' },
|
|
62
|
+
] },
|
|
63
|
+
{ id: 'rolo1', dur: dur[3], titulo: TITULOS[3], legenda: TEXTOS[3], voz: { de: VOZ[3][0], ate: VOZ[3][1] },
|
|
64
|
+
camera: { de: [-13, 5.6, -15], para: [-13, 5.4, 4], alvo: 'r1', fov: 44 },
|
|
65
|
+
pecas: [{ id: 'r1', tipo: 'rolo', pos: [0, 0, -34] }],
|
|
66
|
+
acoes: [
|
|
67
|
+
{ em: 0, dur: 1.5, quem: 'r1', faz: 'mover', para: [0, Z0 - 1.15] },
|
|
68
|
+
{ em: 1.6, dur: 3.9, quem: 'r1', faz: 'mover', para: [0, Z0 - 1.15 + COMP], ease: 'linear' },
|
|
69
|
+
{ em: 1.6, dur: 3.9, quem: 'pista', faz: 'api', metodo: 'compactar', de: 0, para: 1, ease: 'linear' },
|
|
70
|
+
{ em: 0, dur: dur[3], quem: 'op', faz: 'anim', nome: 'surpreso' },
|
|
71
|
+
] },
|
|
72
|
+
{ id: 'rolo2', dur: dur[4], titulo: TITULOS[4], legenda: TEXTOS[4], voz: { de: VOZ[4][0], ate: VOZ[4][1] },
|
|
73
|
+
camera: { de: [9, 3.2, 9], para: [9, 3.2, -9], alvo: 'r1', fov: 40 },
|
|
74
|
+
pecas: [],
|
|
75
|
+
acoes: [
|
|
76
|
+
{ em: 0.2, dur: 3.9, quem: 'r1', faz: 'mover', para: [0, Z0 - 1.15], reversa: true, ease: 'linear' },
|
|
77
|
+
{ em: 0.2, dur: 3.9, quem: 'pista', faz: 'api', metodo: 'compactar', de: 1, para: 2, ease: 'linear' },
|
|
78
|
+
] },
|
|
79
|
+
{ id: 'fim', dur: dur[5], titulo: TITULOS[5], legenda: TEXTOS[5], voz: { de: VOZ[5][0], ate: VOZ[5][1] },
|
|
80
|
+
camera: { de: [6, 4.4, -16], para: [11, 9, -23], alvo: [0, 0.8, 0], alvoPara: [0, 1.2, 2], fov: 46 },
|
|
81
|
+
pecas: [{ id: 'carro', tipo: 'caminhao', cor: '#3b82f6', cargaCheia: false, pos: [0, 0, -40], sai: dur[5] }],
|
|
82
|
+
acoes: [
|
|
83
|
+
{ em: 0, dur: 1.0, quem: 'op', faz: 'mover', para: [0.0, Z0 + 0.4], anim: 'correr' },
|
|
84
|
+
{ em: 1.1, dur: 3.2, quem: 'op', faz: 'mover', para: [0.0, Z0 + COMP - 0.4], anim: 'andar', ease: 'linear', vel: 1.5 },
|
|
85
|
+
{ em: 1.1, dur: 3.2, quem: 'pista', faz: 'api', metodo: 'faixas', de: 0, para: 1, ease: 'linear' },
|
|
86
|
+
{ em: 4.4, dur: 3, quem: 'op', faz: 'anim', nome: 'comemorar' },
|
|
87
|
+
{ em: 4.0, dur: 3, quem: 'eng', faz: 'anim', nome: 'comemorar' },
|
|
88
|
+
{ em: 3.6, dur: 3.2, quem: 'carro', faz: 'mover', para: [-1.1, 34], ease: 'linear' },
|
|
89
|
+
] },
|
|
90
|
+
],
|
|
91
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* maquinas.js — máquinas articuladas: caminhão basculante, guindaste e rolo
|
|
2
|
+
* compactador. A frente de cada uma aponta para +z. Cada articulação é uma
|
|
3
|
+
* função da api (0..1 ou metros), por isso o montador só diz "levanta a
|
|
4
|
+
* caçamba 0→1" e a peça se mexe. */
|
|
5
|
+
import { THREE, mat, caixa, esfera, cilindro, grupo, em, clamp, lerp, suave, ruido } from './base.js';
|
|
6
|
+
|
|
7
|
+
function roda(r, larg, cor = '#26282d') {
|
|
8
|
+
const pivo = grupo(), pneu = cilindro(r, r, larg, cor, 16), cubo = cilindro(r * 0.5, r * 0.5, larg + 0.02, '#c9ccd2', 10);
|
|
9
|
+
pneu.rotation.z = cubo.rotation.z = Math.PI / 2;
|
|
10
|
+
pivo.add(pneu, cubo);
|
|
11
|
+
return pivo;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Caminhão basculante. api: despejar(k) levanta a caçamba e solta a carga; rolar(m) gira as rodas. */
|
|
15
|
+
export function caminhao({ cor = '#f2a900', carga = '#4a3b2f', cargaCheia = true } = {}) {
|
|
16
|
+
const g = new THREE.Group(), rodas = [];
|
|
17
|
+
g.add(em(caixa(1.4, 0.3, 3.7, '#3d4048', 0.05), 0, 0.62, 0));
|
|
18
|
+
g.add(em(caixa(1.45, 0.95, 1.15, cor, 0.16), 0, 1.3, 1.35));
|
|
19
|
+
g.add(em(caixa(1.3, 0.45, 0.06, '#9ed8f5', 0.03), 0, 1.5, 1.93));
|
|
20
|
+
g.add(em(caixa(1.2, 0.2, 0.15, '#2b2b2b', 0.04), 0, 0.72, 2.0));
|
|
21
|
+
[-0.5, 0.5].forEach((x) => g.add(em(esfera(0.1, '#fff4c2', { emissive: 0.6 }), x, 0.8, 2.0)));
|
|
22
|
+
for (const [z, s] of [[1.35, 1], [-0.7, 1], [-1.45, 1]]) for (const x of [-0.78, 0.78]) { const r = roda(0.4, 0.3); r.position.set(x * s, 0.4, z); g.add(r); rodas.push(r); }
|
|
23
|
+
|
|
24
|
+
// caçamba: pivô na traseira (z = -1.8); levantar = girar para trás
|
|
25
|
+
const cacamba = grupo(); cacamba.position.set(0, 0.85, -1.8);
|
|
26
|
+
cacamba.add(em(caixa(1.6, 0.12, 2.7, cor, 0.04), 0, 0, 1.35));
|
|
27
|
+
cacamba.add(em(caixa(0.1, 0.7, 2.7, cor, 0.04), -0.78, 0.35, 1.35), em(caixa(0.1, 0.7, 2.7, cor, 0.04), 0.78, 0.35, 1.35), em(caixa(1.6, 0.9, 0.1, cor, 0.04), 0, 0.45, 2.7));
|
|
28
|
+
const monte = em(esfera(1, carga), 0, 0.1, 1.3); monte.scale.set(0.72, 0.5, 1.25); cacamba.add(monte);
|
|
29
|
+
g.add(cacamba);
|
|
30
|
+
const gotas = []; for (let i = 0; i < 20; i++) { const b = esfera(0.17, carga); b.visible = false; g.add(b); gotas.push(b); }
|
|
31
|
+
|
|
32
|
+
const est = { k: 0 };
|
|
33
|
+
const api = {
|
|
34
|
+
despejar(k) {
|
|
35
|
+
est.k = clamp(k);
|
|
36
|
+
const sobe = suave(clamp(k / 0.35)) * (k > 0.88 ? 1 - (k - 0.88) / 0.12 : 1);
|
|
37
|
+
cacamba.rotation.x = -sobe * 0.72;
|
|
38
|
+
const resto = cargaCheia ? 1 - clamp((k - 0.3) / 0.55) : 0;
|
|
39
|
+
monte.visible = resto > 0.02; monte.scale.set(0.72 * (0.5 + resto * 0.5), 0.5 * resto, 1.25 * (0.4 + resto * 0.6));
|
|
40
|
+
monte.position.z = 1.3 - (1 - resto) * 0.55 * sobe;
|
|
41
|
+
est.cai = sobe > 0.6 && resto > 0.03;
|
|
42
|
+
},
|
|
43
|
+
rolar(m) { rodas.forEach((r) => { r.rotation.x = m / 0.4; }); },
|
|
44
|
+
atualizar(t) {
|
|
45
|
+
gotas.forEach((b, i) => {
|
|
46
|
+
const u = (t * 1.7 + i / gotas.length) % 1, voando = est.cai && u < 0.5;
|
|
47
|
+
b.visible = !!voando;
|
|
48
|
+
if (voando) { b.position.set((ruido(i) - 0.5) * 1.1, 1.05 - 3.4 * u * u, -2.3 - u * 0.5); b.scale.setScalar(1 - u * 0.5); }
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
api.despejar(0);
|
|
53
|
+
g.userData.api = api;
|
|
54
|
+
return g;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Guindaste de esteira. api: girar(rad) · lanca(0..1) · cabo(m) · carga(0|1). */
|
|
58
|
+
export function guindaste({ cor = '#f5c518', escala = 1 } = {}) {
|
|
59
|
+
const g = new THREE.Group();
|
|
60
|
+
g.add(em(caixa(2.4, 0.6, 3.4, '#34373d', 0.1), 0, 0.4, 0));
|
|
61
|
+
[-1.1, 1.1].forEach((x) => g.add(em(caixa(0.5, 0.7, 3.6, '#202226', 0.2), x, 0.38, 0)));
|
|
62
|
+
const torre = grupo(); torre.position.y = 0.8; g.add(torre);
|
|
63
|
+
torre.add(em(caixa(2.0, 0.5, 2.2, cor, 0.1), 0, 0.25, 0), em(caixa(1.0, 0.9, 1.0, cor, 0.1), 0, 0.95, 0.6), em(caixa(0.9, 0.5, 0.06, '#9ed8f5', 0.03), 0, 1.0, 1.12));
|
|
64
|
+
torre.add(em(caixa(0.5, 0.7, 0.9, '#444850', 0.08), 0, 0.6, -0.9));
|
|
65
|
+
torre.add(em(caixa(0.4, 6, 0.4, cor, 0.08), 0, 3.3, -0.2));
|
|
66
|
+
const lanca = grupo(); lanca.position.set(0, 6.2, -0.2); torre.add(lanca);
|
|
67
|
+
lanca.add(em(caixa(0.32, 0.32, 8, cor, 0.06), 0, 0, 4));
|
|
68
|
+
const pont = grupo(); pont.position.z = 8; lanca.add(pont);
|
|
69
|
+
const cabo = cilindro(0.03, 0.03, 1, '#222', 6); cabo.castShadow = false; pont.add(cabo);
|
|
70
|
+
const gancho = grupo(em(caixa(0.3, 0.3, 0.3, '#d33', 0.06), 0, -0.15, 0)); pont.add(gancho);
|
|
71
|
+
const carga = em(caixa(1.2, 0.7, 1.2, '#7fb069', 0.1), 0, -0.9, 0); gancho.add(carga);
|
|
72
|
+
const est = { a: 0.9 };
|
|
73
|
+
const api = {
|
|
74
|
+
girar(r) { torre.rotation.y = r; },
|
|
75
|
+
lanca(k) { est.a = lerp(0.35, 1.15, clamp(k)); lanca.rotation.x = -est.a; pont.rotation.x = est.a; },
|
|
76
|
+
cabo(m) { const c = Math.max(0.2, m); cabo.scale.y = c; cabo.position.y = -c / 2; gancho.position.y = -c; },
|
|
77
|
+
carga(v) { carga.visible = v > 0.5; },
|
|
78
|
+
};
|
|
79
|
+
api.lanca(0.5); api.cabo(3); api.carga(0);
|
|
80
|
+
g.userData.api = api; g.scale.setScalar(escala);
|
|
81
|
+
return g;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Rolo compactador. api: rolar(m) gira o cilindro e as rodas. */
|
|
85
|
+
export function rolo({ cor = '#ffd23f' } = {}) {
|
|
86
|
+
const g = new THREE.Group();
|
|
87
|
+
const tambor = cilindro(0.62, 0.62, 1.8, '#7d8590', 20); tambor.rotation.z = Math.PI / 2;
|
|
88
|
+
const frente = grupo(tambor); frente.position.set(0, 0.62, 1.15); g.add(frente);
|
|
89
|
+
[-0.98, 0.98].forEach((x) => g.add(em(caixa(0.14, 0.18, 1.0, '#454a52', 0.04), x, 1.05, 0.95)));
|
|
90
|
+
g.add(em(caixa(1.4, 0.7, 2.0, cor, 0.16), 0, 1.0, -0.4));
|
|
91
|
+
g.add(em(caixa(1.0, 0.5, 0.9, cor, 0.12), 0, 1.5, -0.7));
|
|
92
|
+
g.add(em(caixa(1.2, 0.08, 1.2, '#2c3038', 0.03), 0, 2.2, -0.7));
|
|
93
|
+
[[-0.5, 0.5], [0.5, 0.5], [-0.5, -1.9], [0.5, -1.9]].forEach(([x, z]) => g.add(em(cilindro(0.04, 0.04, 0.7, '#2c3038', 6), x, 1.85, z * 0.5 - 0.2)));
|
|
94
|
+
const traseiras = [-0.85, 0.85].map((x) => { const r = roda(0.52, 0.5, '#26282d'); r.position.set(x, 0.52, -1.0); g.add(r); return r; });
|
|
95
|
+
g.userData.api = { rolar(m) { frente.rotation.x = m / 0.62; traseiras.forEach((r) => { r.rotation.x = m / 0.52; }); } };
|
|
96
|
+
return g;
|
|
97
|
+
}
|