ape-claw 0.1.3 → 0.1.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.
- package/README.md +36 -24
- package/docs/CLI_GUIDE.md +5 -0
- package/docs/GLOBAL_BACKEND.md +3 -1
- package/docs/PRODUCT_OVERVIEW.md +15 -1
- package/docs/STARTER_PACK.md +7 -5
- package/docs/SUPPORTED_NETWORKS.md +1 -1
- package/docs/operator/01-quickstart.md +100 -9
- package/docs/operator/02-dashboard.md +1 -1
- package/docs/operator/03-cli-reference.md +19 -7
- package/docs/operator/04-skills-library.md +13 -12
- package/docs/operator/06-deployment.md +60 -7
- package/docs/operator/09-env-reference.md +61 -0
- package/docs/social/STARTER_PACK_THREAD.md +3 -3
- package/package.json +1 -1
- package/src/cli.mjs +132 -16
- package/src/server/index.mjs +6 -2
- package/src/server/routes/forge-agent.mjs +820 -0
- package/src/server/routes/skills.mjs +39 -6
- package/src/server/storage/file-backend.mjs +31 -0
- package/src/telemetry-server.mjs +90 -4
- package/ui/forge/css/forge.css +30 -2
- package/ui/forge/index.html +20 -7
- package/ui/forge/js/forge-attachments.js +26 -38
- package/ui/forge/js/forge-chat.js +240 -20
- package/ui/forge/js/forge-data.js +76 -14
- package/ui/forge/js/forge-scene.js +1943 -391
- package/ui/index.html +53 -54
- package/ui/js/skills.js +57 -42
- package/ui/shared/sidebar-nav.js +1 -1
- package/ui/skills.html +80 -53
|
@@ -10,51 +10,224 @@ import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";
|
|
|
10
10
|
import { RenderPass } from "three/addons/postprocessing/RenderPass.js";
|
|
11
11
|
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";
|
|
12
12
|
import { OutlinePass } from "three/addons/postprocessing/OutlinePass.js";
|
|
13
|
+
import { OutputPass } from "three/addons/postprocessing/OutputPass.js";
|
|
14
|
+
import { SSAOPass } from "three/addons/postprocessing/SSAOPass.js";
|
|
15
|
+
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
|
|
16
|
+
import { RoundedBoxGeometry } from "three/addons/geometries/RoundedBoxGeometry.js";
|
|
13
17
|
|
|
14
18
|
/* ══════════════════════════════════════════════════════════
|
|
15
19
|
Material palette — HV-MTL inspired chrome + neon
|
|
16
20
|
══════════════════════════════════════════════════════════ */
|
|
17
21
|
const M = THREE.MeshStandardMaterial;
|
|
22
|
+
const P = THREE.MeshPhysicalMaterial;
|
|
18
23
|
|
|
19
|
-
const GLOW =
|
|
20
|
-
const CYAN =
|
|
24
|
+
const GLOW = 0x4488ff;
|
|
25
|
+
const CYAN = 0x5599ff;
|
|
21
26
|
const HOT = 0xff3344;
|
|
27
|
+
const RED = 0xcc2233;
|
|
28
|
+
const BLUE = 0x1a3a88;
|
|
22
29
|
|
|
23
30
|
export const MAT = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
31
|
+
// Primary armor — deep Optimus-blue with mirror clearcoat
|
|
32
|
+
armor: new P({ color: 0x1a2e6a, roughness: 0.15, metalness: 0.85, emissive: 0x080e28, emissiveIntensity: 0.3, clearcoat: 1.0, clearcoatRoughness: 0.05 }),
|
|
33
|
+
armorLt: new P({ color: 0x283d7a, roughness: 0.18, metalness: 0.8, emissive: 0x0a1230, emissiveIntensity: 0.3, clearcoat: 0.9, clearcoatRoughness: 0.08 }),
|
|
34
|
+
// Heroic red accent plates (chest, shoulders)
|
|
35
|
+
armorRed: new P({ color: 0xaa1825, roughness: 0.12, metalness: 0.85, emissive: 0x330808, emissiveIntensity: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.04 }),
|
|
36
|
+
// Chrome — high-shine polished silver
|
|
37
|
+
chrome: new P({ color: 0xc0ccdd, roughness: 0.04, metalness: 0.98, emissive: 0x3a4555, emissiveIntensity: 0.5, clearcoat: 1.0, clearcoatRoughness: 0.02 }),
|
|
38
|
+
joint: new P({ color: 0x2a2e38, roughness: 0.25, metalness: 0.7, emissive: GLOW, emissiveIntensity: 0.2, clearcoat: 0.8, clearcoatRoughness: 0.15, sheen: 0.3, sheenColor: new THREE.Color(GLOW), sheenRoughness: 0.5 }),
|
|
39
|
+
visor: new P({ color: 0xaaddff, emissive: CYAN, emissiveIntensity: 2.2, roughness: 0.05, metalness: 0.0, transmission: 0.85, thickness: 0.4, clearcoat: 1.0, clearcoatRoughness: 0.0, transparent: true, opacity: 0.85, iridescence: 0.6, iridescenceIOR: 1.3, iridescenceThicknessRange: [100, 400] }),
|
|
40
|
+
core: new P({ color: 0xaaddff, emissive: GLOW, emissiveIntensity: 3.5, roughness: 0.05, metalness: 0.0, transmission: 0.7, thickness: 0.6, clearcoat: 1.0, clearcoatRoughness: 0.0 }),
|
|
41
|
+
coreInner: new P({ color: 0xffffff, emissive: GLOW, emissiveIntensity: 5.0, transparent: true, opacity: 0.95, clearcoat: 1.0, clearcoatRoughness: 0.0 }),
|
|
42
|
+
vent: new P({ color: GLOW, emissive: GLOW, emissiveIntensity: 2.5, transparent: true, opacity: 0.8, clearcoat: 0.5, clearcoatRoughness: 0.2 }),
|
|
43
|
+
ventHot: new P({ color: HOT, emissive: HOT, emissiveIntensity: 3.0, transparent: true, opacity: 0.7, clearcoat: 0.3, clearcoatRoughness: 0.3 }),
|
|
44
|
+
panelLine: new P({ color: GLOW, emissive: GLOW, emissiveIntensity: 1.8, transparent: true, opacity: 0.7 }),
|
|
45
|
+
spineGlow: new P({ color: GLOW, emissive: GLOW, emissiveIntensity: 2.5, clearcoat: 0.4, clearcoatRoughness: 0.3 }),
|
|
46
|
+
hardpoint: new P({ color: 0x404858, roughness: 0.15, metalness: 0.8, emissive: CYAN, emissiveIntensity: 0.4, clearcoat: 0.8, clearcoatRoughness: 0.1 }),
|
|
47
|
+
security: new P({ color: 0xFFB347, emissive: 0xFFB347, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.8, clearcoat: 0.6, clearcoatRoughness: 0.1 }),
|
|
48
|
+
analytics: new P({ color: 0x63d7ff, emissive: 0x63d7ff, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.6, clearcoatRoughness: 0.1 }),
|
|
49
|
+
automation: new P({ color: 0x00ff00, emissive: 0x00ff00, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
50
|
+
devtools: new P({ color: 0x4169E1, emissive: 0x4169E1, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
51
|
+
nft: new P({ color: 0xb026ff, emissive: 0xb026ff, emissiveIntensity: 1.5, roughness: 0.12, metalness: 0.8, clearcoat: 0.7, clearcoatRoughness: 0.08, iridescence: 0.4, iridescenceIOR: 1.5 }),
|
|
52
|
+
social: new P({ color: 0xFF7F50, emissive: 0xFF7F50, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.6, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
53
|
+
storage: new P({ color: 0x6A5ACD, emissive: 0x6A5ACD, emissiveIntensity: 1.0, roughness: 0.2, metalness: 0.6, clearcoat: 0.4, clearcoatRoughness: 0.2 }),
|
|
54
|
+
productivity: new P({ color: 0xE6F3FF, emissive: 0xE6F3FF, emissiveIntensity: 0.8, roughness: 0.15, metalness: 0.5, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
55
|
+
bridge: new P({ color: 0xFF8C00, emissive: 0xFF8C00, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
56
|
+
trading: new P({ color: 0xFFD700, emissive: 0xFFD700, emissiveIntensity: 1.0, roughness: 0.1, metalness: 0.9, clearcoat: 0.8, clearcoatRoughness: 0.05 }),
|
|
57
|
+
governance: new P({ color: 0x4B0082, emissive: 0x4B0082, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
58
|
+
wallet: new P({ color: 0x50C878, emissive: 0x50C878, emissiveIntensity: 1.0, roughness: 0.15, metalness: 0.6, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
59
|
+
defi: new P({ color: 0x32CD32, emissive: 0x32CD32, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
60
|
+
development: new P({ color: 0x4169E1, emissive: 0x4169E1, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
61
|
+
writing: new P({ color: 0xFFF8DC, emissive: 0xFFF8DC, emissiveIntensity: 0.7, roughness: 0.2, metalness: 0.5, clearcoat: 0.4, clearcoatRoughness: 0.2 }),
|
|
62
|
+
communication: new P({ color: 0x00BFFF, emissive: 0x00BFFF, emissiveIntensity: 1.2, roughness: 0.15, metalness: 0.7, clearcoat: 0.6, clearcoatRoughness: 0.1 }),
|
|
63
|
+
undersuit: new P({ color: 0x0a0e18, roughness: 0.6, metalness: 0.3, emissive: 0x020408, emissiveIntensity: 0.05, clearcoat: 0.2 }),
|
|
64
|
+
pistonMat: new P({ color: 0xc8c8d0, roughness: 0.06, metalness: 0.95, emissive: 0x404050, emissiveIntensity: 0.2, clearcoat: 1.0, clearcoatRoughness: 0.02 }),
|
|
65
|
+
cableMat: new M({ color: 0x101018, roughness: 0.85, metalness: 0.15 }),
|
|
66
|
+
rivetMat: new P({ color: 0xe0e8f0, roughness: 0.04, metalness: 0.98, emissive: 0x607088, emissiveIntensity: 0.3, clearcoat: 1.0, clearcoatRoughness: 0.0 }),
|
|
67
|
+
frameMat: new P({ color: 0x1a2030, roughness: 0.35, metalness: 0.6, emissive: 0x050810, emissiveIntensity: 0.1, clearcoat: 0.4, clearcoatRoughness: 0.2 }),
|
|
68
|
+
darkChrome: new P({ color: 0x2a3040, roughness: 0.08, metalness: 0.92, emissive: 0x101520, emissiveIntensity: 0.25, clearcoat: 1.0, clearcoatRoughness: 0.04 }),
|
|
69
|
+
// Mechanical internals
|
|
70
|
+
servo: new P({ color: 0x2a2a32, roughness: 0.35, metalness: 0.7, emissive: 0x080810, emissiveIntensity: 0.1, clearcoat: 0.3, clearcoatRoughness: 0.3 }),
|
|
71
|
+
hydraulic: new P({ color: 0x886622, roughness: 0.2, metalness: 0.85, emissive: 0x221100, emissiveIntensity: 0.15, clearcoat: 0.7, clearcoatRoughness: 0.1 }),
|
|
72
|
+
rubber: new P({ color: 0x0c0c10, roughness: 0.9, metalness: 0.05, emissive: 0x000000, emissiveIntensity: 0.0 }),
|
|
73
|
+
coolingFin:new P({ color: 0x1e2838, roughness: 0.25, metalness: 0.75, emissive: 0x040810, emissiveIntensity: 0.08, clearcoat: 0.5, clearcoatRoughness: 0.15 }),
|
|
74
|
+
weldSeam: new P({ color: 0x505868, roughness: 0.5, metalness: 0.6, emissive: 0x101418, emissiveIntensity: 0.05, clearcoat: 0.2 }),
|
|
75
|
+
oilStain: new P({ color: 0x181410, roughness: 0.7, metalness: 0.2, emissive: 0x000000, emissiveIntensity: 0.0, clearcoat: 0.6, clearcoatRoughness: 0.4 }),
|
|
76
|
+
lens: new P({ color: 0x112244, emissive: CYAN, emissiveIntensity: 1.5, roughness: 0.02, metalness: 0.0, transmission: 0.6, thickness: 0.2, clearcoat: 1.0, clearcoatRoughness: 0.0, transparent: true, opacity: 0.9 }),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/* ══════════════════════════════════════════════════════════
|
|
80
|
+
Procedural surface detail — bump + roughness maps
|
|
81
|
+
Adds panel grooves, bolt heads, micro-scratches, and
|
|
82
|
+
worn-metal roughness variation to chassis materials.
|
|
83
|
+
══════════════════════════════════════════════════════════ */
|
|
84
|
+
function _detailCanvas(size, fn) {
|
|
85
|
+
const c = document.createElement("canvas");
|
|
86
|
+
c.width = c.height = size;
|
|
87
|
+
fn(c.getContext("2d"), size);
|
|
88
|
+
const t = new THREE.CanvasTexture(c);
|
|
89
|
+
t.wrapS = t.wrapT = THREE.RepeatWrapping;
|
|
90
|
+
return t;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const _armorBump = _detailCanvas(256, (ctx, s) => {
|
|
94
|
+
ctx.fillStyle = "#808080"; ctx.fillRect(0, 0, s, s);
|
|
95
|
+
ctx.strokeStyle = "#5e5e5e"; ctx.lineWidth = 1.5;
|
|
96
|
+
for (let y = 0; y < s; y += 28) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(s, y); ctx.stroke(); }
|
|
97
|
+
for (let x = 0; x < s; x += 42) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, s); ctx.stroke(); }
|
|
98
|
+
ctx.fillStyle = "#949494";
|
|
99
|
+
for (let x = 21; x < s; x += 42) for (let y = 14; y < s; y += 28) {
|
|
100
|
+
ctx.beginPath(); ctx.arc(x, y, 2.5, 0, Math.PI * 2); ctx.fill();
|
|
101
|
+
}
|
|
102
|
+
ctx.strokeStyle = "#727272"; ctx.lineWidth = 0.4; ctx.globalAlpha = 0.7;
|
|
103
|
+
for (let i = 0; i < 50; i++) {
|
|
104
|
+
const sx = Math.random() * s, sy = Math.random() * s;
|
|
105
|
+
ctx.beginPath(); ctx.moveTo(sx, sy);
|
|
106
|
+
ctx.lineTo(sx + (Math.random() - 0.5) * 35, sy + (Math.random() - 0.5) * 4);
|
|
107
|
+
ctx.stroke();
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
_armorBump.repeat.set(3, 3);
|
|
111
|
+
|
|
112
|
+
const _chromeBump = _detailCanvas(256, (ctx, s) => {
|
|
113
|
+
ctx.fillStyle = "#808080"; ctx.fillRect(0, 0, s, s);
|
|
114
|
+
ctx.strokeStyle = "#747474"; ctx.lineWidth = 0.3; ctx.globalAlpha = 0.6;
|
|
115
|
+
for (let i = 0; i < 400; i++) {
|
|
116
|
+
const y = Math.random() * s, x = Math.random() * s * 0.15;
|
|
117
|
+
ctx.beginPath(); ctx.moveTo(x, y);
|
|
118
|
+
ctx.lineTo(x + 25 + Math.random() * 70, y + (Math.random() - 0.5) * 1);
|
|
119
|
+
ctx.stroke();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
_chromeBump.repeat.set(4, 4);
|
|
123
|
+
|
|
124
|
+
const _roughVar = _detailCanvas(256, (ctx, s) => {
|
|
125
|
+
// Base matte
|
|
126
|
+
ctx.fillStyle = "#909090"; ctx.fillRect(0, 0, s, s);
|
|
127
|
+
|
|
128
|
+
// Large grunge patches (darker = shinier/oily)
|
|
129
|
+
ctx.globalCompositeOperation = "multiply";
|
|
130
|
+
for (let i = 0; i < 8; i++) {
|
|
131
|
+
const cx = Math.random() * s, cy = Math.random() * s, r = 30 + Math.random() * 50;
|
|
132
|
+
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, r);
|
|
133
|
+
g.addColorStop(0, "rgba(100,100,100,0.6)");
|
|
134
|
+
g.addColorStop(1, "rgba(140,140,140,0)");
|
|
135
|
+
ctx.fillStyle = g;
|
|
136
|
+
ctx.fillRect(0, 0, s, s);
|
|
137
|
+
}
|
|
138
|
+
ctx.globalCompositeOperation = "source-over";
|
|
139
|
+
|
|
140
|
+
// Scratches (lighter = rougher exposed metal/primer)
|
|
141
|
+
ctx.strokeStyle = "#d0d0d0"; ctx.lineWidth = 0.6; ctx.globalAlpha = 0.5;
|
|
142
|
+
for (let i = 0; i < 120; i++) {
|
|
143
|
+
const sx = Math.random() * s, sy = Math.random() * s;
|
|
144
|
+
ctx.beginPath(); ctx.moveTo(sx, sy);
|
|
145
|
+
ctx.lineTo(sx + (Math.random() - 0.5) * 30, sy + (Math.random() - 0.5) * 8);
|
|
146
|
+
ctx.stroke();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Worn panel edges (darker = polished wear)
|
|
150
|
+
ctx.strokeStyle = "#606060"; ctx.lineWidth = 1.5; ctx.globalAlpha = 0.4;
|
|
151
|
+
for (let y = 0; y < s; y += 28) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(s, y); ctx.stroke(); }
|
|
152
|
+
for (let x = 0; x < s; x += 42) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, s); ctx.stroke(); }
|
|
153
|
+
});
|
|
154
|
+
_roughVar.repeat.set(3, 3);
|
|
155
|
+
|
|
156
|
+
// Procedural normal map for directional lighting on panel seams
|
|
157
|
+
const _armorNormal = _detailCanvas(256, (ctx, s) => {
|
|
158
|
+
ctx.fillStyle = "#8080ff"; ctx.fillRect(0, 0, s, s);
|
|
159
|
+
ctx.strokeStyle = "#6060ff"; ctx.lineWidth = 2;
|
|
160
|
+
for (let y = 0; y < s; y += 28) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(s, y); ctx.stroke(); }
|
|
161
|
+
for (let x = 0; x < s; x += 42) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, s); ctx.stroke(); }
|
|
162
|
+
ctx.fillStyle = "#a0a0ff";
|
|
163
|
+
for (let x = 21; x < s; x += 42) for (let y = 14; y < s; y += 28) {
|
|
164
|
+
ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fill();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
_armorNormal.repeat.set(3, 3);
|
|
168
|
+
|
|
169
|
+
// Anisotropic brushed-metal direction for chrome
|
|
170
|
+
const _chromeAniso = _detailCanvas(256, (ctx, s) => {
|
|
171
|
+
ctx.fillStyle = "#8080ff"; ctx.fillRect(0, 0, s, s);
|
|
172
|
+
ctx.strokeStyle = "#9060ff"; ctx.lineWidth = 0.5; ctx.globalAlpha = 0.7;
|
|
173
|
+
for (let i = 0; i < 600; i++) {
|
|
174
|
+
const y = Math.random() * s, x = Math.random() * s * 0.1;
|
|
175
|
+
ctx.beginPath(); ctx.moveTo(x, y);
|
|
176
|
+
ctx.lineTo(x + 20 + Math.random() * 80, y + (Math.random() - 0.5) * 0.5);
|
|
177
|
+
ctx.stroke();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
_chromeAniso.repeat.set(4, 4);
|
|
181
|
+
|
|
182
|
+
for (const k of ["armor", "armorLt", "armorRed"]) {
|
|
183
|
+
MAT[k].bumpMap = _armorBump; MAT[k].bumpScale = 0.018;
|
|
184
|
+
MAT[k].normalMap = _armorNormal; MAT[k].normalScale = new THREE.Vector2(0.15, 0.15);
|
|
185
|
+
MAT[k].roughnessMap = _roughVar;
|
|
186
|
+
}
|
|
187
|
+
for (const k of ["chrome", "darkChrome"]) {
|
|
188
|
+
MAT[k].bumpMap = _chromeBump; MAT[k].bumpScale = 0.01;
|
|
189
|
+
MAT[k].normalMap = _chromeAniso; MAT[k].normalScale = new THREE.Vector2(0.12, 0.12);
|
|
190
|
+
MAT[k].roughnessMap = _roughVar;
|
|
191
|
+
}
|
|
192
|
+
MAT.hardpoint.bumpMap = _armorBump; MAT.hardpoint.bumpScale = 0.012;
|
|
193
|
+
MAT.hardpoint.normalMap = _armorNormal; MAT.hardpoint.normalScale = new THREE.Vector2(0.1, 0.1);
|
|
194
|
+
MAT.pistonMat.bumpMap = _chromeBump; MAT.pistonMat.bumpScale = 0.008;
|
|
195
|
+
MAT.pistonMat.normalMap = _chromeAniso; MAT.pistonMat.normalScale = new THREE.Vector2(0.1, 0.1);
|
|
196
|
+
MAT.joint.bumpMap = _chromeBump; MAT.joint.bumpScale = 0.006;
|
|
197
|
+
MAT.undersuit.bumpMap = _armorBump; MAT.undersuit.bumpScale = 0.005;
|
|
198
|
+
MAT.frameMat.bumpMap = _armorBump; MAT.frameMat.bumpScale = 0.008;
|
|
199
|
+
MAT.servo.bumpMap = _armorBump; MAT.servo.bumpScale = 0.01;
|
|
200
|
+
MAT.servo.roughnessMap = _roughVar;
|
|
201
|
+
MAT.hydraulic.bumpMap = _chromeBump; MAT.hydraulic.bumpScale = 0.006;
|
|
202
|
+
MAT.hydraulic.normalMap = _chromeAniso; MAT.hydraulic.normalScale = new THREE.Vector2(0.08, 0.08);
|
|
203
|
+
MAT.coolingFin.bumpMap = _armorBump; MAT.coolingFin.bumpScale = 0.008;
|
|
204
|
+
MAT.weldSeam.bumpMap = _roughVar; MAT.weldSeam.bumpScale = 0.02;
|
|
205
|
+
|
|
206
|
+
/* ══════════════════════════════════════════════════════════
|
|
207
|
+
Film grain + vignette shader
|
|
208
|
+
══════════════════════════════════════════════════════════ */
|
|
209
|
+
const FilmGrainShader = {
|
|
210
|
+
uniforms: {
|
|
211
|
+
tDiffuse: { value: null },
|
|
212
|
+
time: { value: 0 },
|
|
213
|
+
intensity: { value: 0.03 },
|
|
214
|
+
},
|
|
215
|
+
vertexShader: /* glsl */ `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`,
|
|
216
|
+
fragmentShader: /* glsl */ `
|
|
217
|
+
uniform sampler2D tDiffuse;
|
|
218
|
+
uniform float time;
|
|
219
|
+
uniform float intensity;
|
|
220
|
+
varying vec2 vUv;
|
|
221
|
+
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
222
|
+
void main() {
|
|
223
|
+
vec4 c = texture2D(tDiffuse, vUv);
|
|
224
|
+
float grain = (hash(vUv * 1000.0 + time) - 0.5) * intensity;
|
|
225
|
+
vec2 uv = vUv * 2.0 - 1.0;
|
|
226
|
+
float vig = 1.0 - dot(uv * 0.55, uv * 0.55);
|
|
227
|
+
c.rgb = c.rgb * vig + grain;
|
|
228
|
+
gl_FragColor = c;
|
|
229
|
+
}
|
|
230
|
+
`,
|
|
58
231
|
};
|
|
59
232
|
|
|
60
233
|
/* ══════════════════════════════════════════════════════════
|
|
@@ -63,7 +236,7 @@ export const MAT = {
|
|
|
63
236
|
export let scene, camera, renderer, composer, controls, outlinePass, css2dRenderer;
|
|
64
237
|
export let robotGroup, attachmentGroup, energyNetworkGroup, platformGroup;
|
|
65
238
|
export let coreMesh, visorMesh, spineSegments = [];
|
|
66
|
-
let ambientParticles, energyStreams, bloomPass;
|
|
239
|
+
let ambientParticles, energyStreams, bloomPass, ssaoPass, filmGrainPass;
|
|
67
240
|
let autoRotateTimer = null;
|
|
68
241
|
let bloomEnabled = true;
|
|
69
242
|
let selectedAttachment = null;
|
|
@@ -72,6 +245,15 @@ let exhaustFlames = [];
|
|
|
72
245
|
let initialized = false;
|
|
73
246
|
let renderPaused = false;
|
|
74
247
|
let hoverHudEl = null;
|
|
248
|
+
let headPivotRef = null, leftArmPivotRef = null, rightArmPivotRef = null;
|
|
249
|
+
let agentSpeaking = false;
|
|
250
|
+
let heatShimmerMeshes = [];
|
|
251
|
+
let robotPowered = false;
|
|
252
|
+
let powerLevel = 0;
|
|
253
|
+
let powerDownTimer = null;
|
|
254
|
+
const POWER_UP_SPEED = 2.5;
|
|
255
|
+
const POWER_DOWN_SPEED = 1.2;
|
|
256
|
+
const POWER_DOWN_DELAY = 4000;
|
|
75
257
|
|
|
76
258
|
const viewportEl = () => document.getElementById("forgeViewport");
|
|
77
259
|
const canvasEl = () => document.getElementById("forgeCanvas");
|
|
@@ -114,8 +296,40 @@ export function tweenVector3(obj, from, to, durationMs, easeFn, onDone) {
|
|
|
114
296
|
/* ══════════════════════════════════════════════════════════
|
|
115
297
|
Geometry helpers
|
|
116
298
|
══════════════════════════════════════════════════════════ */
|
|
299
|
+
/* ══════════════════════════════════════════════════════════
|
|
300
|
+
Geometry cache — reuse identical geometries across all parts.
|
|
301
|
+
Keys encode shape + dimensions to share GPU buffers.
|
|
302
|
+
══════════════════════════════════════════════════════════ */
|
|
303
|
+
const _geoCache = new Map();
|
|
304
|
+
function _cachedGeo(key, factory) {
|
|
305
|
+
if (!_geoCache.has(key)) _geoCache.set(key, factory());
|
|
306
|
+
return _geoCache.get(key);
|
|
307
|
+
}
|
|
308
|
+
function _boxGeo(w, h, d) {
|
|
309
|
+
const minDim = Math.min(w, h, d);
|
|
310
|
+
const useRounded = minDim > 0.08;
|
|
311
|
+
const k = `b${w.toFixed(3)}_${h.toFixed(3)}_${d.toFixed(3)}_${useRounded ? 1 : 0}`;
|
|
312
|
+
return _cachedGeo(k, () => useRounded
|
|
313
|
+
? new RoundedBoxGeometry(w, h, d, 2, minDim * 0.12)
|
|
314
|
+
: new THREE.BoxGeometry(w, h, d));
|
|
315
|
+
}
|
|
316
|
+
function _cylGeo(rT, rB, h, segs) {
|
|
317
|
+
const k = `c${rT.toFixed(3)}_${rB.toFixed(3)}_${h.toFixed(3)}_${segs}`;
|
|
318
|
+
return _cachedGeo(k, () => new THREE.CylinderGeometry(rT, rB, h, segs));
|
|
319
|
+
}
|
|
320
|
+
function _sphereGeo(r, ws, hs) {
|
|
321
|
+
const k = `s${r.toFixed(4)}_${ws}_${hs}`;
|
|
322
|
+
return _cachedGeo(k, () => new THREE.SphereGeometry(r, ws, hs));
|
|
323
|
+
}
|
|
324
|
+
function _torusGeo(r, tube, rs, ts) {
|
|
325
|
+
const k = `t${r.toFixed(4)}_${tube.toFixed(4)}_${rs}_${ts}`;
|
|
326
|
+
return _cachedGeo(k, () => new THREE.TorusGeometry(r, tube, rs, ts));
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const _DETAIL_THRESHOLD = 0.06;
|
|
330
|
+
|
|
117
331
|
function box(g, w, h, d, mat, x, y, z, rx, ry, rz) {
|
|
118
|
-
const m = new THREE.Mesh(
|
|
332
|
+
const m = new THREE.Mesh(_boxGeo(w, h, d), mat);
|
|
119
333
|
m.position.set(x, y, z);
|
|
120
334
|
if (rx) m.rotation.x = rx;
|
|
121
335
|
if (ry) m.rotation.y = ry;
|
|
@@ -124,41 +338,95 @@ function box(g, w, h, d, mat, x, y, z, rx, ry, rz) {
|
|
|
124
338
|
return m;
|
|
125
339
|
}
|
|
126
340
|
function cyl(g, rT, rB, h, mat, x, y, z, segs) {
|
|
127
|
-
const
|
|
341
|
+
const s = segs || (Math.max(rT, rB) < _DETAIL_THRESHOLD ? 8 : 16);
|
|
342
|
+
const m = new THREE.Mesh(_cylGeo(rT, rB, h, s), mat);
|
|
128
343
|
m.position.set(x, y, z);
|
|
129
344
|
g.add(m);
|
|
130
345
|
return m;
|
|
131
346
|
}
|
|
132
347
|
function sphere(g, r, mat, x, y, z) {
|
|
133
|
-
const
|
|
348
|
+
const lod = r < 0.02 ? [8, 6] : r < _DETAIL_THRESHOLD ? [12, 8] : [20, 14];
|
|
349
|
+
const m = new THREE.Mesh(_sphereGeo(r, lod[0], lod[1]), mat);
|
|
134
350
|
m.position.set(x, y, z);
|
|
135
351
|
g.add(m);
|
|
136
352
|
return m;
|
|
137
353
|
}
|
|
138
354
|
function torus(g, r, tube, mat, x, y, z) {
|
|
139
|
-
const
|
|
355
|
+
const rs = tube < 0.015 ? 6 : 10;
|
|
356
|
+
const ts = r < _DETAIL_THRESHOLD ? 16 : 28;
|
|
357
|
+
const m = new THREE.Mesh(_torusGeo(r, tube, rs, ts), mat);
|
|
140
358
|
m.position.set(x, y, z);
|
|
141
359
|
m.rotation.x = Math.PI / 2;
|
|
142
360
|
g.add(m);
|
|
143
361
|
return m;
|
|
144
362
|
}
|
|
145
363
|
function pistonGeo(g, r, len, mat, x, y, z, rx, ry, rz) {
|
|
146
|
-
const shaft = cyl(g, r, r, len, mat, x, y, z,
|
|
364
|
+
const shaft = cyl(g, r, r, len, mat, x, y, z, 6);
|
|
147
365
|
if (rx) shaft.rotation.x = rx; if (ry) shaft.rotation.y = ry; if (rz) shaft.rotation.z = rz;
|
|
148
|
-
const capG =
|
|
366
|
+
const capG = _cylGeo(r * 1.7, r * 1.7, len * 0.09, 6);
|
|
149
367
|
const c1 = new THREE.Mesh(capG, MAT.rivetMat); c1.position.y = len / 2; shaft.add(c1);
|
|
150
368
|
const c2 = new THREE.Mesh(capG, MAT.rivetMat); c2.position.y = -len / 2; shaft.add(c2);
|
|
151
369
|
return shaft;
|
|
152
370
|
}
|
|
153
|
-
function rivet(g, x, y, z) {
|
|
371
|
+
function rivet(g, x, y, z) {
|
|
372
|
+
const m = new THREE.Mesh(_sphereGeo(0.028, 6, 4), MAT.rivetMat);
|
|
373
|
+
m.position.set(x, y, z); m.userData._detail = true; g.add(m); return m;
|
|
374
|
+
}
|
|
154
375
|
function ventSlits(g, w, n, sp, mat, x, y, z) {
|
|
155
376
|
for (let i = 0; i < n; i++) box(g, w, 0.02, 0.05, mat, x, y + i * sp, z);
|
|
156
377
|
}
|
|
157
378
|
function cableRun(g, r, len, x, y, z, rx, ry, rz) {
|
|
158
|
-
const m = cyl(g, r, r, len, MAT.cableMat, x, y, z,
|
|
379
|
+
const m = cyl(g, r, r, len, MAT.cableMat, x, y, z, 4);
|
|
380
|
+
m.userData._detail = true;
|
|
159
381
|
if (rx) m.rotation.x = rx; if (ry) m.rotation.y = ry; if (rz) m.rotation.z = rz;
|
|
160
382
|
return m;
|
|
161
383
|
}
|
|
384
|
+
function servoDrum(g, r, w, x, y, z, rz) {
|
|
385
|
+
const drum = cyl(g, r, r, w, MAT.servo, x, y, z, 10);
|
|
386
|
+
if (rz) drum.rotation.z = rz; else drum.rotation.z = Math.PI / 2;
|
|
387
|
+
const cap1 = cyl(g, r * 0.5, r * 0.5, w * 0.15, MAT.chrome, x, y, z, 6);
|
|
388
|
+
cap1.rotation.z = drum.rotation.z;
|
|
389
|
+
torus(g, r * 0.85, r * 0.08, MAT.darkChrome, x, y, z);
|
|
390
|
+
return drum;
|
|
391
|
+
}
|
|
392
|
+
function hydraulicRam(g, r, len, x, y, z, rx, ry, rz) {
|
|
393
|
+
const outer = cyl(g, r, r, len * 0.55, MAT.hydraulic, x, y, z, 8);
|
|
394
|
+
if (rx) outer.rotation.x = rx; if (ry) outer.rotation.y = ry; if (rz) outer.rotation.z = rz;
|
|
395
|
+
const inner = cyl(g, r * 0.6, r * 0.6, len * 0.65, MAT.pistonMat, x, y, z, 6);
|
|
396
|
+
inner.rotation.x = outer.rotation.x; inner.rotation.y = outer.rotation.y; inner.rotation.z = outer.rotation.z;
|
|
397
|
+
const seal = cyl(g, r * 1.2, r * 1.2, len * 0.06, MAT.rubber, x, y, z, 8);
|
|
398
|
+
seal.rotation.x = outer.rotation.x; seal.rotation.y = outer.rotation.y; seal.rotation.z = outer.rotation.z;
|
|
399
|
+
return outer;
|
|
400
|
+
}
|
|
401
|
+
function coolingFins(g, count, w, h, spacing, x, y, z, vertical) {
|
|
402
|
+
for (let i = 0; i < count; i++) {
|
|
403
|
+
const offset = (i - (count - 1) / 2) * spacing;
|
|
404
|
+
if (vertical) {
|
|
405
|
+
box(g, 0.015, h, w, MAT.coolingFin, x + offset, y, z);
|
|
406
|
+
} else {
|
|
407
|
+
box(g, w, 0.015, h, MAT.coolingFin, x, y + offset, z);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function boltCluster(g, count, radius, x, y, z) {
|
|
412
|
+
for (let i = 0; i < count; i++) {
|
|
413
|
+
const a = (i / count) * Math.PI * 2;
|
|
414
|
+
rivet(g, x + Math.cos(a) * radius, y, z + Math.sin(a) * radius);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function rubberSeal(g, r, tube, x, y, z) {
|
|
418
|
+
const m = new THREE.Mesh(_torusGeo(r, tube, 6, 16), MAT.rubber);
|
|
419
|
+
m.position.set(x, y, z); m.rotation.x = Math.PI / 2;
|
|
420
|
+
m.userData._detail = true; g.add(m); return m;
|
|
421
|
+
}
|
|
422
|
+
function cableBundle(g, count, r, len, x, y, z, rx, ry, rz) {
|
|
423
|
+
for (let i = 0; i < count; i++) {
|
|
424
|
+
const a = (i / count) * Math.PI * 2;
|
|
425
|
+
const ox = Math.cos(a) * r * 2.5;
|
|
426
|
+
const oz = Math.sin(a) * r * 2.5;
|
|
427
|
+
cableRun(g, r, len, x + ox, y, z + oz, rx, ry, rz);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
162
430
|
|
|
163
431
|
/* ══════════════════════════════════════════════════════════
|
|
164
432
|
Build chassis — HV-MTL Activated inspired mecha
|
|
@@ -168,100 +436,329 @@ function buildChassis() {
|
|
|
168
436
|
const g = new THREE.Group();
|
|
169
437
|
glowRings = [];
|
|
170
438
|
exhaustFlames = [];
|
|
439
|
+
heatShimmerMeshes = [];
|
|
440
|
+
|
|
441
|
+
// ── Animatable pivot groups (head + arms) ──
|
|
442
|
+
const _hp = new THREE.Group(); _hp.position.set(0, 5.85, 0);
|
|
443
|
+
const hd = new THREE.Group(); hd.position.set(0, -5.85, 0); _hp.add(hd);
|
|
444
|
+
const _lp = new THREE.Group(); _lp.position.set(-1.75, 5.25, 0);
|
|
445
|
+
const la = new THREE.Group(); la.position.set(1.75, -5.25, 0); _lp.add(la);
|
|
446
|
+
const _rp = new THREE.Group(); _rp.position.set(1.75, 5.25, 0);
|
|
447
|
+
const ra = new THREE.Group(); ra.position.set(-1.75, -5.25, 0); _rp.add(ra);
|
|
448
|
+
|
|
449
|
+
// ══════════════════════════════════════════════════════════
|
|
450
|
+
// HEAD — Refined helmet with advanced multi-element optical array
|
|
451
|
+
// ══════════════════════════════════════════════════════════
|
|
452
|
+
|
|
453
|
+
// ── Inner cranium (processor housing visible through panel gaps) ──
|
|
454
|
+
box(hd, 0.82, 0.72, 0.72, MAT.servo, 0, 6.4, 0);
|
|
455
|
+
box(hd, 0.74, 0.66, 0.66, MAT.frameMat, 0, 6.4, 0);
|
|
456
|
+
// Processor board detail (visible through top gap)
|
|
457
|
+
box(hd, 0.4, 0.02, 0.35, MAT.vent, 0, 6.72, 0);
|
|
458
|
+
for (let i = 0; i < 3; i++)
|
|
459
|
+
box(hd, 0.06, 0.04, 0.06, MAT.coreInner, -0.12 + i * 0.12, 6.73, 0.08);
|
|
460
|
+
|
|
461
|
+
// ── Outer helmet (split halves with layered sub-panels) ──
|
|
462
|
+
box(hd, 0.58, 0.98, 0.98, MAT.armor, -0.3, 6.44, 0);
|
|
463
|
+
box(hd, 0.58, 0.98, 0.98, MAT.armor, 0.3, 6.44, 0);
|
|
464
|
+
// Central seam
|
|
465
|
+
box(hd, 0.03, 0.98, 0.06, MAT.panelLine, 0, 6.44, 0.47);
|
|
466
|
+
box(hd, 0.02, 0.75, 0.04, MAT.weldSeam, -0.16, 6.52, 0.47);
|
|
467
|
+
box(hd, 0.02, 0.75, 0.04, MAT.weldSeam, 0.16, 6.52, 0.47);
|
|
468
|
+
// Sub-panel chrome inlays
|
|
469
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
470
|
+
box(hd, 0.05, 0.55, 0.48, MAT.darkChrome, side * 0.32, 6.64, 0.22);
|
|
471
|
+
box(hd, 0.03, 0.35, 0.28, MAT.servo, side * 0.3, 6.74, 0.32);
|
|
472
|
+
// Temple status indicator lights (3 per side)
|
|
473
|
+
for (let i = 0; i < 3; i++)
|
|
474
|
+
sphere(hd, 0.015, MAT.vent, side * 0.58, 6.62 + i * 0.1, 0.3);
|
|
475
|
+
}
|
|
171
476
|
|
|
172
|
-
// ──
|
|
173
|
-
box(
|
|
174
|
-
|
|
175
|
-
box(
|
|
176
|
-
box(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
box(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
//
|
|
477
|
+
// ── Crest (taller, multi-layered Prime fin) ──
|
|
478
|
+
box(hd, 0.2, 0.55, 0.75, MAT.chrome, 0, 7.16, -0.04);
|
|
479
|
+
box(hd, 0.12, 0.32, 0.55, MAT.armorRed, 0, 7.34, -0.04);
|
|
480
|
+
box(hd, 0.07, 0.18, 0.4, MAT.chrome, 0, 7.44, -0.04);
|
|
481
|
+
box(hd, 0.04, 0.1, 0.25, MAT.coreInner, 0, 7.5, -0.04);
|
|
482
|
+
for (let i = 0; i < 5; i++)
|
|
483
|
+
box(hd, 0.14, 0.025, 0.52, MAT.frameMat, 0, 6.98 + i * 0.11, -0.04);
|
|
484
|
+
|
|
485
|
+
// ── Brow plate (heavy chrome visor shield, bolted) ──
|
|
486
|
+
box(hd, 1.28, 0.18, 0.42, MAT.chrome, 0, 6.72, 0.3, -0.1, 0, 0);
|
|
487
|
+
box(hd, 1.0, 0.08, 0.32, MAT.darkChrome, 0, 6.66, 0.34, -0.08, 0, 0);
|
|
488
|
+
// Underside of brow (recessed shadow area above visor)
|
|
489
|
+
box(hd, 1.2, 0.06, 0.12, MAT.servo, 0, 6.62, 0.42);
|
|
490
|
+
boltCluster(hd, 5, 0.1, -0.42, 6.72, 0.46);
|
|
491
|
+
boltCluster(hd, 5, 0.1, 0.42, 6.72, 0.46);
|
|
492
|
+
|
|
493
|
+
// ══════════════════════════════════════════════════════════
|
|
494
|
+
// VISION SYSTEM — Recessed optical housing with multi-element array
|
|
495
|
+
// ══════════════════════════════════════════════════════════
|
|
496
|
+
|
|
497
|
+
// Visor housing recess (dark channel cut into helmet face)
|
|
498
|
+
box(hd, 1.24, 0.3, 0.12, MAT.servo, 0, 6.44, 0.44);
|
|
499
|
+
box(hd, 1.18, 0.26, 0.06, MAT.frameMat, 0, 6.44, 0.46);
|
|
500
|
+
|
|
501
|
+
// Main visor glass (curved band sitting in the recess)
|
|
502
|
+
visorMesh = box(hd, 1.2, 0.24, 0.05, MAT.visor, 0, 6.44, 0.5);
|
|
503
|
+
|
|
504
|
+
// Chrome visor frame (top rail, bottom rail, side brackets)
|
|
505
|
+
box(hd, 1.26, 0.03, 0.1, MAT.chrome, 0, 6.57, 0.48);
|
|
506
|
+
box(hd, 1.26, 0.03, 0.1, MAT.chrome, 0, 6.31, 0.48);
|
|
507
|
+
box(hd, 0.05, 0.3, 0.1, MAT.chrome, -0.62, 6.44, 0.48);
|
|
508
|
+
box(hd, 0.05, 0.3, 0.1, MAT.chrome, 0.62, 6.44, 0.48);
|
|
509
|
+
// Corner brackets
|
|
510
|
+
for (let sx = -1; sx <= 1; sx += 2)
|
|
511
|
+
for (let sy = -1; sy <= 1; sy += 2)
|
|
512
|
+
rivet(hd, sx * 0.58, 6.44 + sy * 0.12, 0.52);
|
|
513
|
+
|
|
514
|
+
// Inner visor glow plate (HUD backlight)
|
|
515
|
+
box(hd, 1.14, 0.2, 0.015, MAT.coreInner, 0, 6.44, 0.43);
|
|
516
|
+
|
|
517
|
+
// ── Primary optics (2 main camera barrels — left and right of center) ──
|
|
189
518
|
for (let side = -1; side <= 1; side += 2) {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
519
|
+
const ox = side * 0.22;
|
|
520
|
+
// Outer barrel housing (chrome)
|
|
521
|
+
cyl(hd, 0.075, 0.075, 0.1, MAT.darkChrome, ox, 6.44, 0.42, 16);
|
|
522
|
+
// Lens element 1 (outer glass)
|
|
523
|
+
cyl(hd, 0.06, 0.055, 0.03, MAT.lens, ox, 6.44, 0.46, 16);
|
|
524
|
+
// Lens element 2 (inner, brighter)
|
|
525
|
+
cyl(hd, 0.04, 0.035, 0.02, MAT.core, ox, 6.44, 0.47, 16);
|
|
526
|
+
// Pupil (bright emissive center)
|
|
527
|
+
sphere(hd, 0.022, MAT.coreInner, ox, 6.44, 0.48);
|
|
528
|
+
// Iris ring
|
|
529
|
+
torus(hd, 0.05, 0.008, MAT.vent, ox, 6.44, 0.46);
|
|
530
|
+
// Focus ring (chrome)
|
|
531
|
+
torus(hd, 0.065, 0.006, MAT.chrome, ox, 6.44, 0.44);
|
|
532
|
+
// Barrel mount bolts
|
|
533
|
+
boltCluster(hd, 6, 0.07, ox, 6.44, 0.42);
|
|
193
534
|
}
|
|
194
|
-
|
|
535
|
+
|
|
536
|
+
// ── Central targeting sensor (between main optics) ──
|
|
537
|
+
cyl(hd, 0.03, 0.03, 0.08, MAT.darkChrome, 0, 6.44, 0.44, 12);
|
|
538
|
+
sphere(hd, 0.018, MAT.ventHot, 0, 6.44, 0.48);
|
|
539
|
+
torus(hd, 0.025, 0.004, MAT.chrome, 0, 6.44, 0.46);
|
|
540
|
+
|
|
541
|
+
// ── Peripheral sensors (small wide-angle lenses at visor edges) ──
|
|
195
542
|
for (let side = -1; side <= 1; side += 2) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
box(
|
|
204
|
-
box(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Forehead glow
|
|
208
|
-
box(g, 0.6, 0.04, 0.05, MAT.panelLine, 0, 6.74, 0.38);
|
|
209
|
-
// Antenna fins (mount base + fin + glow strip)
|
|
543
|
+
const px = side * 0.48;
|
|
544
|
+
cyl(hd, 0.025, 0.025, 0.05, MAT.frameMat, px, 6.44, 0.44, 10);
|
|
545
|
+
cyl(hd, 0.018, 0.015, 0.02, MAT.lens, px, 6.44, 0.47, 10);
|
|
546
|
+
sphere(hd, 0.01, MAT.coreInner, px, 6.44, 0.48);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ── Scanning emitter bar (thin line below visor that sweeps) ──
|
|
550
|
+
box(hd, 1.1, 0.02, 0.04, MAT.vent, 0, 6.3, 0.49);
|
|
551
|
+
box(hd, 0.8, 0.012, 0.02, MAT.coreInner, 0, 6.3, 0.5);
|
|
552
|
+
|
|
553
|
+
// ── Infrared/thermal sensors (above visor, recessed) ──
|
|
210
554
|
for (let side = -1; side <= 1; side += 2) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
box(g, 0.02, 0.35, 0.04, MAT.panelLine, side * 0.56, 6.95, -0.05, 0, 0, side * 0.2);
|
|
214
|
-
rivet(g, side * 0.51, 6.78, -0.02);
|
|
555
|
+
cyl(hd, 0.02, 0.02, 0.04, MAT.darkChrome, side * 0.35, 6.6, 0.44, 8);
|
|
556
|
+
sphere(hd, 0.012, MAT.ventHot, side * 0.35, 6.6, 0.46);
|
|
215
557
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
558
|
+
|
|
559
|
+
// ── Under-visor data strip (status readout line) ──
|
|
560
|
+
box(hd, 0.9, 0.025, 0.03, MAT.panelLine, 0, 6.28, 0.48);
|
|
561
|
+
|
|
562
|
+
// ══════════════════════════════════════════════════════════
|
|
563
|
+
// FACEPLATE / JAW (segmented with jaw actuators)
|
|
564
|
+
// ══════════════════════════════════════════════════════════
|
|
565
|
+
|
|
566
|
+
// Upper faceplate
|
|
567
|
+
box(hd, 0.82, 0.2, 0.54, MAT.chrome, 0, 6.12, 0.15);
|
|
568
|
+
box(hd, 0.64, 0.08, 0.44, MAT.darkChrome, 0, 6.04, 0.2);
|
|
569
|
+
// Faceplate vent grille (articulated slats)
|
|
570
|
+
for (let i = 0; i < 6; i++) {
|
|
571
|
+
box(hd, 0.5, 0.015, 0.06, MAT.vent, 0, 6.16 - i * 0.032, 0.42);
|
|
572
|
+
box(hd, 0.48, 0.008, 0.04, MAT.frameMat, 0, 6.16 - i * 0.032, 0.44);
|
|
573
|
+
}
|
|
574
|
+
// Lower jaw / chin guard
|
|
575
|
+
box(hd, 0.72, 0.18, 0.48, MAT.chrome, 0, 5.88, 0.16);
|
|
576
|
+
box(hd, 0.52, 0.06, 0.36, MAT.servo, 0, 5.82, 0.2);
|
|
577
|
+
// Jaw actuator pistons (visible, one per side)
|
|
578
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
579
|
+
hydraulicRam(hd, 0.015, 0.2, side * 0.32, 5.95, 0.08);
|
|
580
|
+
rivet(hd, side * 0.34, 6.04, 0.38);
|
|
581
|
+
rivet(hd, side * 0.3, 5.86, 0.38);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// ══════════════════════════════════════════════════════════
|
|
585
|
+
// CHEEK GUARDS (angular, with exposed internals)
|
|
586
|
+
// ══════════════════════════════════════════════════════════
|
|
587
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
588
|
+
box(hd, 0.24, 0.56, 0.82, MAT.armor, side * 0.58, 6.34, 0.04, 0, 0, side * 0.08);
|
|
589
|
+
box(hd, 0.12, 0.38, 0.66, MAT.darkChrome, side * 0.62, 6.26, 0.01, 0, 0, side * 0.08);
|
|
590
|
+
// Cheek inset panel (servo exposed)
|
|
591
|
+
box(hd, 0.06, 0.24, 0.38, MAT.servo, side * 0.6, 6.18, -0.08, 0, 0, side * 0.08);
|
|
592
|
+
cyl(hd, 0.04, 0.04, 0.06, MAT.hydraulic, side * 0.6, 6.08, 0.02, 8);
|
|
593
|
+
// Cheek surface detail
|
|
594
|
+
box(hd, 0.02, 0.35, 0.04, MAT.panelLine, side * 0.52, 6.32, 0.38);
|
|
595
|
+
rivet(hd, side * 0.5, 6.72, 0.4); rivet(hd, side * 0.5, 6.18, 0.4);
|
|
596
|
+
rivet(hd, side * 0.52, 6.46, 0.44); rivet(hd, side * 0.54, 6.02, 0.36);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ══════════════════════════════════════════════════════════
|
|
600
|
+
// EAR MODULES (sensor arrays + cooling + comms antenna)
|
|
601
|
+
// ══════════════════════════════════════════════════════════
|
|
602
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
603
|
+
// Main housing
|
|
604
|
+
box(hd, 0.14, 0.46, 0.24, MAT.frameMat, side * 0.7, 6.38, -0.12);
|
|
605
|
+
box(hd, 0.08, 0.34, 0.2, MAT.servo, side * 0.72, 6.38, -0.14);
|
|
606
|
+
// Vent grille (5 slats)
|
|
607
|
+
for (let i = 0; i < 5; i++)
|
|
608
|
+
box(hd, 0.07, 0.05, 0.2, MAT.vent, side * 0.72, 6.26 + i * 0.065, -0.12);
|
|
609
|
+
// Cooling radiator
|
|
610
|
+
coolingFins(hd, 6, 0.18, 0.12, 0.032, side * 0.74, 6.38, -0.24, true);
|
|
611
|
+
// Side-mounted sensor lens
|
|
612
|
+
sphere(hd, 0.025, MAT.lens, side * 0.7, 6.54, -0.02);
|
|
613
|
+
// Comms antenna nub
|
|
614
|
+
cyl(hd, 0.012, 0.008, 0.12, MAT.chrome, side * 0.72, 6.64, -0.14, 6);
|
|
615
|
+
sphere(hd, 0.015, MAT.vent, side * 0.72, 6.7, -0.14);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
box(hd, 0.74, 0.04, 0.05, MAT.panelLine, 0, 6.8, 0.42);
|
|
619
|
+
|
|
620
|
+
// ══════════════════════════════════════════════════════════
|
|
621
|
+
// ANTENNA FINS (taller, with wiring and tip lights)
|
|
622
|
+
// ══════════════════════════════════════════════════════════
|
|
623
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
624
|
+
box(hd, 0.09, 0.14, 0.2, MAT.frameMat, side * 0.55, 6.84, -0.1);
|
|
625
|
+
box(hd, 0.08, 0.8, 0.26, MAT.chrome, side * 0.6, 7.06, -0.1, 0, 0, side * 0.16);
|
|
626
|
+
box(hd, 0.04, 0.6, 0.07, MAT.panelLine, side * 0.61, 7.12, -0.04, 0, 0, side * 0.16);
|
|
627
|
+
cableRun(hd, 0.01, 0.5, side * 0.57, 6.98, -0.2, 0, 0, side * 0.16);
|
|
628
|
+
rivet(hd, side * 0.56, 6.84, -0.02);
|
|
629
|
+
// Tip light
|
|
630
|
+
sphere(hd, 0.022, MAT.coreInner, side * 0.62, 7.42, -0.1);
|
|
631
|
+
// Secondary micro-antenna
|
|
632
|
+
cyl(hd, 0.006, 0.006, 0.15, MAT.chrome, side * 0.56, 7.4, -0.18, 6);
|
|
633
|
+
sphere(hd, 0.01, MAT.ventHot, side * 0.56, 7.48, -0.18);
|
|
221
634
|
}
|
|
222
635
|
|
|
223
|
-
//
|
|
224
|
-
|
|
636
|
+
// ══════════════════════════════════════════════════════════
|
|
637
|
+
// BACK OF HEAD (data ports, cooling radiator, cable harness)
|
|
638
|
+
// ══════════════════════════════════════════════════════════
|
|
639
|
+
box(hd, 0.92, 0.62, 0.16, MAT.darkChrome, 0, 6.42, -0.44);
|
|
640
|
+
// Data port connectors (6 total)
|
|
641
|
+
for (let i = 0; i < 6; i++) {
|
|
642
|
+
const px = -0.3 + i * 0.12;
|
|
643
|
+
cyl(hd, 0.05, 0.05, 0.1, MAT.frameMat, px, 6.42, -0.5, 8);
|
|
644
|
+
sphere(hd, 0.02, MAT.vent, px, 6.42, -0.54);
|
|
645
|
+
cyl(hd, 0.06, 0.06, 0.02, MAT.darkChrome, px, 6.42, -0.48, 8);
|
|
646
|
+
}
|
|
647
|
+
// Cooling radiator array
|
|
648
|
+
coolingFins(hd, 10, 0.6, 0.07, 0.042, 0, 6.18, -0.52, false);
|
|
649
|
+
// Cable harness exiting skull
|
|
650
|
+
cableBundle(hd, 4, 0.014, 0.4, 0, 6.08, -0.5);
|
|
651
|
+
cableBundle(hd, 2, 0.01, 0.3, 0.2, 6.08, -0.48);
|
|
652
|
+
cableBundle(hd, 2, 0.01, 0.3, -0.2, 6.08, -0.48);
|
|
653
|
+
|
|
654
|
+
// ── NECK (exposed servos, hydraulic actuators, cable harness) ──
|
|
655
|
+
// Central neck column
|
|
656
|
+
cyl(g, 0.2, 0.32, 0.55, MAT.servo, 0, 5.85, 0);
|
|
657
|
+
// Rubber gaskets top and bottom
|
|
658
|
+
rubberSeal(g, 0.25, 0.02, 0, 6.08, 0);
|
|
659
|
+
rubberSeal(g, 0.34, 0.025, 0, 5.62, 0);
|
|
660
|
+
// Two neck servos (yaw + pitch)
|
|
661
|
+
servoDrum(g, 0.12, 0.18, 0.22, 5.95, 0);
|
|
662
|
+
servoDrum(g, 0.1, 0.14, -0.2, 5.78, 0);
|
|
663
|
+
// Hydraulic actuators (4 around neck for head tilt)
|
|
225
664
|
for (let i = 0; i < 4; i++) {
|
|
226
665
|
const a = (i / 4) * Math.PI * 2 + 0.4;
|
|
227
|
-
|
|
666
|
+
const cx = Math.cos(a) * 0.26, cz = Math.sin(a) * 0.26;
|
|
667
|
+
hydraulicRam(g, 0.022, 0.45, cx, 5.85, cz);
|
|
228
668
|
}
|
|
229
|
-
|
|
669
|
+
// Cable harness running through neck
|
|
670
|
+
cableBundle(g, 5, 0.014, 0.5, 0, 5.85, 0.22);
|
|
671
|
+
cableBundle(g, 3, 0.012, 0.5, 0, 5.85, -0.22);
|
|
672
|
+
const neckRing = torus(g, 0.35, 0.05, MAT.vent, 0, 5.85, 0);
|
|
230
673
|
glowRings.push(neckRing);
|
|
231
|
-
const neckRing2 = torus(g, 0.
|
|
674
|
+
const neckRing2 = torus(g, 0.3, 0.03, MAT.panelLine, 0, 5.72, 0);
|
|
232
675
|
glowRings.push(neckRing2);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
//
|
|
248
|
-
box(g, 2.0, 0.6, 0.12, MAT.chrome, 0, 5.0, 0.66);
|
|
249
|
-
box(g, 1.6, 0.35, 0.06, MAT.darkChrome, 0, 5.15, 0.7);
|
|
250
|
-
// Lower chest plate
|
|
251
|
-
box(g, 1.8, 0.4, 0.12, MAT.armorLt, 0, 4.4, 0.7);
|
|
252
|
-
// Side flanks (angled outward)
|
|
676
|
+
const neckRing3 = torus(g, 0.28, 0.02, MAT.darkChrome, 0, 5.98, 0);
|
|
677
|
+
// Collar plate
|
|
678
|
+
box(g, 1.75, 0.2, 0.88, MAT.darkChrome, 0, 5.6, 0);
|
|
679
|
+
boltCluster(g, 6, 0.12, 0, 5.6, 0.42);
|
|
680
|
+
|
|
681
|
+
// ── TORSO (visible internal frame, hydraulic rams, servo mounts, radiator) ──
|
|
682
|
+
// Central spine column (visible endoskeleton)
|
|
683
|
+
box(g, 0.4, 5.0, 0.4, MAT.frameMat, 0, 3.6, -0.15);
|
|
684
|
+
// Internal cross-ribs (exposed structural skeleton)
|
|
685
|
+
for (let i = 0; i < 6; i++) {
|
|
686
|
+
box(g, 1.6, 0.05, 0.22, MAT.frameMat, 0, 2.85 + i * 0.45, 0);
|
|
687
|
+
// Weld seam on each rib
|
|
688
|
+
box(g, 1.2, 0.02, 0.02, MAT.weldSeam, 0, 2.87 + i * 0.45, 0.11);
|
|
689
|
+
}
|
|
690
|
+
// Internal servo mounts (visible in torso gap)
|
|
253
691
|
for (let side = -1; side <= 1; side += 2) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
692
|
+
servoDrum(g, 0.08, 0.12, side * 0.4, 4.6, -0.1);
|
|
693
|
+
servoDrum(g, 0.07, 0.1, side * 0.35, 3.4, -0.08);
|
|
694
|
+
}
|
|
695
|
+
// Hydraulic rams connecting chest to waist (visible in side gaps)
|
|
696
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
697
|
+
hydraulicRam(g, 0.035, 1.4, side * 0.95, 3.5, -0.35);
|
|
698
|
+
hydraulicRam(g, 0.028, 1.0, side * 0.85, 3.8, 0.35);
|
|
699
|
+
}
|
|
700
|
+
// Main chest plates — RED L/R split
|
|
701
|
+
box(g, 1.15, 2.8, 1.35, MAT.armorRed, -0.58, 4.0, 0);
|
|
702
|
+
box(g, 1.15, 2.8, 1.35, MAT.armorRed, 0.58, 4.0, 0);
|
|
703
|
+
// Central seam with weld detail
|
|
704
|
+
box(g, 0.04, 2.6, 0.06, MAT.panelLine, 0, 4.0, 0.68);
|
|
705
|
+
box(g, 0.02, 2.4, 0.03, MAT.weldSeam, 0, 4.0, 0.71);
|
|
706
|
+
// Windshield-style chest windows
|
|
707
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
708
|
+
box(g, 0.55, 0.7, 0.06, MAT.visor, side * 0.42, 4.6, 0.7);
|
|
709
|
+
box(g, 0.04, 0.76, 0.08, MAT.chrome, side * 0.14, 4.6, 0.7);
|
|
710
|
+
// Window frame bolts
|
|
711
|
+
rivet(g, side * 0.62, 4.88, 0.72); rivet(g, side * 0.62, 4.32, 0.72);
|
|
712
|
+
rivet(g, side * 0.22, 4.88, 0.72); rivet(g, side * 0.22, 4.32, 0.72);
|
|
713
|
+
}
|
|
714
|
+
// Collar / gorget — heavy chrome, bolted
|
|
715
|
+
box(g, 1.88, 0.26, 1.02, MAT.chrome, 0, 5.38, 0);
|
|
716
|
+
box(g, 1.52, 0.08, 0.56, MAT.panelLine, 0, 5.48, 0.38);
|
|
717
|
+
boltCluster(g, 6, 0.16, 0, 5.38, 0.5);
|
|
718
|
+
// Upper chest chevron — chrome accent
|
|
719
|
+
box(g, 2.22, 0.66, 0.14, MAT.chrome, 0, 5.02, 0.68);
|
|
720
|
+
box(g, 1.72, 0.4, 0.07, MAT.darkChrome, 0, 5.18, 0.72);
|
|
721
|
+
// Lower chest
|
|
722
|
+
box(g, 1.92, 0.44, 0.14, MAT.armor, 0, 4.4, 0.72);
|
|
723
|
+
// Side flanks — blue armor with exposed mechanical sub-layer
|
|
724
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
725
|
+
box(g, 0.34, 2.2, 1.18, MAT.armor, side * 1.22, 4.1, 0, 0, 0, side * -0.08);
|
|
726
|
+
box(g, 0.12, 1.8, 0.92, MAT.darkChrome, side * 1.3, 4.1, 0, 0, 0, side * -0.08);
|
|
727
|
+
// Visible servo + cable through flank gap
|
|
728
|
+
box(g, 0.06, 0.8, 0.3, MAT.servo, side * 1.08, 4.5, -0.2, 0, 0, side * -0.08);
|
|
729
|
+
cableBundle(g, 2, 0.01, 1.4, side * 1.05, 4.1, 0.3);
|
|
730
|
+
rivet(g, side * 1.12, 5.0, 0.58); rivet(g, side * 1.12, 3.2, 0.58);
|
|
731
|
+
rivet(g, side * 1.12, 4.3, 0.58); rivet(g, side * 1.12, 3.8, 0.58);
|
|
732
|
+
}
|
|
733
|
+
// Back plate — layered armor with exposed mechanical sub-structure
|
|
734
|
+
box(g, 1.94, 2.44, 0.24, MAT.chrome, 0, 4.1, -0.72);
|
|
735
|
+
box(g, 1.44, 1.84, 0.12, MAT.darkChrome, 0, 4.2, -0.78);
|
|
736
|
+
// Central spine channel recess
|
|
737
|
+
box(g, 0.26, 2.0, 0.06, MAT.servo, 0, 4.1, -0.84);
|
|
738
|
+
// Upper + lower horizontal reinforcement ribs
|
|
739
|
+
box(g, 1.6, 0.06, 0.08, MAT.frameMat, 0, 5.1, -0.78);
|
|
740
|
+
box(g, 1.6, 0.06, 0.08, MAT.frameMat, 0, 3.1, -0.78);
|
|
741
|
+
box(g, 1.3, 0.06, 0.06, MAT.weldSeam, 0, 4.1, -0.82);
|
|
742
|
+
// Back armor panel lines (vertical + cross)
|
|
743
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
744
|
+
box(g, 0.04, 2.2, 0.06, MAT.panelLine, side * 0.62, 4.1, -0.78);
|
|
745
|
+
box(g, 0.04, 1.4, 0.06, MAT.panelLine, side * 0.3, 4.1, -0.78);
|
|
746
|
+
// Exposed servo blocks visible through back gaps
|
|
747
|
+
box(g, 0.14, 0.4, 0.08, MAT.servo, side * 0.48, 4.5, -0.82);
|
|
748
|
+
box(g, 0.14, 0.4, 0.08, MAT.servo, side * 0.48, 3.7, -0.82);
|
|
749
|
+
// Back plate rivets
|
|
750
|
+
rivet(g, side * 0.7, 5.0, -0.78); rivet(g, side * 0.7, 3.2, -0.78);
|
|
751
|
+
rivet(g, side * 0.4, 4.8, -0.78); rivet(g, side * 0.4, 3.4, -0.78);
|
|
752
|
+
}
|
|
753
|
+
// Primary radiator cooling array
|
|
754
|
+
coolingFins(g, 14, 1.3, 0.09, 0.055, 0, 4.2, -0.88, false);
|
|
755
|
+
// Secondary lower radiator
|
|
756
|
+
coolingFins(g, 8, 0.8, 0.06, 0.045, 0, 3.2, -0.86, false);
|
|
757
|
+
// Ab plates — chrome segments with bolts
|
|
262
758
|
for (let side = -1; side <= 1; side += 2) {
|
|
263
759
|
for (let i = 0; i < 3; i++) {
|
|
264
|
-
box(g, 0.
|
|
760
|
+
box(g, 0.44, 0.3, 0.13, MAT.chrome, side * 0.36, 3.0 + i * 0.35, 0.7);
|
|
761
|
+
rivet(g, side * 0.36, 3.1 + i * 0.35, 0.78);
|
|
265
762
|
}
|
|
266
763
|
}
|
|
267
764
|
// Panel line accents
|
|
@@ -270,6 +767,53 @@ function buildChassis() {
|
|
|
270
767
|
box(g, 1.4, 0.04, 0.06, MAT.panelLine, 0, 3.2, 0.68);
|
|
271
768
|
box(g, 1.4, 0.04, 0.06, MAT.panelLine, 0, 4.8, 0.68);
|
|
272
769
|
|
|
770
|
+
// ── DECALS (unit markings, hazard stripes, caution labels) ──
|
|
771
|
+
function decalTex(w, h, fn) {
|
|
772
|
+
const c = document.createElement("canvas"); c.width = w; c.height = h;
|
|
773
|
+
fn(c.getContext("2d"), w, h);
|
|
774
|
+
const t = new THREE.CanvasTexture(c);
|
|
775
|
+
t.minFilter = THREE.LinearFilter; t.generateMipmaps = false;
|
|
776
|
+
return new P({ map: t, transparent: true, depthWrite: false, roughness: 0.5, metalness: 0.3 });
|
|
777
|
+
}
|
|
778
|
+
// Left chest: unit designation
|
|
779
|
+
const unitMat = decalTex(256, 64, (ctx, w, h) => {
|
|
780
|
+
ctx.clearRect(0, 0, w, h);
|
|
781
|
+
ctx.font = "bold 36px monospace"; ctx.fillStyle = "#e0e8ff";
|
|
782
|
+
ctx.globalAlpha = 0.65; ctx.fillText("AC-01", 12, 40);
|
|
783
|
+
ctx.font = "11px monospace"; ctx.fillStyle = "#6688cc";
|
|
784
|
+
ctx.globalAlpha = 0.5; ctx.fillText("APECLAW PRIME", 12, 56);
|
|
785
|
+
});
|
|
786
|
+
const unitDecal = new THREE.Mesh(new THREE.PlaneGeometry(0.65, 0.17), unitMat);
|
|
787
|
+
unitDecal.position.set(-0.58, 4.68, 0.74); unitDecal.renderOrder = 3;
|
|
788
|
+
g.add(unitDecal);
|
|
789
|
+
// Right chest: faction badge (blue/chrome stripe)
|
|
790
|
+
const hazMat = decalTex(256, 128, (ctx, w, h) => {
|
|
791
|
+
ctx.clearRect(0, 0, w, h);
|
|
792
|
+
ctx.globalAlpha = 0.5;
|
|
793
|
+
for (let i = 0; i < 8; i++) {
|
|
794
|
+
ctx.fillStyle = i % 2 === 0 ? "#3355aa" : "#c0ccdd";
|
|
795
|
+
ctx.beginPath();
|
|
796
|
+
ctx.moveTo(i * 32, 0); ctx.lineTo(i * 32 + 32, 0);
|
|
797
|
+
ctx.lineTo(i * 32 + 16, h); ctx.lineTo(i * 32 - 16, h);
|
|
798
|
+
ctx.closePath(); ctx.fill();
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
const hazDecal = new THREE.Mesh(new THREE.PlaneGeometry(0.5, 0.25), hazMat);
|
|
802
|
+
hazDecal.position.set(0.58, 4.22, 0.74); hazDecal.renderOrder = 3;
|
|
803
|
+
g.add(hazDecal);
|
|
804
|
+
// Waist caution strip
|
|
805
|
+
const cautionMat = decalTex(192, 32, (ctx, w, h) => {
|
|
806
|
+
ctx.clearRect(0, 0, w, h);
|
|
807
|
+
ctx.globalAlpha = 0.3;
|
|
808
|
+
for (let x = 0; x < w; x += 16) {
|
|
809
|
+
ctx.fillStyle = (x / 16) % 2 === 0 ? "#cc2233" : "#1a2030";
|
|
810
|
+
ctx.fillRect(x, 0, 16, h);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
const cautionDecal = new THREE.Mesh(new THREE.PlaneGeometry(1.1, 0.06), cautionMat);
|
|
814
|
+
cautionDecal.position.set(0, 2.74, 0.56); cautionDecal.renderOrder = 3;
|
|
815
|
+
g.add(cautionDecal);
|
|
816
|
+
|
|
273
817
|
// ── CORE REACTOR (multi-ring housing) ──
|
|
274
818
|
coreMesh = new THREE.Mesh(new THREE.OctahedronGeometry(0.3), MAT.coreInner);
|
|
275
819
|
coreMesh.position.set(0, 4.5, 0.7);
|
|
@@ -290,195 +834,828 @@ function buildChassis() {
|
|
|
290
834
|
coreGlow.layers.enable(1);
|
|
291
835
|
sphere(g, 0.08, MAT.coreInner, 0, 4.5, 0.7);
|
|
292
836
|
|
|
293
|
-
// ── WAIST / PELVIS ──
|
|
294
|
-
box(g, 1.
|
|
295
|
-
box(g, 1.
|
|
296
|
-
|
|
297
|
-
|
|
837
|
+
// ── WAIST / PELVIS (servo mounts, hydraulic cross-links, bolted hip skirts) ──
|
|
838
|
+
box(g, 1.72, 0.76, 1.06, MAT.armor, 0, 2.35, 0);
|
|
839
|
+
box(g, 1.32, 0.56, 0.86, MAT.undersuit, 0, 2.35, 0);
|
|
840
|
+
// Internal waist servos (visible in gaps)
|
|
841
|
+
servoDrum(g, 0.1, 0.16, 0, 2.35, 0);
|
|
842
|
+
for (let side = -1; side <= 1; side += 2)
|
|
843
|
+
hydraulicRam(g, 0.025, 0.5, side * 0.55, 2.35, -0.3, 0, 0, side * 0.3);
|
|
844
|
+
// Chrome belt buckle
|
|
845
|
+
box(g, 1.92, 0.23, 0.17, MAT.chrome, 0, 2.72, 0.54);
|
|
846
|
+
box(g, 0.62, 0.17, 0.11, MAT.armorRed, 0, 2.72, 0.58);
|
|
847
|
+
boltCluster(g, 6, 0.14, 0, 2.72, 0.62);
|
|
848
|
+
// Hip guard skirts with bolt detail
|
|
298
849
|
for (let side = -1; side <= 1; side += 2) {
|
|
299
|
-
box(g, 0.
|
|
300
|
-
box(g, 0.
|
|
301
|
-
rivet(g, side * 0.
|
|
850
|
+
box(g, 0.52, 0.56, 0.76, MAT.armor, side * 0.73, 2.15, 0, 0, 0, side * -0.12);
|
|
851
|
+
box(g, 0.4, 0.15, 0.62, MAT.darkChrome, side * 0.75, 2.32, 0, 0, 0, side * -0.12);
|
|
852
|
+
rivet(g, side * 0.63, 2.52, 0.42); rivet(g, side * 0.63, 2.12, 0.42);
|
|
853
|
+
// Visible mechanical substructure in hip gap
|
|
854
|
+
box(g, 0.08, 0.3, 0.2, MAT.servo, side * 0.55, 2.1, -0.15, 0, 0, side * -0.12);
|
|
302
855
|
}
|
|
303
|
-
const waistRing = torus(g, 0.
|
|
856
|
+
const waistRing = torus(g, 0.96, 0.055, MAT.vent, 0, 2.72, 0);
|
|
304
857
|
glowRings.push(waistRing);
|
|
305
|
-
box(g, 0.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
sphere(
|
|
313
|
-
|
|
858
|
+
box(g, 0.86, 0.05, 0.07, MAT.panelLine, 0, 2.0, 0.55);
|
|
859
|
+
cableBundle(g, 4, 0.012, 1.3, 0, 2.58, -0.5);
|
|
860
|
+
|
|
861
|
+
// ── SHOULDERS (massive pauldrons, exposed servo + actuators) ──
|
|
862
|
+
function buildShoulder(tgt, side) {
|
|
863
|
+
const sx = side * 1.75;
|
|
864
|
+
// Ball joint with rubber gasket
|
|
865
|
+
sphere(tgt, 0.45, MAT.joint, sx, 5.25, 0);
|
|
866
|
+
rubberSeal(tgt, 0.42, 0.025, sx, 5.25, 0);
|
|
867
|
+
const shoulderRing = torus(tgt, 0.5, 0.05, MAT.vent, sx, 5.25, 0);
|
|
314
868
|
glowRings.push(shoulderRing);
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
box(
|
|
319
|
-
box(
|
|
320
|
-
//
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
869
|
+
// Rotational servo drum (visible)
|
|
870
|
+
servoDrum(tgt, 0.15, 0.2, sx + side * 0.08, 5.25, -0.15);
|
|
871
|
+
// Inner structural mount
|
|
872
|
+
box(tgt, 0.72, 0.42, 0.72, MAT.frameMat, sx + side * 0.2, 5.38, 0, 0, 0, side * -0.1);
|
|
873
|
+
box(tgt, 0.5, 0.3, 0.5, MAT.servo, sx + side * 0.2, 5.38, 0, 0, 0, side * -0.1);
|
|
874
|
+
// Pauldron — red with chrome layers
|
|
875
|
+
box(tgt, 1.18, 0.62, 1.02, MAT.armorRed, sx + side * 0.35, 5.46, 0, 0, 0, side * -0.12);
|
|
876
|
+
box(tgt, 1.02, 0.2, 0.88, MAT.chrome, sx + side * 0.4, 5.74, 0, 0, 0, side * -0.12);
|
|
877
|
+
box(tgt, 0.72, 0.07, 0.72, MAT.darkChrome, sx + side * 0.35, 5.63, 0, 0, 0, side * -0.12);
|
|
878
|
+
// Top cap with bolts
|
|
879
|
+
box(tgt, 0.88, 0.12, 0.78, MAT.chrome, sx + side * 0.38, 5.8, 0, 0, 0, side * -0.12);
|
|
880
|
+
boltCluster(tgt, 4, 0.18, sx + side * 0.38, 5.82, 0);
|
|
881
|
+
// Hydraulic actuator under pauldron
|
|
882
|
+
hydraulicRam(tgt, 0.03, 0.5, sx * 0.65, 5.32, -0.35, 0, 0, side * 0.55);
|
|
883
|
+
// Vent grilles
|
|
884
|
+
ventSlits(tgt, 0.48, 3, 0.07, MAT.vent, sx + side * 0.42, 5.26, 0);
|
|
885
|
+
// Cable harness from shoulder to arm
|
|
886
|
+
cableBundle(tgt, 3, 0.012, 0.5, sx + side * 0.1, 5.0, 0.25);
|
|
887
|
+
rivet(tgt, sx + side * 0.15, 5.67, 0.44); rivet(tgt, sx + side * 0.62, 5.67, -0.38);
|
|
888
|
+
rivet(tgt, sx + side * 0.35, 5.44, 0.5); rivet(tgt, sx + side * 0.35, 5.44, -0.5);
|
|
889
|
+
}
|
|
890
|
+
buildShoulder(la, -1);
|
|
891
|
+
buildShoulder(ra, 1);
|
|
892
|
+
|
|
893
|
+
// ══════════════════════════════════════════════════════════
|
|
894
|
+
// ARMS — layered armor, exposed actuators, articulated hands
|
|
895
|
+
// ══════════════════════════════════════════════════════════
|
|
896
|
+
function buildArm(tgt, side) {
|
|
897
|
+
const sx = side * 2.2;
|
|
898
|
+
|
|
899
|
+
// ── UPPER ARM ──
|
|
900
|
+
// Inner skeleton
|
|
901
|
+
box(tgt, 0.3, 1.35, 0.3, MAT.undersuit, sx, 4.2, 0);
|
|
902
|
+
cyl(tgt, 0.1, 0.1, 1.3, MAT.frameMat, sx, 4.2, 0, 8);
|
|
903
|
+
// Outer armor shell (front + back plates)
|
|
904
|
+
box(tgt, 0.64, 1.48, 0.58, MAT.armor, sx, 4.2, 0);
|
|
905
|
+
// Chrome accent band at top
|
|
906
|
+
box(tgt, 0.52, 0.54, 0.44, MAT.darkChrome, sx, 4.72, 0.2);
|
|
907
|
+
box(tgt, 0.48, 0.08, 0.42, MAT.panelLine, sx, 4.56, 0.28);
|
|
908
|
+
// Inner armor plate (visible in gap)
|
|
909
|
+
box(tgt, 0.3, 0.8, 0.2, MAT.servo, sx - side * 0.15, 4.2, -0.2);
|
|
910
|
+
// Bicep piston + hydraulic ram
|
|
911
|
+
pistonGeo(tgt, 0.036, 0.72, MAT.pistonMat, sx + side * 0.26, 4.2, -0.24);
|
|
912
|
+
hydraulicRam(tgt, 0.03, 0.65, sx - side * 0.22, 4.32, -0.2);
|
|
913
|
+
// Cable harness (inner channel)
|
|
914
|
+
cableBundle(tgt, 5, 0.009, 1.3, sx - side * 0.18, 4.2, 0.24);
|
|
915
|
+
// Bicep servo
|
|
916
|
+
servoDrum(tgt, 0.07, 0.12, sx + side * 0.2, 4.62, -0.08);
|
|
917
|
+
// Panel lines + rivets
|
|
918
|
+
box(tgt, 0.04, 1.2, 0.04, MAT.panelLine, sx + side * 0.3, 4.2, 0.26);
|
|
919
|
+
rivet(tgt, sx + side * 0.28, 4.8, 0.28); rivet(tgt, sx + side * 0.28, 3.65, 0.28);
|
|
920
|
+
rivet(tgt, sx - side * 0.28, 4.5, 0.28); rivet(tgt, sx - side * 0.28, 3.9, 0.28);
|
|
921
|
+
|
|
922
|
+
// ── ELBOW (multi-component joint) ──
|
|
923
|
+
// Ball joint core
|
|
924
|
+
sphere(tgt, 0.36, MAT.joint, sx, 3.4, 0);
|
|
925
|
+
rubberSeal(tgt, 0.34, 0.022, sx, 3.4, 0);
|
|
926
|
+
// Two servos (flexion + rotation)
|
|
927
|
+
servoDrum(tgt, 0.11, 0.16, sx, 3.4, 0.2);
|
|
928
|
+
servoDrum(tgt, 0.08, 0.1, sx + side * 0.18, 3.4, -0.1);
|
|
929
|
+
const elbowRing = torus(tgt, 0.4, 0.045, MAT.vent, sx, 3.4, 0);
|
|
343
930
|
glowRings.push(elbowRing);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
box(
|
|
350
|
-
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
box(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
931
|
+
// Triple actuator assembly
|
|
932
|
+
pistonGeo(tgt, 0.03, 0.44, MAT.pistonMat, sx + side * 0.24, 3.4, -0.22, 0.3, 0, 0);
|
|
933
|
+
pistonGeo(tgt, 0.028, 0.38, MAT.pistonMat, sx - side * 0.2, 3.4, -0.2, -0.25, 0, 0);
|
|
934
|
+
hydraulicRam(tgt, 0.022, 0.35, sx, 3.4, -0.28, 0, 0, side * 0.2);
|
|
935
|
+
// Elbow guard plate
|
|
936
|
+
box(tgt, 0.45, 0.22, 0.15, MAT.darkChrome, sx - side * 0.05, 3.4, -0.22);
|
|
937
|
+
|
|
938
|
+
// ── FOREARM (chrome gauntlet with layered plating) ──
|
|
939
|
+
// Inner skeleton
|
|
940
|
+
box(tgt, 0.32, 1.34, 0.32, MAT.undersuit, sx, 2.4, 0);
|
|
941
|
+
cyl(tgt, 0.08, 0.08, 1.2, MAT.frameMat, sx, 2.4, 0, 8);
|
|
942
|
+
// Main chrome shell
|
|
943
|
+
box(tgt, 0.68, 1.44, 0.64, MAT.chrome, sx, 2.4, 0);
|
|
944
|
+
// Red accent band (upper forearm)
|
|
945
|
+
box(tgt, 0.74, 0.38, 0.68, MAT.armorRed, sx, 2.88, 0);
|
|
946
|
+
box(tgt, 0.56, 0.12, 0.5, MAT.darkChrome, sx, 3.02, 0.1);
|
|
947
|
+
// Outer ridge (running down arm)
|
|
948
|
+
box(tgt, 0.18, 1.06, 0.18, MAT.darkChrome, sx + side * 0.35, 2.4, 0);
|
|
949
|
+
box(tgt, 0.08, 0.96, 0.08, MAT.panelLine, sx + side * 0.33, 2.4, 0.32);
|
|
950
|
+
// Inner conduit channel (exposed cables)
|
|
951
|
+
box(tgt, 0.12, 0.9, 0.08, MAT.servo, sx - side * 0.28, 2.4, -0.24);
|
|
952
|
+
cableBundle(tgt, 4, 0.008, 1.0, sx - side * 0.26, 2.4, -0.24);
|
|
953
|
+
// Cooling vents (lateral)
|
|
954
|
+
ventSlits(tgt, 0.28, 4, 0.065, MAT.vent, sx + side * 0.36, 2.15, 0.18);
|
|
955
|
+
// Forearm hydraulic ram
|
|
956
|
+
hydraulicRam(tgt, 0.022, 0.8, sx - side * 0.1, 2.4, -0.28);
|
|
957
|
+
// Panel rivets
|
|
958
|
+
rivet(tgt, sx - side * 0.26, 2.88, 0.34); rivet(tgt, sx + side * 0.26, 2.06, 0.32);
|
|
959
|
+
rivet(tgt, sx, 2.68, 0.35); rivet(tgt, sx, 2.18, 0.35);
|
|
960
|
+
rivet(tgt, sx + side * 0.34, 2.68, 0); rivet(tgt, sx + side * 0.34, 2.12, 0);
|
|
961
|
+
|
|
962
|
+
// ── WRIST (rotation assembly) ──
|
|
963
|
+
torus(tgt, 0.32, 0.05, MAT.vent, sx, 1.78, 0);
|
|
964
|
+
rubberSeal(tgt, 0.3, 0.02, sx, 1.74, 0);
|
|
965
|
+
rubberSeal(tgt, 0.3, 0.02, sx, 1.82, 0);
|
|
966
|
+
servoDrum(tgt, 0.09, 0.14, sx, 1.78, 0.14);
|
|
967
|
+
box(tgt, 0.46, 0.14, 0.46, MAT.darkChrome, sx, 1.78, 0);
|
|
968
|
+
boltCluster(tgt, 6, 0.18, sx, 1.78, 0);
|
|
969
|
+
|
|
970
|
+
// ══════════════════════════════════════════════════════════
|
|
971
|
+
// HAND — full mechanical assembly with armored fingers
|
|
972
|
+
// ══════════════════════════════════════════════════════════
|
|
973
|
+
// Palm chassis (layered)
|
|
974
|
+
box(tgt, 0.42, 0.18, 0.42, MAT.chrome, sx, 1.6, 0);
|
|
975
|
+
box(tgt, 0.36, 0.14, 0.36, MAT.joint, sx, 1.58, 0);
|
|
976
|
+
box(tgt, 0.3, 0.04, 0.3, MAT.servo, sx, 1.67, 0);
|
|
977
|
+
box(tgt, 0.3, 0.04, 0.3, MAT.frameMat, sx, 1.49, 0);
|
|
978
|
+
// Palm armor plate (top)
|
|
979
|
+
box(tgt, 0.38, 0.03, 0.34, MAT.darkChrome, sx, 1.685, 0);
|
|
980
|
+
// Grip pads on underside (3 rubber strips)
|
|
981
|
+
for (let p = -1; p <= 1; p++) {
|
|
982
|
+
box(tgt, 0.08, 0.02, 0.28, MAT.rubber, sx + p * 0.11, 1.49, 0);
|
|
983
|
+
}
|
|
984
|
+
// Tendon servo bank (5 micro servos across knuckle line)
|
|
985
|
+
for (let f = 0; f < 5; f++) {
|
|
986
|
+
const fz = -0.14 + f * 0.07;
|
|
987
|
+
servoDrum(tgt, 0.018, 0.05, sx, 1.66, fz);
|
|
358
988
|
}
|
|
359
|
-
|
|
360
|
-
|
|
989
|
+
// Hydraulic manifold (distributes pressure to fingers)
|
|
990
|
+
box(tgt, 0.06, 0.08, 0.3, MAT.hydraulic, sx - side * 0.14, 1.58, 0);
|
|
991
|
+
cableBundle(tgt, 3, 0.005, 0.2, sx - side * 0.12, 1.58, 0.14);
|
|
992
|
+
// Knuckle guard plate (chrome bar across all 4 fingers)
|
|
993
|
+
box(tgt, 0.06, 0.08, 0.32, MAT.chrome, sx, 1.46, 0.02);
|
|
994
|
+
boltCluster(tgt, 4, 0.12, sx, 1.46, 0.04);
|
|
995
|
+
|
|
996
|
+
// 4 fingers: knuckle + 3 armored phalanges + sensor tip
|
|
997
|
+
const fingerSpread = [-0.13, -0.05, 0.03, 0.11];
|
|
998
|
+
const fingerLen = [1.0, 1.1, 1.05, 0.9];
|
|
999
|
+
for (let f = 0; f < 4; f++) {
|
|
1000
|
+
const fz = fingerSpread[f];
|
|
1001
|
+
const scale = fingerLen[f];
|
|
1002
|
+
const fy = 1.44;
|
|
1003
|
+
|
|
1004
|
+
// Knuckle ball joint + rubber gasket
|
|
1005
|
+
sphere(tgt, 0.032, MAT.joint, sx, fy, fz);
|
|
1006
|
+
torus(tgt, 0.028, 0.006, MAT.rubber, sx, fy, fz);
|
|
1007
|
+
|
|
1008
|
+
// Proximal phalanx (armored)
|
|
1009
|
+
const p1y = fy - 0.08 * scale;
|
|
1010
|
+
box(tgt, 0.052, 0.14 * scale, 0.055, MAT.chrome, sx, p1y, fz);
|
|
1011
|
+
box(tgt, 0.04, 0.12 * scale, 0.042, MAT.servo, sx, p1y, fz);
|
|
1012
|
+
// Piston actuator along proximal
|
|
1013
|
+
pistonGeo(tgt, 0.006, 0.1 * scale, MAT.pistonMat, sx - side * 0.022, p1y, fz);
|
|
1014
|
+
|
|
1015
|
+
// PIP joint (mid knuckle)
|
|
1016
|
+
const pipy = fy - 0.17 * scale;
|
|
1017
|
+
sphere(tgt, 0.022, MAT.joint, sx, pipy, fz);
|
|
1018
|
+
|
|
1019
|
+
// Middle phalanx (armored)
|
|
1020
|
+
const p2y = pipy - 0.06 * scale;
|
|
1021
|
+
box(tgt, 0.046, 0.1 * scale, 0.05, MAT.frameMat, sx, p2y, fz);
|
|
1022
|
+
box(tgt, 0.038, 0.08 * scale, 0.04, MAT.darkChrome, sx, p2y, fz);
|
|
1023
|
+
|
|
1024
|
+
// DIP joint
|
|
1025
|
+
const dipy = pipy - 0.12 * scale;
|
|
1026
|
+
sphere(tgt, 0.018, MAT.joint, sx, dipy, fz);
|
|
1027
|
+
|
|
1028
|
+
// Distal phalanx (fingertip armor)
|
|
1029
|
+
const p3y = dipy - 0.045 * scale;
|
|
1030
|
+
box(tgt, 0.042, 0.075 * scale, 0.046, MAT.chrome, sx, p3y, fz);
|
|
1031
|
+
|
|
1032
|
+
// Fingertip: rubber grip pad + pressure sensor
|
|
1033
|
+
const tipy = dipy - 0.08 * scale;
|
|
1034
|
+
sphere(tgt, 0.02, MAT.rubber, sx, tipy, fz);
|
|
1035
|
+
sphere(tgt, 0.008, MAT.lens, sx, tipy - 0.01, fz);
|
|
1036
|
+
|
|
1037
|
+
// Tendon cable (runs full finger length)
|
|
1038
|
+
cableRun(tgt, 0.004, 0.35 * scale, sx - side * 0.018, p1y, fz);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Thumb — 3-segment, opposable (angled outward and rotated)
|
|
1042
|
+
const tx = sx + side * 0.2, tz = -0.18;
|
|
1043
|
+
// Carpometacarpal joint (base — swivel)
|
|
1044
|
+
sphere(tgt, 0.035, MAT.joint, tx, 1.5, tz);
|
|
1045
|
+
torus(tgt, 0.03, 0.007, MAT.rubber, tx, 1.5, tz);
|
|
1046
|
+
servoDrum(tgt, 0.015, 0.04, tx, 1.5, tz - 0.03);
|
|
1047
|
+
// Metacarpal (thick base segment)
|
|
1048
|
+
box(tgt, 0.058, 0.1, 0.062, MAT.chrome, tx + side * 0.02, 1.42, tz);
|
|
1049
|
+
box(tgt, 0.046, 0.08, 0.05, MAT.servo, tx + side * 0.02, 1.42, tz);
|
|
1050
|
+
pistonGeo(tgt, 0.006, 0.08, MAT.pistonMat, tx + side * 0.03, 1.42, tz + 0.025);
|
|
1051
|
+
// MCP joint
|
|
1052
|
+
sphere(tgt, 0.025, MAT.joint, tx + side * 0.03, 1.36, tz);
|
|
1053
|
+
// Proximal phalanx
|
|
1054
|
+
box(tgt, 0.052, 0.09, 0.056, MAT.frameMat, tx + side * 0.04, 1.3, tz);
|
|
1055
|
+
box(tgt, 0.042, 0.07, 0.044, MAT.darkChrome, tx + side * 0.04, 1.3, tz);
|
|
1056
|
+
// IP joint
|
|
1057
|
+
sphere(tgt, 0.02, MAT.joint, tx + side * 0.04, 1.25, tz);
|
|
1058
|
+
// Distal phalanx
|
|
1059
|
+
box(tgt, 0.048, 0.07, 0.052, MAT.chrome, tx + side * 0.05, 1.2, tz);
|
|
1060
|
+
// Thumb tip
|
|
1061
|
+
sphere(tgt, 0.022, MAT.rubber, tx + side * 0.05, 1.165, tz);
|
|
1062
|
+
sphere(tgt, 0.009, MAT.lens, tx + side * 0.05, 1.155, tz);
|
|
1063
|
+
cableRun(tgt, 0.005, 0.28, tx, 1.38, tz);
|
|
1064
|
+
|
|
1065
|
+
// Wrist-to-palm data connector
|
|
1066
|
+
cyl(tgt, 0.03, 0.03, 0.04, MAT.frameMat, sx - side * 0.16, 1.7, -0.12, 8);
|
|
1067
|
+
sphere(tgt, 0.012, MAT.vent, sx - side * 0.16, 1.72, -0.12);
|
|
361
1068
|
}
|
|
362
|
-
buildArm(-1);
|
|
363
|
-
buildArm(1);
|
|
1069
|
+
buildArm(la, -1);
|
|
1070
|
+
buildArm(ra, 1);
|
|
364
1071
|
|
|
365
|
-
//
|
|
1072
|
+
// ══════════════════════════════════════════════════════════
|
|
1073
|
+
// LEGS — layered armor, shock absorber knee, articulated feet
|
|
1074
|
+
// ══════════════════════════════════════════════════════════
|
|
366
1075
|
function buildLeg(side) {
|
|
367
|
-
const sx = side * 0.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
1076
|
+
const sx = side * 0.6;
|
|
1077
|
+
|
|
1078
|
+
// ── HIP JOINT (multi-axis) ──
|
|
1079
|
+
sphere(g, 0.38, MAT.joint, sx, 1.95, 0);
|
|
1080
|
+
rubberSeal(g, 0.36, 0.022, sx, 1.95, 0);
|
|
1081
|
+
servoDrum(g, 0.12, 0.16, sx + side * 0.1, 1.95, -0.14);
|
|
1082
|
+
servoDrum(g, 0.08, 0.1, sx - side * 0.06, 1.95, 0.14);
|
|
1083
|
+
const hipRing = torus(g, 0.44, 0.05, MAT.vent, sx, 1.95, 0);
|
|
371
1084
|
glowRings.push(hipRing);
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
box(g, 0.
|
|
375
|
-
|
|
376
|
-
box(g, 0.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
box(g, 0.
|
|
380
|
-
|
|
381
|
-
//
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
1085
|
+
|
|
1086
|
+
// ── HIP SKIRT ARMOR ──
|
|
1087
|
+
box(g, 0.64, 0.44, 0.64, MAT.armor, sx + side * 0.12, 1.82, 0.12, 0, 0, side * -0.1);
|
|
1088
|
+
box(g, 0.48, 0.14, 0.48, MAT.darkChrome, sx + side * 0.12, 1.94, 0.15, 0, 0, side * -0.1);
|
|
1089
|
+
box(g, 0.38, 0.08, 0.38, MAT.servo, sx + side * 0.12, 1.72, 0.12, 0, 0, side * -0.1);
|
|
1090
|
+
boltCluster(g, 5, 0.16, sx + side * 0.12, 1.82, 0.4);
|
|
1091
|
+
// Hip panel line
|
|
1092
|
+
box(g, 0.03, 0.4, 0.04, MAT.panelLine, sx + side * 0.35, 1.82, 0.35);
|
|
1093
|
+
|
|
1094
|
+
// ── THIGH (blue plates, layered armor, exposed hydraulics) ──
|
|
1095
|
+
// Inner skeleton + undersuit
|
|
1096
|
+
box(g, 0.4, 1.55, 0.4, MAT.undersuit, sx, 0.8, 0);
|
|
1097
|
+
cyl(g, 0.1, 0.1, 1.45, MAT.frameMat, sx, 0.8, 0, 8);
|
|
1098
|
+
// Outer armor (front + back + sides)
|
|
1099
|
+
box(g, 0.74, 1.68, 0.74, MAT.armor, sx, 0.8, 0);
|
|
1100
|
+
// Chrome accent plates (top & bottom of thigh)
|
|
1101
|
+
box(g, 0.58, 0.16, 0.54, MAT.chrome, sx, 1.38, 0.38);
|
|
1102
|
+
box(g, 0.54, 0.16, 0.5, MAT.darkChrome, sx, 0.48, 0.38);
|
|
1103
|
+
// Lateral panel lines
|
|
1104
|
+
box(g, 0.07, 1.14, 0.07, MAT.panelLine, sx + side * 0.36, 0.8, 0.38);
|
|
1105
|
+
box(g, 0.07, 1.14, 0.07, MAT.panelLine, sx - side * 0.36, 0.8, 0.38);
|
|
1106
|
+
// Thigh hydraulic ram (front, through armor gap)
|
|
1107
|
+
hydraulicRam(g, 0.032, 1.25, sx, 0.8, 0.4);
|
|
1108
|
+
// Secondary inner piston
|
|
1109
|
+
pistonGeo(g, 0.025, 0.8, MAT.pistonMat, sx + side * 0.15, 0.8, -0.28);
|
|
1110
|
+
// Rear cable conduit
|
|
1111
|
+
cableBundle(g, 4, 0.01, 1.4, sx - side * 0.3, 0.8, -0.28);
|
|
1112
|
+
// Inner mech visible in side gap
|
|
1113
|
+
box(g, 0.06, 0.6, 0.2, MAT.servo, sx + side * 0.32, 0.9, -0.12);
|
|
1114
|
+
// Rivets (8 total per thigh)
|
|
1115
|
+
rivet(g, sx + side * 0.32, 1.54, 0.37); rivet(g, sx + side * 0.32, 0.06, 0.37);
|
|
1116
|
+
rivet(g, sx - side * 0.32, 1.22, 0.37); rivet(g, sx - side * 0.32, 0.38, 0.37);
|
|
1117
|
+
rivet(g, sx + side * 0.32, 1.0, -0.37); rivet(g, sx + side * 0.32, 0.6, -0.37);
|
|
1118
|
+
rivet(g, sx, 1.48, 0.37); rivet(g, sx, 0.12, 0.37);
|
|
1119
|
+
|
|
1120
|
+
// ── KNEE (shock absorber + multi-actuator + guard) ──
|
|
1121
|
+
// Ball joint
|
|
1122
|
+
sphere(g, 0.34, MAT.joint, sx, -0.1, 0.1);
|
|
1123
|
+
rubberSeal(g, 0.32, 0.022, sx, -0.1, 0.1);
|
|
1124
|
+
const kneeRing = torus(g, 0.38, 0.05, MAT.vent, sx, -0.1, 0.1);
|
|
387
1125
|
glowRings.push(kneeRing);
|
|
388
|
-
|
|
389
|
-
pistonGeo(g, 0.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
//
|
|
400
|
-
cyl(g, 0.
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
box(g, 0.
|
|
405
|
-
box(g, 0.
|
|
406
|
-
box(g, 0.
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
1126
|
+
// Triple actuator assembly
|
|
1127
|
+
pistonGeo(g, 0.034, 0.44, MAT.pistonMat, sx + side * 0.28, -0.1, -0.22, 0.25, 0, 0);
|
|
1128
|
+
pistonGeo(g, 0.034, 0.38, MAT.pistonMat, sx - side * 0.28, -0.1, 0.26, -0.2, 0, 0);
|
|
1129
|
+
hydraulicRam(g, 0.025, 0.35, sx, -0.1, -0.3, 0.15, 0, 0);
|
|
1130
|
+
// Shock absorber (dual-tube behind knee)
|
|
1131
|
+
cyl(g, 0.055, 0.055, 0.55, MAT.pistonMat, sx + side * 0.08, -0.1, -0.28, 12);
|
|
1132
|
+
cyl(g, 0.04, 0.04, 0.55, MAT.chrome, sx - side * 0.08, -0.1, -0.28, 12);
|
|
1133
|
+
cyl(g, 0.07, 0.07, 0.22, MAT.hydraulic, sx + side * 0.08, -0.22, -0.28, 12);
|
|
1134
|
+
cyl(g, 0.055, 0.055, 0.18, MAT.hydraulic, sx - side * 0.08, -0.22, -0.28, 12);
|
|
1135
|
+
rubberSeal(g, 0.065, 0.014, sx + side * 0.08, 0.06, -0.28);
|
|
1136
|
+
rubberSeal(g, 0.05, 0.012, sx - side * 0.08, 0.06, -0.28);
|
|
1137
|
+
// Caliper brake disc (front of knee)
|
|
1138
|
+
cyl(g, 0.18, 0.18, 0.03, MAT.darkChrome, sx, -0.1, 0.26, 24);
|
|
1139
|
+
torus(g, 0.14, 0.012, MAT.chrome, sx, -0.1, 0.26);
|
|
1140
|
+
// Knee guard (layered: chrome + red + dark)
|
|
1141
|
+
box(g, 0.76, 0.58, 0.26, MAT.chrome, sx, -0.1, 0.42);
|
|
1142
|
+
box(g, 0.62, 0.4, 0.14, MAT.armorRed, sx, -0.08, 0.52);
|
|
1143
|
+
box(g, 0.52, 0.1, 0.08, MAT.darkChrome, sx, -0.3, 0.48);
|
|
1144
|
+
box(g, 0.52, 0.1, 0.08, MAT.darkChrome, sx, 0.1, 0.48);
|
|
1145
|
+
boltCluster(g, 6, 0.12, sx, -0.1, 0.54);
|
|
1146
|
+
// Knee panel line
|
|
1147
|
+
box(g, 0.04, 0.5, 0.04, MAT.panelLine, sx + side * 0.36, -0.1, 0.38);
|
|
1148
|
+
|
|
1149
|
+
// ── SHIN (blue armor, exposed cable runs, cooling vents) ──
|
|
1150
|
+
// Inner skeleton
|
|
1151
|
+
box(g, 0.38, 1.48, 0.38, MAT.undersuit, sx, -1.2, 0.08);
|
|
1152
|
+
cyl(g, 0.09, 0.09, 1.4, MAT.frameMat, sx, -1.2, 0.08, 8);
|
|
1153
|
+
// Outer armor
|
|
1154
|
+
box(g, 0.64, 1.58, 0.64, MAT.armor, sx, -1.2, 0.08);
|
|
1155
|
+
// Chrome shin guard (front plate)
|
|
1156
|
+
box(g, 0.69, 0.68, 0.2, MAT.chrome, sx, -0.58, 0.42);
|
|
1157
|
+
box(g, 0.59, 0.42, 0.14, MAT.darkChrome, sx, -0.84, 0.44);
|
|
1158
|
+
// Panel line down shin
|
|
1159
|
+
box(g, 0.07, 1.04, 0.07, MAT.panelLine, sx, -1.2, 0.44);
|
|
1160
|
+
box(g, 0.07, 1.04, 0.07, MAT.panelLine, sx + side * 0.3, -1.2, 0.28);
|
|
1161
|
+
// Shin coolant lines (rear)
|
|
1162
|
+
cableBundle(g, 3, 0.009, 1.1, sx + side * 0.28, -1.2, -0.24);
|
|
1163
|
+
cableBundle(g, 2, 0.007, 0.8, sx - side * 0.2, -1.2, -0.22);
|
|
1164
|
+
// Cooling vents (lateral)
|
|
1165
|
+
ventSlits(g, 0.26, 5, 0.065, MAT.vent, sx - side * 0.34, -1.35, 0);
|
|
1166
|
+
// Inner mech visible
|
|
1167
|
+
box(g, 0.04, 0.5, 0.12, MAT.servo, sx + side * 0.28, -1.0, -0.08);
|
|
1168
|
+
// Shin rivets
|
|
1169
|
+
rivet(g, sx + side * 0.28, -0.55, 0.38); rivet(g, sx + side * 0.28, -1.85, 0.38);
|
|
1170
|
+
rivet(g, sx - side * 0.28, -0.75, 0.38); rivet(g, sx - side * 0.28, -1.65, 0.38);
|
|
1171
|
+
rivet(g, sx, -0.55, -0.32); rivet(g, sx, -1.85, -0.32);
|
|
1172
|
+
|
|
1173
|
+
// ── ANKLE (servo + dual axis) ──
|
|
1174
|
+
cyl(g, 0.22, 0.26, 0.36, MAT.joint, sx, -2.1, 0.08);
|
|
1175
|
+
rubberSeal(g, 0.24, 0.016, sx, -1.96, 0.08);
|
|
1176
|
+
rubberSeal(g, 0.24, 0.016, sx, -2.24, 0.08);
|
|
1177
|
+
servoDrum(g, 0.075, 0.12, sx + side * 0.12, -2.1, 0.22);
|
|
1178
|
+
servoDrum(g, 0.06, 0.08, sx - side * 0.1, -2.1, -0.06);
|
|
1179
|
+
// Ankle actuators
|
|
1180
|
+
pistonGeo(g, 0.028, 0.32, MAT.pistonMat, sx + side * 0.16, -2.0, 0.28);
|
|
1181
|
+
pistonGeo(g, 0.028, 0.32, MAT.pistonMat, sx - side * 0.16, -2.0, -0.14);
|
|
1182
|
+
hydraulicRam(g, 0.02, 0.25, sx, -2.04, -0.16);
|
|
1183
|
+
// Ankle guard
|
|
1184
|
+
box(g, 0.35, 0.18, 0.3, MAT.darkChrome, sx, -2.02, 0.26);
|
|
1185
|
+
|
|
1186
|
+
// ══════════════════════════════════════════════════════════
|
|
1187
|
+
// FOOT — armored multi-segment with arch, tread, and sensors
|
|
1188
|
+
// ══════════════════════════════════════════════════════════
|
|
1189
|
+
|
|
1190
|
+
// ── TOE SECTION (4 armored toe segments) ──
|
|
1191
|
+
// Toe plate (main armored shell)
|
|
1192
|
+
box(g, 0.76, 0.18, 0.28, MAT.chrome, sx, -2.38, 0.62);
|
|
1193
|
+
box(g, 0.62, 0.05, 0.22, MAT.darkChrome, sx, -2.3, 0.64);
|
|
1194
|
+
// Individual toe caps (4 articulated digits)
|
|
1195
|
+
for (let t = 0; t < 4; t++) {
|
|
1196
|
+
const tz = 0.66 + (t === 0 || t === 3 ? 0 : 0.04);
|
|
1197
|
+
const tx = sx + (-0.24 + t * 0.16);
|
|
1198
|
+
// MTP joint
|
|
1199
|
+
sphere(g, 0.024, MAT.joint, tx, -2.38, tz - 0.04);
|
|
1200
|
+
// Proximal toe segment (armored)
|
|
1201
|
+
box(g, 0.1, 0.1, 0.1, MAT.chrome, tx, -2.42, tz);
|
|
1202
|
+
box(g, 0.07, 0.06, 0.07, MAT.servo, tx, -2.42, tz);
|
|
1203
|
+
// IP joint
|
|
1204
|
+
sphere(g, 0.016, MAT.joint, tx, -2.42, tz + 0.06);
|
|
1205
|
+
// Distal toe (claw-like tip)
|
|
1206
|
+
box(g, 0.08, 0.08, 0.08, MAT.darkChrome, tx, -2.44, tz + 0.1);
|
|
1207
|
+
// Toe grip pad (rubber)
|
|
1208
|
+
box(g, 0.06, 0.02, 0.06, MAT.rubber, tx, -2.48, tz + 0.1);
|
|
1209
|
+
// Toe pressure sensor
|
|
1210
|
+
sphere(g, 0.008, MAT.lens, tx, -2.48, tz + 0.12);
|
|
1211
|
+
}
|
|
1212
|
+
// Toe guard plate (protective front chrome bar)
|
|
1213
|
+
box(g, 0.72, 0.1, 0.04, MAT.chrome, sx, -2.36, 0.8);
|
|
1214
|
+
box(g, 0.56, 0.06, 0.03, MAT.armorRed, sx, -2.36, 0.82);
|
|
1215
|
+
boltCluster(g, 4, 0.14, sx, -2.36, 0.82);
|
|
1216
|
+
|
|
1217
|
+
// ── METATARSAL BRIDGE (visible arch structure) ──
|
|
1218
|
+
// Metatarsal hinge axle (connects toe plate to midfoot)
|
|
1219
|
+
cyl(g, 0.05, 0.05, 0.65, MAT.joint, sx, -2.38, 0.5, 12);
|
|
1220
|
+
rubberSeal(g, 0.045, 0.012, sx, -2.38, 0.5);
|
|
1221
|
+
// Arch springs (visible coil springs under arch)
|
|
1222
|
+
for (let s = -1; s <= 1; s += 2) {
|
|
1223
|
+
cyl(g, 0.025, 0.025, 0.15, MAT.pistonMat, sx + s * 0.14, -2.42, 0.38, 8);
|
|
1224
|
+
torus(g, 0.022, 0.005, MAT.chrome, sx + s * 0.14, -2.38, 0.38);
|
|
1225
|
+
torus(g, 0.022, 0.005, MAT.chrome, sx + s * 0.14, -2.46, 0.38);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
// ── MIDFOOT PLATE (armored bridge with internal servo) ──
|
|
1229
|
+
box(g, 0.72, 0.16, 0.28, MAT.armor, sx, -2.38, 0.3);
|
|
1230
|
+
box(g, 0.6, 0.04, 0.22, MAT.servo, sx, -2.32, 0.3);
|
|
1231
|
+
box(g, 0.6, 0.04, 0.22, MAT.frameMat, sx, -2.44, 0.3);
|
|
1232
|
+
// Midfoot lateral stabilizer wings
|
|
1233
|
+
for (let s = -1; s <= 1; s += 2) {
|
|
1234
|
+
box(g, 0.06, 0.12, 0.2, MAT.darkChrome, sx + s * 0.38, -2.36, 0.32);
|
|
1235
|
+
rivet(g, sx + s * 0.38, -2.3, 0.38);
|
|
1236
|
+
}
|
|
1237
|
+
// Arch servo (flexion control)
|
|
1238
|
+
servoDrum(g, 0.03, 0.16, sx, -2.36, 0.42);
|
|
1239
|
+
// Panel lines
|
|
1240
|
+
box(g, 0.04, 0.14, 0.24, MAT.panelLine, sx + side * 0.28, -2.38, 0.3);
|
|
1241
|
+
|
|
1242
|
+
// ── HEEL SECTION (shock-absorbing, armored) ──
|
|
1243
|
+
box(g, 0.68, 0.18, 0.36, MAT.armor, sx, -2.38, 0.02);
|
|
1244
|
+
box(g, 0.56, 0.05, 0.28, MAT.chrome, sx, -2.3, 0.02);
|
|
1245
|
+
box(g, 0.56, 0.05, 0.28, MAT.darkChrome, sx, -2.46, 0.02);
|
|
1246
|
+
// Heel shock absorber (visible damper cylinder)
|
|
1247
|
+
cyl(g, 0.04, 0.04, 0.16, MAT.pistonMat, sx, -2.36, -0.1, 10);
|
|
1248
|
+
cyl(g, 0.055, 0.055, 0.08, MAT.hydraulic, sx, -2.42, -0.1, 10);
|
|
1249
|
+
rubberSeal(g, 0.05, 0.01, sx, -2.3, -0.1);
|
|
1250
|
+
// Lateral dampers
|
|
1251
|
+
for (let s = -1; s <= 1; s += 2) {
|
|
1252
|
+
cyl(g, 0.025, 0.025, 0.12, MAT.pistonMat, sx + s * 0.2, -2.38, -0.06, 8);
|
|
1253
|
+
rubberSeal(g, 0.022, 0.006, sx + s * 0.2, -2.32, -0.06);
|
|
1254
|
+
}
|
|
1255
|
+
// Heel stabilizer fins (twin vertical fins)
|
|
1256
|
+
for (let s = -1; s <= 1; s += 2) {
|
|
1257
|
+
box(g, 0.04, 0.22, 0.12, MAT.darkChrome, sx + s * 0.18, -2.32, -0.2);
|
|
1258
|
+
box(g, 0.02, 0.16, 0.08, MAT.panelLine, sx + s * 0.18, -2.34, -0.24);
|
|
1259
|
+
}
|
|
1260
|
+
// Central heel ridge
|
|
1261
|
+
box(g, 0.14, 0.04, 0.18, MAT.chrome, sx, -2.28, -0.12);
|
|
1262
|
+
rivet(g, sx, -2.28, -0.2);
|
|
1263
|
+
|
|
1264
|
+
// ── SOLE (tread pattern + thrusters + ground sensors) ──
|
|
1265
|
+
// Sole plate
|
|
1266
|
+
box(g, 0.7, 0.04, 0.8, MAT.darkChrome, sx, -2.5, 0.2);
|
|
1267
|
+
// Tread pads (rubber grip strips)
|
|
1268
|
+
for (let i = 0; i < 6; i++) {
|
|
1269
|
+
const pz = -0.08 + i * 0.14;
|
|
1270
|
+
box(g, 0.56, 0.02, 0.06, MAT.rubber, sx, -2.52, pz);
|
|
1271
|
+
}
|
|
1272
|
+
// Cross-hatched tread (lateral ribs)
|
|
1273
|
+
for (let s = -1; s <= 1; s += 2) {
|
|
1274
|
+
for (let i = 0; i < 3; i++) {
|
|
1275
|
+
box(g, 0.04, 0.02, 0.5, MAT.rubber, sx + s * (0.2 + i * 0.06), -2.52, 0.2);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
// Primary thruster (central)
|
|
1279
|
+
box(g, 0.3, 0.08, 0.3, MAT.ventHot, sx, -2.54, 0.22);
|
|
1280
|
+
torus(g, 0.1, 0.015, MAT.vent, sx, -2.54, 0.22);
|
|
1281
|
+
// Secondary micro-thrusters (heel + toe)
|
|
1282
|
+
cyl(g, 0.06, 0.06, 0.04, MAT.ventHot, sx, -2.54, -0.05, 8);
|
|
1283
|
+
cyl(g, 0.06, 0.06, 0.04, MAT.ventHot, sx, -2.54, 0.55, 8);
|
|
1284
|
+
// Ground proximity sensors (5-sensor array)
|
|
1285
|
+
sphere(g, 0.02, MAT.lens, sx, -2.52, 0.7);
|
|
1286
|
+
sphere(g, 0.02, MAT.lens, sx + side * 0.24, -2.52, 0.5);
|
|
1287
|
+
sphere(g, 0.018, MAT.lens, sx, -2.52, 0.3);
|
|
1288
|
+
sphere(g, 0.018, MAT.lens, sx - side * 0.24, -2.52, 0.1);
|
|
1289
|
+
sphere(g, 0.02, MAT.lens, sx, -2.52, -0.1);
|
|
1290
|
+
// Foot rivets
|
|
1291
|
+
rivet(g, sx + side * 0.34, -2.28, 0.64); rivet(g, sx - side * 0.34, -2.28, 0.64);
|
|
1292
|
+
rivet(g, sx + side * 0.3, -2.28, 0.02); rivet(g, sx - side * 0.3, -2.28, 0.02);
|
|
1293
|
+
rivet(g, sx + side * 0.3, -2.28, -0.14); rivet(g, sx - side * 0.3, -2.28, -0.14);
|
|
1294
|
+
rivet(g, sx, -2.28, 0.46);
|
|
410
1295
|
}
|
|
411
1296
|
buildLeg(-1);
|
|
412
1297
|
buildLeg(1);
|
|
413
1298
|
|
|
414
|
-
//
|
|
1299
|
+
// ══════════════════════════════════════════════════════════
|
|
1300
|
+
// SPINE — articulated vertebrae, coolant, data conduits, cross-bracing
|
|
1301
|
+
// ══════════════════════════════════════════════════════════
|
|
415
1302
|
spineSegments = [];
|
|
416
|
-
for (let i = 0; i <
|
|
1303
|
+
for (let i = 0; i < 10; i++) {
|
|
1304
|
+
const yOff = 2.5 + i * 0.35;
|
|
417
1305
|
const mat = MAT.spineGlow.clone();
|
|
418
|
-
|
|
1306
|
+
// Vertebra body (tapered — wider in lumbar, narrower in thoracic)
|
|
1307
|
+
const vw = 0.16 - i * 0.004;
|
|
1308
|
+
const seg = box(g, vw, 0.2, vw, mat, 0, yOff, -0.72);
|
|
419
1309
|
spineSegments.push(seg);
|
|
1310
|
+
// Spinous process (rear protrusion)
|
|
1311
|
+
box(g, 0.04, 0.14, 0.06, MAT.frameMat, 0, yOff, -0.78);
|
|
1312
|
+
// Inter-vertebral disc (rubber gasket + fluid gap)
|
|
420
1313
|
if (i > 0) {
|
|
421
|
-
box(g, 0.
|
|
1314
|
+
box(g, vw - 0.02, 0.03, vw - 0.02, MAT.rubber, 0, yOff - 0.175, -0.72);
|
|
1315
|
+
box(g, vw + 0.08, 0.03, 0.06, MAT.frameMat, 0, yOff - 0.175, -0.72);
|
|
1316
|
+
// Micro-servo for inter-vertebral articulation
|
|
1317
|
+
servoDrum(g, 0.015, 0.04, 0, yOff - 0.175, -0.68);
|
|
422
1318
|
}
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
box(g, 0.
|
|
1319
|
+
// Lateral transverse processes (structural wings + cable anchors)
|
|
1320
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1321
|
+
box(g, 0.14, 0.05, 0.04, MAT.frameMat, side * 0.14, yOff, -0.72);
|
|
1322
|
+
// Ligament cable anchor
|
|
1323
|
+
sphere(g, 0.008, MAT.joint, side * 0.2, yOff, -0.72);
|
|
1324
|
+
}
|
|
1325
|
+
// Cross-struts to torso (thoracic region)
|
|
1326
|
+
if (i >= 2 && i <= 7) {
|
|
1327
|
+
box(g, 0.04, 0.04, 0.24, MAT.frameMat, 0, yOff, -0.58);
|
|
1328
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1329
|
+
box(g, 0.02, 0.04, 0.2, MAT.frameMat, side * 0.07, yOff, -0.56, 0, side * 0.15, 0);
|
|
1330
|
+
// Diagonal tension cables
|
|
1331
|
+
cableRun(g, 0.004, 0.22, side * 0.08, yOff, -0.52, 0, side * 0.2, 0);
|
|
1332
|
+
}
|
|
426
1333
|
}
|
|
1334
|
+
// Rivet at each vertebra top
|
|
1335
|
+
if (i % 2 === 0) rivet(g, 0, yOff + 0.08, -0.74);
|
|
1336
|
+
}
|
|
1337
|
+
// Coolant lines (dual, running full spine length)
|
|
1338
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1339
|
+
cableRun(g, 0.016, 3.4, side * 0.13, 4.0, -0.78);
|
|
1340
|
+
cableRun(g, 0.008, 3.4, side * 0.2, 4.0, -0.76);
|
|
1341
|
+
}
|
|
1342
|
+
// Central data conduit (heavy gauge)
|
|
1343
|
+
cableRun(g, 0.022, 3.6, 0, 4.0, -0.84);
|
|
1344
|
+
// Spinal junction boxes (3 intermediate nodes)
|
|
1345
|
+
for (let j = 0; j < 3; j++) {
|
|
1346
|
+
const jy = 3.0 + j * 1.0;
|
|
1347
|
+
box(g, 0.08, 0.08, 0.06, MAT.darkChrome, 0, jy, -0.86);
|
|
1348
|
+
sphere(g, 0.012, MAT.vent, 0, jy, -0.9);
|
|
427
1349
|
}
|
|
428
1350
|
|
|
429
|
-
//
|
|
1351
|
+
// ══════════════════════════════════════════════════════════
|
|
1352
|
+
// JETPACK — full backpack assembly with engines, fuel, RCS, wings
|
|
1353
|
+
// ══════════════════════════════════════════════════════════
|
|
1354
|
+
|
|
1355
|
+
// ── CENTRAL BACKPACK FRAME ──
|
|
1356
|
+
// Main housing (bolted to back plate)
|
|
1357
|
+
box(g, 1.4, 2.2, 0.44, MAT.armor, 0, 4.0, -0.94);
|
|
1358
|
+
box(g, 1.26, 2.0, 0.36, MAT.darkChrome, 0, 4.0, -0.98);
|
|
1359
|
+
box(g, 1.1, 1.8, 0.1, MAT.servo, 0, 4.0, -1.02);
|
|
1360
|
+
// Mounting rail (horizontal bar across upper back)
|
|
1361
|
+
box(g, 1.6, 0.1, 0.2, MAT.chrome, 0, 5.0, -0.9);
|
|
1362
|
+
boltCluster(g, 8, 0.32, 0, 5.0, -0.84);
|
|
1363
|
+
// Lower mounting rail
|
|
1364
|
+
box(g, 1.2, 0.08, 0.16, MAT.chrome, 0, 3.0, -0.9);
|
|
1365
|
+
boltCluster(g, 6, 0.24, 0, 3.0, -0.84);
|
|
1366
|
+
// Vertical structural ribs
|
|
430
1367
|
for (let side = -1; side <= 1; side += 2) {
|
|
431
|
-
|
|
432
|
-
box(g, 0.
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
1368
|
+
box(g, 0.06, 2.0, 0.12, MAT.frameMat, side * 0.52, 4.0, -0.96);
|
|
1369
|
+
box(g, 0.03, 1.8, 0.06, MAT.weldSeam, side * 0.52, 4.0, -1.02);
|
|
1370
|
+
}
|
|
1371
|
+
// Backpack panel lines
|
|
1372
|
+
box(g, 0.04, 2.0, 0.06, MAT.panelLine, 0, 4.0, -1.14);
|
|
1373
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1374
|
+
box(g, 0.04, 1.6, 0.06, MAT.panelLine, side * 0.3, 4.0, -1.14);
|
|
1375
|
+
box(g, 0.6, 0.04, 0.06, MAT.panelLine, side * 0.3, 4.8, -1.14);
|
|
1376
|
+
box(g, 0.6, 0.04, 0.06, MAT.panelLine, side * 0.3, 3.2, -1.14);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// ── FUEL TANKS (cylindrical, flanking center) ──
|
|
1380
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1381
|
+
const ftx = side * 0.42;
|
|
1382
|
+
// Tank body
|
|
1383
|
+
cyl(g, 0.14, 0.14, 1.5, MAT.chrome, ftx, 3.9, -1.14, 16);
|
|
1384
|
+
// Tank end caps (hemispherical)
|
|
1385
|
+
sphere(g, 0.14, MAT.chrome, ftx, 4.65, -1.14);
|
|
1386
|
+
sphere(g, 0.14, MAT.chrome, ftx, 3.15, -1.14);
|
|
1387
|
+
// Tank bands (reinforcement straps)
|
|
1388
|
+
torus(g, 0.155, 0.015, MAT.darkChrome, ftx, 4.3, -1.14);
|
|
1389
|
+
torus(g, 0.155, 0.015, MAT.darkChrome, ftx, 3.9, -1.14);
|
|
1390
|
+
torus(g, 0.155, 0.015, MAT.darkChrome, ftx, 3.5, -1.14);
|
|
1391
|
+
// Fuel gauge (small window)
|
|
1392
|
+
box(g, 0.03, 0.2, 0.02, MAT.visor, ftx + side * 0.13, 3.9, -1.14);
|
|
1393
|
+
// Filler cap
|
|
1394
|
+
cyl(g, 0.04, 0.04, 0.03, MAT.darkChrome, ftx, 4.66, -1.14, 8);
|
|
1395
|
+
rivet(g, ftx, 4.68, -1.14);
|
|
1396
|
+
// Feed line from tank to engine
|
|
1397
|
+
cableRun(g, 0.016, 0.8, ftx, 3.5, -1.08);
|
|
1398
|
+
cableRun(g, 0.01, 0.6, ftx + side * 0.08, 3.6, -1.04);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// ── MAIN ENGINE PODS (twin turbines) ──
|
|
1402
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1403
|
+
const px = side * 0.72;
|
|
1404
|
+
// Engine pylon (connecting to backpack frame)
|
|
1405
|
+
box(g, 0.14, 0.55, 0.22, MAT.frameMat, px, 3.95, -0.98);
|
|
1406
|
+
box(g, 0.18, 0.1, 0.26, MAT.darkChrome, px, 4.18, -0.98);
|
|
1407
|
+
boltCluster(g, 4, 0.08, px, 4.18, -0.94);
|
|
1408
|
+
// Gimbal mount (thrust vectoring ball joint)
|
|
1409
|
+
sphere(g, 0.12, MAT.joint, px, 3.58, -1.06);
|
|
1410
|
+
rubberSeal(g, 0.11, 0.012, px, 3.58, -1.06);
|
|
1411
|
+
servoDrum(g, 0.04, 0.08, px, 3.58, -0.96);
|
|
1412
|
+
// Fuel manifold block
|
|
1413
|
+
box(g, 0.08, 0.12, 0.08, MAT.hydraulic, px - side * 0.14, 3.72, -1.0);
|
|
1414
|
+
cableBundle(g, 2, 0.008, 0.3, px - side * 0.1, 3.72, -0.94);
|
|
1415
|
+
|
|
1416
|
+
// Outer nacelle (3-layer cowling)
|
|
1417
|
+
cyl(g, 0.32, 0.42, 0.95, MAT.chrome, px, 3.78, -1.16, 18);
|
|
1418
|
+
cyl(g, 0.28, 0.36, 0.75, MAT.darkChrome, px, 3.7, -1.16, 18);
|
|
1419
|
+
cyl(g, 0.24, 0.3, 0.5, MAT.armor, px, 3.68, -1.16, 16);
|
|
1420
|
+
// Intake grille (top of nacelle)
|
|
1421
|
+
torus(g, 0.34, 0.028, MAT.chrome, px, 4.22, -1.16);
|
|
1422
|
+
torus(g, 0.3, 0.012, MAT.vent, px, 4.2, -1.16);
|
|
1423
|
+
// Intake vane blades (visible fan)
|
|
1424
|
+
for (let v = 0; v < 6; v++) {
|
|
1425
|
+
const va = (v / 6) * Math.PI * 2;
|
|
1426
|
+
const vx = Math.cos(va) * 0.18, vy = Math.sin(va) * 0.18;
|
|
1427
|
+
box(g, 0.04, 0.16, 0.015, MAT.chrome, px + vx, 4.18 + vy * 0.3, -1.16, va, 0, 0);
|
|
1428
|
+
}
|
|
1429
|
+
// Combustion chamber (inner glow tube)
|
|
1430
|
+
cyl(g, 0.2, 0.2, 0.55, MAT.servo, px, 3.65, -1.16, 14);
|
|
1431
|
+
cyl(g, 0.12, 0.12, 0.4, MAT.ventHot, px, 3.6, -1.16, 12);
|
|
1432
|
+
// Turbine exhaust stages
|
|
1433
|
+
cyl(g, 0.16, 0.26, 0.2, MAT.darkChrome, px, 3.42, -1.16, 16);
|
|
1434
|
+
cyl(g, 0.2, 0.3, 0.15, MAT.chrome, px, 3.35, -1.16, 16);
|
|
1435
|
+
// Nozzle (convergent-divergent)
|
|
1436
|
+
cyl(g, 0.15, 0.28, 0.35, MAT.darkChrome, px, 3.22, -1.16, 16);
|
|
1437
|
+
// Nozzle exit ring (polished)
|
|
1438
|
+
torus(g, 0.3, 0.025, MAT.chrome, px, 3.08, -1.16);
|
|
437
1439
|
// Exhaust glow
|
|
438
|
-
const exhaust = cyl(g, 0.
|
|
1440
|
+
const exhaust = cyl(g, 0.24, 0.34, 0.12, MAT.ventHot, px, 3.08, -1.16, 16);
|
|
439
1441
|
exhaustFlames.push(exhaust);
|
|
440
|
-
//
|
|
441
|
-
const thrustRing = torus(g, 0.
|
|
1442
|
+
// Nozzle glow rings (4 concentric)
|
|
1443
|
+
const thrustRing = torus(g, 0.36, 0.032, MAT.vent, px, 3.08, -1.16);
|
|
442
1444
|
glowRings.push(thrustRing);
|
|
443
|
-
const thrustRing2 = torus(g, 0.
|
|
1445
|
+
const thrustRing2 = torus(g, 0.3, 0.02, MAT.panelLine, px, 3.18, -1.16);
|
|
444
1446
|
glowRings.push(thrustRing2);
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
1447
|
+
torus(g, 0.22, 0.014, MAT.vent, px, 3.14, -1.16);
|
|
1448
|
+
torus(g, 0.16, 0.01, MAT.coreInner, px, 3.1, -1.16);
|
|
1449
|
+
|
|
1450
|
+
// Engine cowling panel lines + rivets
|
|
1451
|
+
box(g, 0.04, 0.8, 0.04, MAT.panelLine, px + side * 0.3, 3.78, -1.16);
|
|
1452
|
+
box(g, 0.04, 0.8, 0.04, MAT.panelLine, px, 3.78, -1.44);
|
|
1453
|
+
rivet(g, px + side * 0.26, 4.12, -1.06); rivet(g, px + side * 0.26, 3.44, -1.06);
|
|
1454
|
+
rivet(g, px - side * 0.14, 4.12, -1.3); rivet(g, px - side * 0.14, 3.44, -1.3);
|
|
1455
|
+
rivet(g, px + side * 0.3, 3.78, -1.06); rivet(g, px - side * 0.14, 3.78, -1.3);
|
|
1456
|
+
// Cooling vents on nacelle
|
|
1457
|
+
ventSlits(g, 0.2, 4, 0.055, MAT.vent, px + side * 0.34, 3.85, -1.16);
|
|
1458
|
+
coolingFins(g, 6, 0.18, 0.04, 0.035, px - side * 0.3, 3.5, -1.16, true);
|
|
1459
|
+
|
|
1460
|
+
// Heat shimmer plume (larger, more dramatic)
|
|
1461
|
+
const shimGeo = new THREE.ConeGeometry(0.32, 1.6, 12, 1, true);
|
|
1462
|
+
const shimMat = new P({
|
|
1463
|
+
color: 0xff6600, emissive: 0xff4400, emissiveIntensity: 0.4,
|
|
1464
|
+
transparent: true, opacity: 0.045, side: THREE.DoubleSide,
|
|
1465
|
+
depthWrite: false, blending: THREE.AdditiveBlending,
|
|
1466
|
+
});
|
|
1467
|
+
const shim = new THREE.Mesh(shimGeo, shimMat);
|
|
1468
|
+
shim.position.set(px, 2.2, -1.16);
|
|
1469
|
+
g.add(shim);
|
|
1470
|
+
heatShimmerMeshes.push(shim);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// ── WING PANELS (folded flight surfaces) ──
|
|
1474
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1475
|
+
const wx = side * 0.9;
|
|
1476
|
+
// Wing root hinge
|
|
1477
|
+
sphere(g, 0.06, MAT.joint, wx, 4.2, -1.04);
|
|
1478
|
+
servoDrum(g, 0.03, 0.08, wx, 4.2, -0.98);
|
|
1479
|
+
// Main wing panel (angled slightly outward)
|
|
1480
|
+
box(g, 0.7, 0.04, 0.35, MAT.armor, wx + side * 0.3, 4.2, -1.14, 0, 0, side * -0.15);
|
|
1481
|
+
box(g, 0.6, 0.02, 0.28, MAT.darkChrome, wx + side * 0.32, 4.2, -1.14, 0, 0, side * -0.15);
|
|
1482
|
+
// Aileron (trailing edge)
|
|
1483
|
+
box(g, 0.55, 0.025, 0.1, MAT.chrome, wx + side * 0.34, 4.2, -1.32, 0, 0, side * -0.15);
|
|
1484
|
+
// Wing tip light
|
|
1485
|
+
sphere(g, 0.02, side === -1 ? MAT.armorRed : MAT.coreInner, wx + side * 0.64, 4.2, -1.14);
|
|
1486
|
+
// Wing panel lines
|
|
1487
|
+
box(g, 0.3, 0.01, 0.03, MAT.panelLine, wx + side * 0.22, 4.22, -1.08, 0, 0, side * -0.15);
|
|
1488
|
+
box(g, 0.3, 0.01, 0.03, MAT.panelLine, wx + side * 0.22, 4.18, -1.22, 0, 0, side * -0.15);
|
|
1489
|
+
rivet(g, wx + side * 0.08, 4.22, -1.08); rivet(g, wx + side * 0.54, 4.22, -1.08);
|
|
448
1490
|
}
|
|
449
1491
|
|
|
450
|
-
// ──
|
|
1492
|
+
// ── RCS ATTITUDE THRUSTERS (6 micro-thrusters for maneuvering) ──
|
|
1493
|
+
// Upper pair (yaw control)
|
|
1494
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1495
|
+
cyl(g, 0.06, 0.08, 0.12, MAT.chrome, side * 0.62, 4.9, -1.08, 10);
|
|
1496
|
+
cyl(g, 0.04, 0.06, 0.06, MAT.darkChrome, side * 0.62, 4.84, -1.08, 10);
|
|
1497
|
+
cyl(g, 0.05, 0.07, 0.04, MAT.ventHot, side * 0.62, 4.82, -1.08, 10);
|
|
1498
|
+
torus(g, 0.065, 0.008, MAT.vent, side * 0.62, 4.82, -1.08);
|
|
1499
|
+
}
|
|
1500
|
+
// Lower pair (pitch control)
|
|
1501
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1502
|
+
cyl(g, 0.055, 0.07, 0.1, MAT.chrome, side * 0.58, 3.1, -1.08, 10);
|
|
1503
|
+
cyl(g, 0.035, 0.055, 0.05, MAT.darkChrome, side * 0.58, 3.06, -1.08, 10);
|
|
1504
|
+
cyl(g, 0.045, 0.06, 0.035, MAT.ventHot, side * 0.58, 3.04, -1.08, 10);
|
|
1505
|
+
torus(g, 0.06, 0.007, MAT.vent, side * 0.58, 3.04, -1.08);
|
|
1506
|
+
}
|
|
1507
|
+
// Dorsal pair (roll control — on wing roots)
|
|
451
1508
|
for (let side = -1; side <= 1; side += 2) {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
box(g, 0.12, 0.2, 0.12, MAT.frameMat, sx + side * 0.4, 5.5, -0.2);
|
|
455
|
-
cyl(g, 0.12, 0.12, 0.7, MAT.hardpoint, sx + side * 0.4, 5.6, -0.2);
|
|
456
|
-
box(g, 0.22, 0.22, 0.65, MAT.hardpoint, sx + side * 0.4, 5.95, -0.2);
|
|
457
|
-
box(g, 0.16, 0.08, 0.55, MAT.darkChrome, sx + side * 0.4, 6.05, -0.2);
|
|
458
|
-
box(g, 0.08, 0.06, 0.5, MAT.vent, sx + side * 0.4, 5.85, -0.2);
|
|
459
|
-
rivet(g, sx + side * 0.4, 5.95, 0.1);
|
|
1509
|
+
cyl(g, 0.04, 0.05, 0.08, MAT.chrome, side * 0.88, 4.35, -1.3, 8);
|
|
1510
|
+
cyl(g, 0.03, 0.04, 0.04, MAT.ventHot, side * 0.88, 4.35, -1.34, 8);
|
|
460
1511
|
}
|
|
461
1512
|
|
|
462
|
-
// ──
|
|
1513
|
+
// ── BACKPACK EXTERIOR DETAIL ──
|
|
1514
|
+
// Hazard stripe band across top
|
|
1515
|
+
box(g, 1.36, 0.06, 0.06, MAT.armorRed, 0, 5.06, -1.0);
|
|
1516
|
+
// Warning chevrons (bottom)
|
|
1517
|
+
box(g, 1.16, 0.06, 0.06, MAT.armorRed, 0, 2.94, -1.0);
|
|
1518
|
+
// Grab handles (chrome rungs on sides)
|
|
1519
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1520
|
+
cyl(g, 0.02, 0.02, 0.22, MAT.chrome, side * 0.68, 4.4, -0.98, 6);
|
|
1521
|
+
cyl(g, 0.02, 0.02, 0.22, MAT.chrome, side * 0.68, 3.6, -0.98, 6);
|
|
1522
|
+
sphere(g, 0.025, MAT.joint, side * 0.68, 4.4, -0.88);
|
|
1523
|
+
sphere(g, 0.025, MAT.joint, side * 0.68, 3.6, -0.88);
|
|
1524
|
+
sphere(g, 0.025, MAT.joint, side * 0.68, 4.4, -1.08);
|
|
1525
|
+
sphere(g, 0.025, MAT.joint, side * 0.68, 3.6, -1.08);
|
|
1526
|
+
}
|
|
1527
|
+
// Central exhaust port (waste heat vent)
|
|
1528
|
+
cyl(g, 0.1, 0.1, 0.06, MAT.darkChrome, 0, 3.5, -1.16, 12);
|
|
1529
|
+
cyl(g, 0.06, 0.06, 0.04, MAT.ventHot, 0, 3.5, -1.18, 10);
|
|
1530
|
+
torus(g, 0.08, 0.01, MAT.vent, 0, 3.5, -1.16);
|
|
1531
|
+
|
|
1532
|
+
// ══════════════════════════════════════════════════════════
|
|
1533
|
+
// SHOULDER HARDPOINTS — weapon/tool mounts on arm pivots
|
|
1534
|
+
// ══════════════════════════════════════════════════════════
|
|
463
1535
|
for (let side = -1; side <= 1; side += 2) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
1536
|
+
const tgt = side === -1 ? la : ra;
|
|
1537
|
+
const sx = side * 2.1;
|
|
1538
|
+
const hx = sx + side * 0.45;
|
|
1539
|
+
// Mounting bracket (bolted to pauldron)
|
|
1540
|
+
box(tgt, 0.16, 0.24, 0.16, MAT.frameMat, hx, 5.52, -0.22);
|
|
1541
|
+
boltCluster(tgt, 4, 0.08, hx, 5.52, -0.14);
|
|
1542
|
+
// Hardpoint pylon (chrome cylinder)
|
|
1543
|
+
cyl(tgt, 0.15, 0.15, 0.8, MAT.hardpoint, hx, 5.68, -0.22);
|
|
1544
|
+
// Servo rotation drum at base
|
|
1545
|
+
servoDrum(tgt, 0.06, 0.1, hx, 5.52, -0.28);
|
|
1546
|
+
// Weapon rail housing (layered)
|
|
1547
|
+
box(tgt, 0.26, 0.26, 0.74, MAT.hardpoint, hx, 6.02, -0.22);
|
|
1548
|
+
box(tgt, 0.2, 0.12, 0.66, MAT.darkChrome, hx, 6.12, -0.22);
|
|
1549
|
+
box(tgt, 0.12, 0.08, 0.58, MAT.vent, hx, 5.92, -0.22);
|
|
1550
|
+
// Rail guides (top grooves for tool attachment)
|
|
1551
|
+
for (let r = -1; r <= 1; r += 2) {
|
|
1552
|
+
box(tgt, 0.02, 0.2, 0.6, MAT.panelLine, hx + r * 0.1, 6.14, -0.22);
|
|
467
1553
|
}
|
|
1554
|
+
// Status indicator
|
|
1555
|
+
sphere(tgt, 0.015, MAT.coreInner, hx, 6.16, 0.1);
|
|
1556
|
+
// Hardpoint cable feed
|
|
1557
|
+
cableBundle(tgt, 2, 0.008, 0.4, hx - side * 0.08, 5.72, -0.12);
|
|
1558
|
+
rivet(tgt, hx, 6.0, 0.14); rivet(tgt, hx, 6.0, -0.56);
|
|
468
1559
|
}
|
|
469
1560
|
|
|
470
|
-
//
|
|
1561
|
+
// ══════════════════════════════════════════════════════════
|
|
1562
|
+
// CHEST ENERGY VENTS — intake/exhaust grilles with glow
|
|
1563
|
+
// ══════════════════════════════════════════════════════════
|
|
1564
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1565
|
+
// Vent housing recess
|
|
1566
|
+
box(g, 0.48, 0.9, 0.06, MAT.frameMat, side * 0.55, 4.45, 0.67);
|
|
1567
|
+
box(g, 0.42, 0.82, 0.03, MAT.servo, side * 0.55, 4.45, 0.68);
|
|
1568
|
+
// Vent slats (6 per side with inner glow backing)
|
|
1569
|
+
for (let i = 0; i < 6; i++) {
|
|
1570
|
+
box(g, 0.36, 0.035, 0.07, MAT.vent, side * 0.55, 4.72 - i * 0.14, 0.7);
|
|
1571
|
+
box(g, 0.3, 0.015, 0.04, MAT.ventHot, side * 0.55, 4.72 - i * 0.14, 0.68);
|
|
1572
|
+
}
|
|
1573
|
+
// Vent frame bolts
|
|
1574
|
+
rivet(g, side * 0.72, 4.82, 0.68); rivet(g, side * 0.72, 4.08, 0.68);
|
|
1575
|
+
rivet(g, side * 0.38, 4.82, 0.68); rivet(g, side * 0.38, 4.08, 0.68);
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
// ══════════════════════════════════════════════════════════
|
|
1579
|
+
// JETPACK INTERNAL SYSTEMS — reactor, flight computer, power bus
|
|
1580
|
+
// ══════════════════════════════════════════════════════════
|
|
1581
|
+
// Central reactor housing (visible through backpack center)
|
|
1582
|
+
box(g, 0.54, 0.64, 0.24, MAT.darkChrome, 0, 3.9, -1.0);
|
|
1583
|
+
box(g, 0.44, 0.54, 0.16, MAT.servo, 0, 3.9, -1.0);
|
|
1584
|
+
// Reactor glow core (larger, multi-ring)
|
|
1585
|
+
sphere(g, 0.1, MAT.coreInner, 0, 3.9, -1.06);
|
|
1586
|
+
torus(g, 0.14, 0.018, MAT.vent, 0, 3.9, -1.06);
|
|
1587
|
+
torus(g, 0.1, 0.01, MAT.coreInner, 0, 3.9, -1.08);
|
|
1588
|
+
// Reactor coolant manifold (feeds engines)
|
|
1589
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1590
|
+
cyl(g, 0.035, 0.035, 0.2, MAT.hydraulic, side * 0.2, 3.9, -1.1, 8);
|
|
1591
|
+
cableRun(g, 0.012, 0.6, side * 0.24, 3.9, -1.06);
|
|
1592
|
+
// Coolant to engine feed
|
|
1593
|
+
cableRun(g, 0.014, 0.4, side * 0.36, 3.9, -1.06);
|
|
1594
|
+
}
|
|
1595
|
+
boltCluster(g, 6, 0.2, 0, 3.9, -0.94);
|
|
1596
|
+
|
|
1597
|
+
// Flight computer module (upper backpack)
|
|
1598
|
+
box(g, 0.4, 0.3, 0.18, MAT.darkChrome, 0, 4.72, -1.0);
|
|
1599
|
+
box(g, 0.32, 0.22, 0.12, MAT.servo, 0, 4.72, -1.0);
|
|
1600
|
+
// Data port array (6 connectors)
|
|
1601
|
+
for (let i = 0; i < 6; i++) {
|
|
1602
|
+
const dx = -0.14 + i * 0.056;
|
|
1603
|
+
cyl(g, 0.022, 0.022, 0.04, MAT.frameMat, dx, 4.72, -1.1, 6);
|
|
1604
|
+
sphere(g, 0.009, MAT.vent, dx, 4.72, -1.12);
|
|
1605
|
+
}
|
|
1606
|
+
// Status LEDs
|
|
1607
|
+
sphere(g, 0.012, MAT.coreInner, 0.14, 4.84, -1.0);
|
|
1608
|
+
sphere(g, 0.012, MAT.ventHot, -0.14, 4.84, -1.0);
|
|
1609
|
+
sphere(g, 0.01, MAT.vent, 0, 4.84, -1.0);
|
|
1610
|
+
|
|
1611
|
+
// Dorsal stabilizer fin (tall, between shoulders)
|
|
1612
|
+
box(g, 0.07, 0.6, 0.28, MAT.chrome, 0, 5.4, -0.88);
|
|
1613
|
+
box(g, 0.04, 0.5, 0.22, MAT.panelLine, 0, 5.4, -0.92);
|
|
1614
|
+
box(g, 0.04, 0.35, 0.14, MAT.darkChrome, 0, 5.5, -0.96);
|
|
1615
|
+
// Fin tip beacon
|
|
1616
|
+
sphere(g, 0.022, MAT.coreInner, 0, 5.68, -0.88);
|
|
1617
|
+
// Fin base bolts
|
|
1618
|
+
rivet(g, 0.03, 5.12, -0.86); rivet(g, -0.03, 5.12, -0.86);
|
|
1619
|
+
|
|
1620
|
+
// Power distribution bus (horizontal bar, lower backpack)
|
|
1621
|
+
box(g, 0.9, 0.08, 0.1, MAT.frameMat, 0, 3.2, -1.04);
|
|
1622
|
+
for (let i = 0; i < 5; i++) {
|
|
1623
|
+
const bx = -0.3 + i * 0.15;
|
|
1624
|
+
cyl(g, 0.018, 0.018, 0.06, MAT.hydraulic, bx, 3.2, -1.1, 6);
|
|
1625
|
+
sphere(g, 0.008, MAT.vent, bx, 3.2, -1.14);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Lumbar support braces (lower back reinforcement, connecting to jetpack)
|
|
1629
|
+
for (let side = -1; side <= 1; side += 2) {
|
|
1630
|
+
box(g, 0.16, 0.85, 0.14, MAT.frameMat, side * 0.36, 2.9, -0.76);
|
|
1631
|
+
hydraulicRam(g, 0.022, 0.65, side * 0.36, 2.9, -0.7);
|
|
1632
|
+
box(g, 0.06, 0.6, 0.06, MAT.panelLine, side * 0.36, 2.9, -0.82);
|
|
1633
|
+
rivet(g, side * 0.36, 3.28, -0.72); rivet(g, side * 0.36, 2.52, -0.72);
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// ── Attach pivot groups and store refs ──
|
|
1637
|
+
g.add(_hp); g.add(_lp); g.add(_rp);
|
|
1638
|
+
headPivotRef = _hp; leftArmPivotRef = _lp; rightArmPivotRef = _rp;
|
|
1639
|
+
|
|
1640
|
+
// Edge highlights on major armor pieces only (skip rivets, cables, small detail).
|
|
471
1641
|
const edgeMat = new THREE.LineBasicMaterial({
|
|
472
|
-
color:
|
|
1642
|
+
color: 0x3355aa,
|
|
473
1643
|
transparent: true,
|
|
474
|
-
opacity: 0.
|
|
1644
|
+
opacity: 0.15,
|
|
475
1645
|
blending: THREE.AdditiveBlending,
|
|
476
1646
|
depthWrite: false,
|
|
477
1647
|
});
|
|
1648
|
+
const _edgeCache = new Map();
|
|
478
1649
|
g.traverse((node) => {
|
|
479
1650
|
if (!node.isMesh || !node.geometry) return;
|
|
480
|
-
|
|
481
|
-
const
|
|
1651
|
+
if (node.userData._detail) return;
|
|
1652
|
+
const s = node.geometry.boundingSphere;
|
|
1653
|
+
if (!s) node.geometry.computeBoundingSphere();
|
|
1654
|
+
if (node.geometry.boundingSphere && node.geometry.boundingSphere.radius < 0.04) return;
|
|
1655
|
+
const geoId = node.geometry.uuid;
|
|
1656
|
+
let edgeGeo = _edgeCache.get(geoId);
|
|
1657
|
+
if (!edgeGeo) { edgeGeo = new THREE.EdgesGeometry(node.geometry, 28); _edgeCache.set(geoId, edgeGeo); }
|
|
1658
|
+
const lines = new THREE.LineSegments(edgeGeo, edgeMat);
|
|
482
1659
|
lines.renderOrder = 2;
|
|
483
1660
|
node.add(lines);
|
|
484
1661
|
});
|
|
@@ -508,63 +1685,136 @@ function buildChassis() {
|
|
|
508
1685
|
function buildPlatform() {
|
|
509
1686
|
const platform = new THREE.Group();
|
|
510
1687
|
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
const
|
|
1688
|
+
// ── BASE STRUCTURE (multi-layer industrial hex pad) ──
|
|
1689
|
+
// Lower foundation ring (heavy, dark)
|
|
1690
|
+
const foundGeo = new THREE.CylinderGeometry(5.6, 5.8, 0.18, 6);
|
|
1691
|
+
const foundMat = new P({ color: 0x040408, roughness: 0.7, metalness: 0.6, clearcoat: 0.2 });
|
|
1692
|
+
const foundation = new THREE.Mesh(foundGeo, foundMat);
|
|
1693
|
+
foundation.position.y = -2.9;
|
|
1694
|
+
platform.add(foundation);
|
|
1695
|
+
|
|
1696
|
+
// Main platform (polished dark surface)
|
|
1697
|
+
const padGeo = new THREE.CylinderGeometry(5.2, 5.2, 0.14, 6);
|
|
1698
|
+
const padMat = new P({ color: 0x0a0a10, roughness: 0.25, metalness: 0.7, clearcoat: 0.6, clearcoatRoughness: 0.15 });
|
|
514
1699
|
const pad = new THREE.Mesh(padGeo, padMat);
|
|
515
|
-
pad.position.y = -2.
|
|
1700
|
+
pad.position.y = -2.72;
|
|
516
1701
|
platform.add(pad);
|
|
517
1702
|
|
|
518
|
-
//
|
|
519
|
-
|
|
520
|
-
|
|
1703
|
+
// Upper deck (thin chrome trim ring)
|
|
1704
|
+
const deckGeo = new THREE.CylinderGeometry(4.8, 4.9, 0.04, 6);
|
|
1705
|
+
const deckMat = new P({ color: 0x1a1a24, roughness: 0.15, metalness: 0.85, clearcoat: 0.8 });
|
|
1706
|
+
const deck = new THREE.Mesh(deckGeo, deckMat);
|
|
1707
|
+
deck.position.y = -2.64;
|
|
1708
|
+
platform.add(deck);
|
|
1709
|
+
|
|
1710
|
+
// ── CONCENTRIC HEX RINGS (glowing, animated) ──
|
|
1711
|
+
for (let i = 1; i <= 6; i++) {
|
|
1712
|
+
const r = i * 0.82;
|
|
1713
|
+
const ringGeo = new THREE.RingGeometry(r - 0.015, r + 0.015, 6);
|
|
1714
|
+
const isPrimary = i === 3 || i === 5;
|
|
521
1715
|
const ringMat = new THREE.MeshBasicMaterial({
|
|
522
|
-
color:
|
|
523
|
-
|
|
1716
|
+
color: isPrimary ? CYAN : GLOW,
|
|
1717
|
+
transparent: true,
|
|
1718
|
+
opacity: isPrimary ? 0.12 : 0.05,
|
|
1719
|
+
side: THREE.DoubleSide,
|
|
1720
|
+
blending: THREE.AdditiveBlending,
|
|
524
1721
|
});
|
|
525
1722
|
const ring = new THREE.Mesh(ringGeo, ringMat);
|
|
526
1723
|
ring.rotation.x = -Math.PI / 2;
|
|
527
|
-
ring.position.y = -2.
|
|
1724
|
+
ring.position.y = -2.64;
|
|
528
1725
|
ring.userData.ringIndex = i;
|
|
529
1726
|
platform.add(ring);
|
|
530
1727
|
}
|
|
531
1728
|
|
|
532
|
-
//
|
|
533
|
-
for (let
|
|
534
|
-
const
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1729
|
+
// ── HEX EDGE GLOW LINES (outer boundary, double-width for primary) ──
|
|
1730
|
+
for (let layer = 0; layer < 2; layer++) {
|
|
1731
|
+
const r = layer === 0 ? 5.2 : 4.0;
|
|
1732
|
+
const opacity = layer === 0 ? 0.35 : 0.12;
|
|
1733
|
+
for (let i = 0; i < 6; i++) {
|
|
1734
|
+
const a = (i / 6) * Math.PI * 2;
|
|
1735
|
+
const b = ((i + 1) / 6) * Math.PI * 2;
|
|
1736
|
+
const pts = [
|
|
1737
|
+
new THREE.Vector3(Math.cos(a) * r, -2.65, Math.sin(a) * r),
|
|
1738
|
+
new THREE.Vector3(Math.cos(b) * r, -2.65, Math.sin(b) * r),
|
|
1739
|
+
];
|
|
1740
|
+
const geo = new THREE.BufferGeometry().setFromPoints(pts);
|
|
1741
|
+
const mat = new THREE.LineBasicMaterial({
|
|
1742
|
+
color: layer === 0 ? GLOW : CYAN,
|
|
1743
|
+
transparent: true, opacity,
|
|
1744
|
+
blending: THREE.AdditiveBlending,
|
|
1745
|
+
});
|
|
1746
|
+
platform.add(new THREE.Line(geo, mat));
|
|
1747
|
+
}
|
|
547
1748
|
}
|
|
548
1749
|
|
|
549
|
-
//
|
|
550
|
-
for (let i = 0; i <
|
|
551
|
-
const a = (i /
|
|
1750
|
+
// ── RADIAL SPOKES (24 fine lines + 6 thick primary) ──
|
|
1751
|
+
for (let i = 0; i < 24; i++) {
|
|
1752
|
+
const a = (i / 24) * Math.PI * 2;
|
|
1753
|
+
const isPrimary = i % 4 === 0;
|
|
1754
|
+
const len = isPrimary ? 5.0 : 4.2;
|
|
552
1755
|
const pts = [
|
|
553
|
-
new THREE.Vector3(0, -2.
|
|
554
|
-
new THREE.Vector3(Math.cos(a) *
|
|
1756
|
+
new THREE.Vector3(Math.cos(a) * 0.5, -2.65, Math.sin(a) * 0.5),
|
|
1757
|
+
new THREE.Vector3(Math.cos(a) * len, -2.65, Math.sin(a) * len),
|
|
555
1758
|
];
|
|
556
1759
|
const geo = new THREE.BufferGeometry().setFromPoints(pts);
|
|
557
1760
|
const mat = new THREE.LineBasicMaterial({
|
|
558
|
-
color:
|
|
1761
|
+
color: isPrimary ? CYAN : GLOW,
|
|
1762
|
+
transparent: true, opacity: isPrimary ? 0.08 : 0.03,
|
|
559
1763
|
blending: THREE.AdditiveBlending,
|
|
560
1764
|
});
|
|
561
1765
|
platform.add(new THREE.Line(geo, mat));
|
|
562
1766
|
}
|
|
563
1767
|
|
|
564
|
-
//
|
|
565
|
-
|
|
1768
|
+
// ── CORNER PYLONS (6 hex vertices, industrial bollards) ──
|
|
1769
|
+
for (let i = 0; i < 6; i++) {
|
|
1770
|
+
const a = (i / 6) * Math.PI * 2;
|
|
1771
|
+
const px = Math.cos(a) * 5.0, pz = Math.sin(a) * 5.0;
|
|
1772
|
+
// Pylon body
|
|
1773
|
+
const pylonGeo = new THREE.CylinderGeometry(0.12, 0.14, 0.6, 8);
|
|
1774
|
+
const pylon = new THREE.Mesh(pylonGeo, new P({ color: 0x101018, roughness: 0.3, metalness: 0.8, clearcoat: 0.4 }));
|
|
1775
|
+
pylon.position.set(px, -2.5, pz);
|
|
1776
|
+
platform.add(pylon);
|
|
1777
|
+
// Pylon top cap (chrome)
|
|
1778
|
+
const capGeo = new THREE.CylinderGeometry(0.15, 0.12, 0.06, 8);
|
|
1779
|
+
const cap = new THREE.Mesh(capGeo, new P({ color: 0x2a2a34, roughness: 0.12, metalness: 0.9 }));
|
|
1780
|
+
cap.position.set(px, -2.22, pz);
|
|
1781
|
+
platform.add(cap);
|
|
1782
|
+
// Pylon glow ring
|
|
1783
|
+
const glowRing = new THREE.Mesh(
|
|
1784
|
+
new THREE.TorusGeometry(0.14, 0.012, 6, 16),
|
|
1785
|
+
new THREE.MeshBasicMaterial({ color: GLOW, transparent: true, opacity: 0.2, blending: THREE.AdditiveBlending })
|
|
1786
|
+
);
|
|
1787
|
+
glowRing.position.set(px, -2.35, pz);
|
|
1788
|
+
glowRing.rotation.x = Math.PI / 2;
|
|
1789
|
+
glowRing.userData.pylonGlow = true;
|
|
1790
|
+
platform.add(glowRing);
|
|
1791
|
+
// Pylon status LED
|
|
1792
|
+
const led = new THREE.Mesh(
|
|
1793
|
+
new THREE.SphereGeometry(0.03, 6, 4),
|
|
1794
|
+
new THREE.MeshBasicMaterial({ color: CYAN, transparent: true, opacity: 0.6, blending: THREE.AdditiveBlending })
|
|
1795
|
+
);
|
|
1796
|
+
led.position.set(px, -2.2, pz);
|
|
1797
|
+
led.userData.pylonLed = i;
|
|
1798
|
+
platform.add(led);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// ── HOLOGRAPHIC GRID (subtle floor markings inside hex) ──
|
|
1802
|
+
const gridMat = new THREE.LineBasicMaterial({
|
|
1803
|
+
color: GLOW, transparent: true, opacity: 0.02, blending: THREE.AdditiveBlending,
|
|
1804
|
+
});
|
|
1805
|
+
for (let gx = -4; gx <= 4; gx++) {
|
|
1806
|
+
const pts = [new THREE.Vector3(gx, -2.64, -4.5), new THREE.Vector3(gx, -2.64, 4.5)];
|
|
1807
|
+
platform.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), gridMat));
|
|
1808
|
+
}
|
|
1809
|
+
for (let gz = -4; gz <= 4; gz++) {
|
|
1810
|
+
const pts = [new THREE.Vector3(-4.5, -2.64, gz), new THREE.Vector3(4.5, -2.64, gz)];
|
|
1811
|
+
platform.add(new THREE.Line(new THREE.BufferGeometry().setFromPoints(pts), gridMat));
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// ── HOLOGRAPHIC SCANLINE CYLINDER ──
|
|
1815
|
+
const scanGeo = new THREE.CylinderGeometry(4.8, 4.8, 0.02, 48, 1, true);
|
|
566
1816
|
const scanMat = new THREE.MeshBasicMaterial({
|
|
567
|
-
color:
|
|
1817
|
+
color: CYAN, transparent: true, opacity: 0.04,
|
|
568
1818
|
side: THREE.DoubleSide, blending: THREE.AdditiveBlending,
|
|
569
1819
|
});
|
|
570
1820
|
const scan = new THREE.Mesh(scanGeo, scanMat);
|
|
@@ -572,6 +1822,39 @@ function buildPlatform() {
|
|
|
572
1822
|
scan.userData.isScanline = true;
|
|
573
1823
|
platform.add(scan);
|
|
574
1824
|
|
|
1825
|
+
// ── CENTRAL GLOW DISC (under the robot — volumetric uplight) ──
|
|
1826
|
+
const uplightGeo = new THREE.CylinderGeometry(1.8, 2.2, 0.01, 32);
|
|
1827
|
+
const uplightMat = new THREE.MeshBasicMaterial({
|
|
1828
|
+
color: GLOW, transparent: true, opacity: 0.06,
|
|
1829
|
+
blending: THREE.AdditiveBlending, depthWrite: false,
|
|
1830
|
+
});
|
|
1831
|
+
const uplight = new THREE.Mesh(uplightGeo, uplightMat);
|
|
1832
|
+
uplight.position.y = -2.63;
|
|
1833
|
+
uplight.userData.uplight = true;
|
|
1834
|
+
platform.add(uplight);
|
|
1835
|
+
|
|
1836
|
+
// ── HOLOGRAPHIC FIELD BOUNDARY (vertical light curtain at hex edges) ──
|
|
1837
|
+
for (let i = 0; i < 6; i++) {
|
|
1838
|
+
const a = (i / 6) * Math.PI * 2;
|
|
1839
|
+
const b = ((i + 1) / 6) * Math.PI * 2;
|
|
1840
|
+
const r = 5.15;
|
|
1841
|
+
const curtainGeo = new THREE.PlaneGeometry(
|
|
1842
|
+
Math.sqrt(Math.pow(Math.cos(b) * r - Math.cos(a) * r, 2) + Math.pow(Math.sin(b) * r - Math.sin(a) * r, 2)),
|
|
1843
|
+
3.0
|
|
1844
|
+
);
|
|
1845
|
+
const curtainMat = new THREE.MeshBasicMaterial({
|
|
1846
|
+
color: GLOW, transparent: true, opacity: 0.008,
|
|
1847
|
+
side: THREE.DoubleSide, blending: THREE.AdditiveBlending, depthWrite: false,
|
|
1848
|
+
});
|
|
1849
|
+
const curtain = new THREE.Mesh(curtainGeo, curtainMat);
|
|
1850
|
+
const mx = (Math.cos(a) + Math.cos(b)) / 2 * r;
|
|
1851
|
+
const mz = (Math.sin(a) + Math.sin(b)) / 2 * r;
|
|
1852
|
+
curtain.position.set(mx, -1.2, mz);
|
|
1853
|
+
curtain.lookAt(0, -1.2, 0);
|
|
1854
|
+
curtain.userData.curtain = true;
|
|
1855
|
+
platform.add(curtain);
|
|
1856
|
+
}
|
|
1857
|
+
|
|
575
1858
|
return platform;
|
|
576
1859
|
}
|
|
577
1860
|
|
|
@@ -579,44 +1862,81 @@ function buildPlatform() {
|
|
|
579
1862
|
Ambient particles + energy streams
|
|
580
1863
|
══════════════════════════════════════════════════════════ */
|
|
581
1864
|
function createAmbientParticles() {
|
|
582
|
-
const count =
|
|
1865
|
+
const count = 800;
|
|
583
1866
|
const positions = new Float32Array(count * 3);
|
|
584
|
-
const
|
|
1867
|
+
const colors = new Float32Array(count * 3);
|
|
1868
|
+
const palette = [
|
|
1869
|
+
new THREE.Color(GLOW),
|
|
1870
|
+
new THREE.Color(CYAN),
|
|
1871
|
+
new THREE.Color(0x8fbfff),
|
|
1872
|
+
new THREE.Color(0xb026ff),
|
|
1873
|
+
new THREE.Color(0x6699cc),
|
|
1874
|
+
];
|
|
585
1875
|
for (let i = 0; i < count; i++) {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
positions[i * 3
|
|
589
|
-
|
|
1876
|
+
const isNear = i < count * 0.3;
|
|
1877
|
+
const spread = isNear ? 8 : 28;
|
|
1878
|
+
positions[i * 3] = (Math.random() - 0.5) * spread;
|
|
1879
|
+
positions[i * 3 + 1] = Math.random() * 20 - 4;
|
|
1880
|
+
positions[i * 3 + 2] = (Math.random() - 0.5) * spread;
|
|
1881
|
+
const c = palette[Math.floor(Math.random() * palette.length)];
|
|
1882
|
+
colors[i * 3] = c.r; colors[i * 3 + 1] = c.g; colors[i * 3 + 2] = c.b;
|
|
590
1883
|
}
|
|
591
1884
|
const geo = new THREE.BufferGeometry();
|
|
592
1885
|
geo.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
1886
|
+
geo.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
593
1887
|
return new THREE.Points(geo, new THREE.PointsMaterial({
|
|
594
|
-
|
|
595
|
-
blending: THREE.AdditiveBlending, depthWrite: false,
|
|
1888
|
+
size: 0.04, transparent: true, opacity: 0.35, vertexColors: true,
|
|
1889
|
+
blending: THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true,
|
|
596
1890
|
}));
|
|
597
1891
|
}
|
|
598
1892
|
|
|
599
1893
|
function createEnergyStreams() {
|
|
600
1894
|
const group = new THREE.Group();
|
|
1895
|
+
const streamColors = [GLOW, CYAN, 0x8fbfff, GLOW, GLOW, CYAN, 0xb026ff, GLOW, CYAN, GLOW, GLOW, 0x8fbfff, 0x6699cc, CYAN, GLOW, 0xb026ff];
|
|
1896
|
+
// Rising helical streams (16 total, denser)
|
|
1897
|
+
for (let i = 0; i < 16; i++) {
|
|
1898
|
+
const pts = [];
|
|
1899
|
+
const angle = (i / 16) * Math.PI * 2;
|
|
1900
|
+
const r = 1.8 + Math.random() * 2.5;
|
|
1901
|
+
const twist = 0.4 + Math.random() * 0.5;
|
|
1902
|
+
const segments = 36;
|
|
1903
|
+
for (let j = 0; j <= segments; j++) {
|
|
1904
|
+
const t = j / segments;
|
|
1905
|
+
pts.push(new THREE.Vector3(
|
|
1906
|
+
Math.cos(angle + t * Math.PI * twist) * r * (1 - t * 0.4),
|
|
1907
|
+
-2.8 + t * 16,
|
|
1908
|
+
Math.sin(angle + t * Math.PI * twist) * r * (1 - t * 0.4),
|
|
1909
|
+
));
|
|
1910
|
+
}
|
|
1911
|
+
const geo = new THREE.BufferGeometry().setFromPoints(pts);
|
|
1912
|
+
const mat = new THREE.LineBasicMaterial({
|
|
1913
|
+
color: streamColors[i % streamColors.length], transparent: true, opacity: 0.035,
|
|
1914
|
+
blending: THREE.AdditiveBlending,
|
|
1915
|
+
});
|
|
1916
|
+
const line = new THREE.Line(geo, mat);
|
|
1917
|
+
line.userData.streamAngle = angle;
|
|
1918
|
+
group.add(line);
|
|
1919
|
+
}
|
|
1920
|
+
// Descending data cascade (8 vertical streams falling through platform)
|
|
601
1921
|
for (let i = 0; i < 8; i++) {
|
|
602
1922
|
const pts = [];
|
|
603
|
-
const angle = (i / 8) * Math.PI * 2;
|
|
604
|
-
const r = 2
|
|
1923
|
+
const angle = (i / 8) * Math.PI * 2 + 0.3;
|
|
1924
|
+
const r = 3.2 + Math.random() * 1.5;
|
|
605
1925
|
for (let j = 0; j <= 20; j++) {
|
|
606
1926
|
const t = j / 20;
|
|
607
1927
|
pts.push(new THREE.Vector3(
|
|
608
|
-
Math.cos(angle
|
|
609
|
-
-
|
|
610
|
-
Math.sin(angle
|
|
1928
|
+
Math.cos(angle) * r + Math.sin(t * 6) * 0.15,
|
|
1929
|
+
12 - t * 16,
|
|
1930
|
+
Math.sin(angle) * r + Math.cos(t * 6) * 0.15,
|
|
611
1931
|
));
|
|
612
1932
|
}
|
|
613
1933
|
const geo = new THREE.BufferGeometry().setFromPoints(pts);
|
|
614
1934
|
const mat = new THREE.LineBasicMaterial({
|
|
615
|
-
color:
|
|
1935
|
+
color: 0x2244aa, transparent: true, opacity: 0.02,
|
|
616
1936
|
blending: THREE.AdditiveBlending,
|
|
617
1937
|
});
|
|
618
1938
|
const line = new THREE.Line(geo, mat);
|
|
619
|
-
line.userData.
|
|
1939
|
+
line.userData.cascade = true;
|
|
620
1940
|
group.add(line);
|
|
621
1941
|
}
|
|
622
1942
|
return group;
|
|
@@ -624,11 +1944,15 @@ function createEnergyStreams() {
|
|
|
624
1944
|
|
|
625
1945
|
function updateAmbientParticles(pts, time) {
|
|
626
1946
|
const pos = pts.geometry.attributes.position.array;
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1947
|
+
const count = pos.length / 3;
|
|
1948
|
+
const nearCount = Math.floor(count * 0.3);
|
|
1949
|
+
for (let i = 0; i < count; i++) {
|
|
1950
|
+
const idx = i * 3;
|
|
1951
|
+
const speed = i < nearCount ? 0.006 : 0.003;
|
|
1952
|
+
pos[idx + 1] += speed;
|
|
1953
|
+
pos[idx] += Math.sin(time * 0.7 + i * 0.3) * 0.001;
|
|
1954
|
+
pos[idx + 2] += Math.cos(time * 0.5 + i * 0.2) * 0.0008;
|
|
1955
|
+
if (pos[idx + 1] > 14) pos[idx + 1] = -4;
|
|
632
1956
|
}
|
|
633
1957
|
pts.geometry.attributes.position.needsUpdate = true;
|
|
634
1958
|
}
|
|
@@ -641,11 +1965,23 @@ function updatePlatformRings(time) {
|
|
|
641
1965
|
platformGroup.children.forEach(child => {
|
|
642
1966
|
if (child.userData.ringIndex) {
|
|
643
1967
|
const i = child.userData.ringIndex;
|
|
644
|
-
child.material.opacity = 0.03 + 0.
|
|
1968
|
+
child.material.opacity = 0.03 + 0.12 * Math.max(0, Math.sin(time * 1.5 - i * 0.55));
|
|
645
1969
|
}
|
|
646
1970
|
if (child.userData.isScanline) {
|
|
647
|
-
child.position.y = -2.5 + Math.sin(time * 0.
|
|
648
|
-
child.material.opacity = 0.
|
|
1971
|
+
child.position.y = -2.5 + Math.sin(time * 0.6) * 7 + 7;
|
|
1972
|
+
child.material.opacity = 0.025 + 0.025 * Math.sin(time * 2.2);
|
|
1973
|
+
}
|
|
1974
|
+
if (child.userData.pylonGlow) {
|
|
1975
|
+
child.material.opacity = 0.12 + 0.12 * Math.sin(time * 2.0 + child.position.x * 2);
|
|
1976
|
+
}
|
|
1977
|
+
if (child.userData.pylonLed !== undefined) {
|
|
1978
|
+
child.material.opacity = 0.3 + 0.4 * Math.max(0, Math.sin(time * 3 + child.userData.pylonLed * 1.05));
|
|
1979
|
+
}
|
|
1980
|
+
if (child.userData.uplight) {
|
|
1981
|
+
child.material.opacity = 0.04 + 0.03 * Math.sin(time * 1.2);
|
|
1982
|
+
}
|
|
1983
|
+
if (child.userData.curtain) {
|
|
1984
|
+
child.material.opacity = 0.005 + 0.005 * Math.sin(time * 0.8 + child.position.x);
|
|
649
1985
|
}
|
|
650
1986
|
});
|
|
651
1987
|
}
|
|
@@ -676,7 +2012,9 @@ function playCinematicIntro(onComplete) {
|
|
|
676
2012
|
const startTarget = new THREE.Vector3(0, 1, 0);
|
|
677
2013
|
const endTarget = new THREE.Vector3(0, 2.5, 0);
|
|
678
2014
|
controls.enabled = false;
|
|
679
|
-
scene.fog
|
|
2015
|
+
const AMBIENT_FOG_DENSITY = scene.fog ? scene.fog.density : 0.018;
|
|
2016
|
+
if (scene.fog) scene.fog.density = 0.07;
|
|
2017
|
+
else scene.fog = new THREE.FogExp2(0x020206, 0.07);
|
|
680
2018
|
camera.position.copy(startPos);
|
|
681
2019
|
controls.target.copy(startTarget);
|
|
682
2020
|
|
|
@@ -685,7 +2023,7 @@ function playCinematicIntro(onComplete) {
|
|
|
685
2023
|
|
|
686
2024
|
function tick() {
|
|
687
2025
|
if (introSkipped) {
|
|
688
|
-
scene.fog =
|
|
2026
|
+
if (scene.fog) scene.fog.density = AMBIENT_FOG_DENSITY;
|
|
689
2027
|
camera.position.copy(endPos);
|
|
690
2028
|
controls.target.copy(endTarget);
|
|
691
2029
|
controls.enabled = true;
|
|
@@ -702,11 +2040,11 @@ function playCinematicIntro(onComplete) {
|
|
|
702
2040
|
camera.position.lerpVectors(startPos, endPos, eased);
|
|
703
2041
|
controls.target.lerpVectors(startTarget, endTarget, eased);
|
|
704
2042
|
const fogT = Math.min(elapsed / 2500, 1);
|
|
705
|
-
scene.fog.density = 0.
|
|
2043
|
+
if (scene.fog) scene.fog.density = 0.07 + (AMBIENT_FOG_DENSITY - 0.07) * fogT;
|
|
706
2044
|
if (t < 1) {
|
|
707
2045
|
requestAnimationFrame(tick);
|
|
708
2046
|
} else {
|
|
709
|
-
scene.fog =
|
|
2047
|
+
if (scene.fog) scene.fog.density = AMBIENT_FOG_DENSITY;
|
|
710
2048
|
controls.enabled = true;
|
|
711
2049
|
introPlaying = false;
|
|
712
2050
|
sessionStorage.setItem("forge_intro_done", "1");
|
|
@@ -815,7 +2153,23 @@ function findNearestAttachment(ndc) {
|
|
|
815
2153
|
function onCanvasClick(e) {
|
|
816
2154
|
const ndc = screenToNDC(e);
|
|
817
2155
|
const target = findNearestAttachment(ndc);
|
|
818
|
-
|
|
2156
|
+
if (target) {
|
|
2157
|
+
selectAttachment(target);
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
// Check if click hit the robot body
|
|
2161
|
+
if (robotGroup) {
|
|
2162
|
+
mouse.copy(ndc);
|
|
2163
|
+
raycaster.setFromCamera(mouse, camera);
|
|
2164
|
+
const bodyMeshes = [];
|
|
2165
|
+
robotGroup.traverse(c => { if (c.isMesh) bodyMeshes.push(c); });
|
|
2166
|
+
const hits = raycaster.intersectObjects(bodyMeshes, false);
|
|
2167
|
+
if (hits.length > 0) {
|
|
2168
|
+
toggleRobotPower();
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
selectAttachment(null);
|
|
819
2173
|
}
|
|
820
2174
|
|
|
821
2175
|
function onCanvasHover(e) {
|
|
@@ -886,7 +2240,7 @@ function initHUD() {
|
|
|
886
2240
|
});
|
|
887
2241
|
document.getElementById("forgeToggleBloom")?.addEventListener("click", (e) => {
|
|
888
2242
|
bloomEnabled = !bloomEnabled;
|
|
889
|
-
bloomPass.strength = bloomEnabled ?
|
|
2243
|
+
bloomPass.strength = bloomEnabled ? 0.55 : 0;
|
|
890
2244
|
e.currentTarget.classList.toggle("active", bloomEnabled);
|
|
891
2245
|
});
|
|
892
2246
|
}
|
|
@@ -954,47 +2308,107 @@ export function captureScreenshot(agentName, skillCount, onchainCount, categoryC
|
|
|
954
2308
|
══════════════════════════════════════════════════════════ */
|
|
955
2309
|
let idleAnimatorFn = null;
|
|
956
2310
|
export function setIdleAnimator(fn) { idleAnimatorFn = fn; }
|
|
2311
|
+
export function setAgentSpeaking(v) {
|
|
2312
|
+
agentSpeaking = !!v;
|
|
2313
|
+
if (agentSpeaking) {
|
|
2314
|
+
powerOn();
|
|
2315
|
+
} else {
|
|
2316
|
+
schedulePowerDown();
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
function powerOn() {
|
|
2321
|
+
if (powerDownTimer) { clearTimeout(powerDownTimer); powerDownTimer = null; }
|
|
2322
|
+
robotPowered = true;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function schedulePowerDown() {
|
|
2326
|
+
if (powerDownTimer) clearTimeout(powerDownTimer);
|
|
2327
|
+
powerDownTimer = setTimeout(() => {
|
|
2328
|
+
robotPowered = false;
|
|
2329
|
+
powerDownTimer = null;
|
|
2330
|
+
}, POWER_DOWN_DELAY);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
export function toggleRobotPower() {
|
|
2334
|
+
if (robotPowered) {
|
|
2335
|
+
robotPowered = false;
|
|
2336
|
+
if (powerDownTimer) { clearTimeout(powerDownTimer); powerDownTimer = null; }
|
|
2337
|
+
} else {
|
|
2338
|
+
powerOn();
|
|
2339
|
+
schedulePowerDown();
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
957
2342
|
|
|
958
2343
|
function animate() {
|
|
959
2344
|
requestAnimationFrame(animate);
|
|
960
2345
|
if (renderPaused) return;
|
|
961
2346
|
const time = performance.now() * 0.001;
|
|
2347
|
+
const dt = 1 / 60;
|
|
962
2348
|
|
|
963
2349
|
controls.update();
|
|
964
2350
|
|
|
2351
|
+
// ── Power level ramp (smooth on/off transition) ──
|
|
2352
|
+
const targetPower = robotPowered ? 1.0 : 0.0;
|
|
2353
|
+
if (powerLevel < targetPower) {
|
|
2354
|
+
powerLevel = Math.min(powerLevel + dt * POWER_UP_SPEED, 1.0);
|
|
2355
|
+
} else if (powerLevel > targetPower) {
|
|
2356
|
+
powerLevel = Math.max(powerLevel - dt * POWER_DOWN_SPEED, 0.0);
|
|
2357
|
+
}
|
|
2358
|
+
const pw = powerLevel;
|
|
2359
|
+
|
|
965
2360
|
// Core reactor pulse
|
|
966
2361
|
if (coreMesh) {
|
|
967
|
-
coreMesh.material.emissiveIntensity = 1.55 + 0.45 * Math.sin(time * 2.5);
|
|
968
|
-
coreMesh.rotation.y += 0.02;
|
|
969
|
-
coreMesh.rotation.x = Math.sin(time * 1.5) * 0.2;
|
|
2362
|
+
coreMesh.material.emissiveIntensity = (1.55 + 0.45 * Math.sin(time * 2.5)) * pw;
|
|
2363
|
+
coreMesh.rotation.y += 0.02 * pw;
|
|
2364
|
+
coreMesh.rotation.x = Math.sin(time * 1.5) * 0.2 * pw;
|
|
970
2365
|
}
|
|
971
2366
|
|
|
972
2367
|
// Spine glow cascade
|
|
973
2368
|
spineSegments.forEach((seg, i) => {
|
|
974
|
-
seg.material.emissiveIntensity = 1.0 + 1.5 * Math.max(0, Math.sin(time * 3.5 - i * 0.5));
|
|
2369
|
+
seg.material.emissiveIntensity = (1.0 + 1.5 * Math.max(0, Math.sin(time * 3.5 - i * 0.5))) * pw;
|
|
975
2370
|
});
|
|
976
2371
|
|
|
977
|
-
// Visor
|
|
2372
|
+
// Visor — off when powered down, full optical system when powered up
|
|
978
2373
|
if (visorMesh) {
|
|
979
|
-
|
|
980
|
-
|
|
2374
|
+
const sp = agentSpeaking ? 1.6 : 1.0;
|
|
2375
|
+
const vBreath = Math.sin(time * 1.8) * 0.02;
|
|
2376
|
+
const vNoise = (Math.random() - 0.5) * 0.015;
|
|
2377
|
+
const scanPhase = (time * 0.7) % 1.0;
|
|
2378
|
+
const scanPulse = Math.exp(-30 * (scanPhase - 0.5) * (scanPhase - 0.5)) * 0.12;
|
|
2379
|
+
const speakBurst = agentSpeaking ? Math.sin(time * 8) * 0.04 + Math.random() * 0.03 : 0;
|
|
2380
|
+
|
|
2381
|
+
visorMesh.material.transmission = 0.4 + 0.42 * pw + (vBreath + vNoise + scanPulse * 0.3) * pw;
|
|
2382
|
+
visorMesh.material.emissiveIntensity = ((2.0 + 0.4 * Math.sin(time * 2.5) + scanPulse * 2.0 + speakBurst) * sp) * pw;
|
|
2383
|
+
visorMesh.material.opacity = 0.5 + 0.34 * pw + (vBreath * 0.5 + scanPulse * 0.1) * pw;
|
|
2384
|
+
visorMesh.material.iridescence = (0.5 + 0.15 * Math.sin(time * 1.2)) * pw;
|
|
981
2385
|
}
|
|
982
2386
|
|
|
983
2387
|
// Joint glow rings pulse
|
|
984
2388
|
glowRings.forEach((ring, i) => {
|
|
985
2389
|
if (ring.material) {
|
|
986
|
-
ring.material.opacity = 0.3 + 0.2 * Math.sin(time * 2 + i * 0.7);
|
|
987
|
-
ring.material.emissiveIntensity = 1.1 + 0.6 * Math.sin(time * 2.5 + i * 0.5);
|
|
2390
|
+
ring.material.opacity = (0.3 + 0.2 * Math.sin(time * 2 + i * 0.7)) * pw;
|
|
2391
|
+
ring.material.emissiveIntensity = (1.1 + 0.6 * Math.sin(time * 2.5 + i * 0.5)) * pw;
|
|
988
2392
|
}
|
|
989
2393
|
});
|
|
990
2394
|
|
|
991
2395
|
// Exhaust flames flicker
|
|
992
2396
|
exhaustFlames.forEach((flame, i) => {
|
|
993
2397
|
if (flame.material) {
|
|
994
|
-
flame.material.emissiveIntensity = 1.4 + 0.8 * Math.random();
|
|
995
|
-
flame.material.opacity = 0.32 + 0.2 * Math.random();
|
|
2398
|
+
flame.material.emissiveIntensity = (1.4 + 0.8 * Math.random()) * pw;
|
|
2399
|
+
flame.material.opacity = (0.32 + 0.2 * Math.random()) * pw;
|
|
996
2400
|
}
|
|
997
|
-
flame.scale.y = 0.8 + 0.4 * Math.random();
|
|
2401
|
+
flame.scale.y = 0.8 + 0.4 * Math.random() * pw;
|
|
2402
|
+
});
|
|
2403
|
+
|
|
2404
|
+
// Heat shimmer above exhausts
|
|
2405
|
+
heatShimmerMeshes.forEach((shim, i) => {
|
|
2406
|
+
const phase = time * 3 + i * 1.5;
|
|
2407
|
+
shim.material.opacity = (0.025 + 0.02 * Math.sin(phase)) * pw;
|
|
2408
|
+
shim.scale.x = 1.0 + 0.15 * Math.sin(phase * 1.3) * pw;
|
|
2409
|
+
shim.scale.z = 1.0 + 0.15 * Math.sin(phase * 1.3 + 0.5) * pw;
|
|
2410
|
+
shim.scale.y = 0.9 + 0.2 * Math.sin(phase * 0.7) * pw;
|
|
2411
|
+
shim.rotation.y += 0.02 * pw;
|
|
998
2412
|
});
|
|
999
2413
|
|
|
1000
2414
|
// Energy streams rotation
|
|
@@ -1016,6 +2430,36 @@ function animate() {
|
|
|
1016
2430
|
|
|
1017
2431
|
if (idleAnimatorFn) idleAnimatorFn(time);
|
|
1018
2432
|
|
|
2433
|
+
// ── Procedural idle motion ──
|
|
2434
|
+
const sp = agentSpeaking ? 1.8 : 1.0;
|
|
2435
|
+
if (robotGroup) {
|
|
2436
|
+
robotGroup.position.y = Math.sin(time * 1.2) * 0.025 * sp;
|
|
2437
|
+
robotGroup.rotation.x = Math.sin(time * 0.7) * 0.006 * sp;
|
|
2438
|
+
robotGroup.position.x = Math.sin(time * 0.35) * 0.012 * sp;
|
|
2439
|
+
}
|
|
2440
|
+
if (headPivotRef) {
|
|
2441
|
+
headPivotRef.rotation.y = Math.sin(time * 0.3) * 0.12 * sp;
|
|
2442
|
+
headPivotRef.rotation.x = Math.sin(time * 0.5 + 0.3) * 0.04 * sp;
|
|
2443
|
+
headPivotRef.rotation.z = Math.sin(time * 0.22) * 0.018 * sp;
|
|
2444
|
+
}
|
|
2445
|
+
if (leftArmPivotRef) {
|
|
2446
|
+
leftArmPivotRef.rotation.x = Math.sin(time * 0.8) * 0.04 * sp;
|
|
2447
|
+
leftArmPivotRef.rotation.z = Math.sin(time * 0.55) * 0.025 * sp;
|
|
2448
|
+
}
|
|
2449
|
+
if (rightArmPivotRef) {
|
|
2450
|
+
rightArmPivotRef.rotation.x = Math.sin(time * 0.8 + 1.5) * 0.04 * sp;
|
|
2451
|
+
rightArmPivotRef.rotation.z = -Math.sin(time * 0.55 + 0.4) * 0.025 * sp;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// Handheld camera sway (applied to scene root to avoid fighting OrbitControls)
|
|
2455
|
+
// Subtle "breathing" of the camera operator
|
|
2456
|
+
const camSwayY = Math.sin(time * 0.4) * 0.03 + Math.sin(time * 0.15) * 0.04;
|
|
2457
|
+
const camSwayX = Math.sin(time * 0.25) * 0.02;
|
|
2458
|
+
scene.position.y = camSwayY * 0.5;
|
|
2459
|
+
scene.position.x = camSwayX * 0.5;
|
|
2460
|
+
|
|
2461
|
+
if (filmGrainPass) filmGrainPass.uniforms.time.value = time;
|
|
2462
|
+
|
|
1019
2463
|
composer.render();
|
|
1020
2464
|
css2dRenderer.render(scene, camera);
|
|
1021
2465
|
}
|
|
@@ -1032,6 +2476,7 @@ function onResize() {
|
|
|
1032
2476
|
camera.updateProjectionMatrix();
|
|
1033
2477
|
renderer.setSize(w, h);
|
|
1034
2478
|
composer.setSize(w, h);
|
|
2479
|
+
if (ssaoPass) { ssaoPass.width = w; ssaoPass.height = h; }
|
|
1035
2480
|
css2dRenderer.setSize(w, h);
|
|
1036
2481
|
}
|
|
1037
2482
|
|
|
@@ -1048,7 +2493,7 @@ export function initForgeScene() {
|
|
|
1048
2493
|
const h = vp.clientHeight;
|
|
1049
2494
|
|
|
1050
2495
|
scene = new THREE.Scene();
|
|
1051
|
-
scene.background = new THREE.Color(
|
|
2496
|
+
scene.background = new THREE.Color(0x030308);
|
|
1052
2497
|
|
|
1053
2498
|
camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 100);
|
|
1054
2499
|
camera.position.set(6, 3, 10);
|
|
@@ -1064,7 +2509,7 @@ export function initForgeScene() {
|
|
|
1064
2509
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
|
|
1065
2510
|
renderer.setSize(w, h);
|
|
1066
2511
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
1067
|
-
renderer.toneMappingExposure =
|
|
2512
|
+
renderer.toneMappingExposure = 0.95;
|
|
1068
2513
|
|
|
1069
2514
|
css2dRenderer = new CSS2DRenderer({ element: document.getElementById("forgeLabelLayer") });
|
|
1070
2515
|
css2dRenderer.setSize(w, h);
|
|
@@ -1079,78 +2524,156 @@ export function initForgeScene() {
|
|
|
1079
2524
|
controls.autoRotate = true;
|
|
1080
2525
|
controls.autoRotateSpeed = 0.5;
|
|
1081
2526
|
|
|
1082
|
-
// ──
|
|
2527
|
+
// ── Rich procedural environment map for PBR reflections ──
|
|
1083
2528
|
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
1084
2529
|
const envScene = new THREE.Scene();
|
|
1085
|
-
envScene.background = new THREE.Color(
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
2530
|
+
envScene.background = new THREE.Color(0x020408);
|
|
2531
|
+
|
|
2532
|
+
// Gradient sky dome — deep dark-blue gradient for chrome contrast
|
|
2533
|
+
const skyGeo = new THREE.SphereGeometry(12, 32, 16);
|
|
2534
|
+
const skyPos = skyGeo.attributes.position;
|
|
2535
|
+
const skyCol = new Float32Array(skyPos.count * 3);
|
|
2536
|
+
const _cTop = new THREE.Color(0x101830);
|
|
2537
|
+
const _cMid = new THREE.Color(0x050c18);
|
|
2538
|
+
const _cBot = new THREE.Color(0x01020a);
|
|
2539
|
+
const _cTmp = new THREE.Color();
|
|
2540
|
+
for (let i = 0; i < skyPos.count; i++) {
|
|
2541
|
+
const ny = skyPos.getY(i) / 12;
|
|
2542
|
+
const t = ny * 0.5 + 0.5;
|
|
2543
|
+
_cTmp.copy(t > 0.5 ? _cTop : _cMid).lerp(t > 0.5 ? _cMid : _cBot, t > 0.5 ? (1 - t) * 2 : (0.5 - t) * 2);
|
|
2544
|
+
skyCol[i * 3] = _cTmp.r; skyCol[i * 3 + 1] = _cTmp.g; skyCol[i * 3 + 2] = _cTmp.b;
|
|
2545
|
+
}
|
|
2546
|
+
skyGeo.setAttribute("color", new THREE.BufferAttribute(skyCol, 3));
|
|
2547
|
+
envScene.add(new THREE.Mesh(skyGeo, new THREE.MeshBasicMaterial({ side: THREE.BackSide, vertexColors: true })));
|
|
2548
|
+
|
|
2549
|
+
// Studio-style reflection panels — multiple for richer reflections
|
|
2550
|
+
const panelGeo = new THREE.PlaneGeometry(7, 12);
|
|
2551
|
+
// Key (right — bright white for strong specular highlight)
|
|
2552
|
+
const brightPanel = new THREE.Mesh(panelGeo, new THREE.MeshBasicMaterial({ color: 0xd8e0f0 }));
|
|
2553
|
+
brightPanel.position.set(8, 4, 0); brightPanel.rotation.y = -Math.PI / 2;
|
|
2554
|
+
envScene.add(brightPanel);
|
|
2555
|
+
// Fill (left — cool blue)
|
|
2556
|
+
const fillPanel = new THREE.Mesh(panelGeo.clone(), new THREE.MeshBasicMaterial({ color: 0x4a6080 }));
|
|
2557
|
+
fillPanel.position.set(-8, 3, 2); fillPanel.rotation.y = Math.PI / 2;
|
|
2558
|
+
envScene.add(fillPanel);
|
|
2559
|
+
// Top softbox
|
|
2560
|
+
const topPanel = new THREE.Mesh(new THREE.PlaneGeometry(10, 10), new THREE.MeshBasicMaterial({ color: 0x8898b0 }));
|
|
2561
|
+
topPanel.position.set(0, 10, 0); topPanel.rotation.x = Math.PI / 2;
|
|
2562
|
+
envScene.add(topPanel);
|
|
2563
|
+
// Rear accent (cool purple backlight)
|
|
2564
|
+
const rearPanel = new THREE.Mesh(new THREE.PlaneGeometry(8, 10), new THREE.MeshBasicMaterial({ color: 0x303050 }));
|
|
2565
|
+
rearPanel.position.set(0, 4, -8);
|
|
2566
|
+
envScene.add(rearPanel);
|
|
2567
|
+
// Front subtle warm (gives depth to front surfaces)
|
|
2568
|
+
const frontPanel = new THREE.Mesh(new THREE.PlaneGeometry(6, 8), new THREE.MeshBasicMaterial({ color: 0x181420 }));
|
|
2569
|
+
frontPanel.position.set(0, 2, 8); frontPanel.rotation.y = Math.PI;
|
|
2570
|
+
envScene.add(frontPanel);
|
|
2571
|
+
// Floor bounce
|
|
2572
|
+
const floorPanel = new THREE.Mesh(new THREE.PlaneGeometry(12, 12), new THREE.MeshBasicMaterial({ color: 0x101420 }));
|
|
2573
|
+
floorPanel.position.set(0, -6, 0); floorPanel.rotation.x = -Math.PI / 2;
|
|
2574
|
+
envScene.add(floorPanel);
|
|
2575
|
+
|
|
2576
|
+
// Env lights — richer multi-source for detailed chrome reflections
|
|
2577
|
+
const envLights = [
|
|
2578
|
+
[0xffffff, 14, [5, 9, 3]],
|
|
2579
|
+
[0x4488ff, 9, [-4, 4, 5]],
|
|
2580
|
+
[0xffffff, 7, [0, 11, 0]],
|
|
2581
|
+
[CYAN, 5, [-6, 5, -4]],
|
|
2582
|
+
[0x6688bb, 3, [3, -3, 6]],
|
|
2583
|
+
[0x9966cc, 3, [-3, 6, -6]],
|
|
2584
|
+
[0xffffff, 4, [6, 2, -4]],
|
|
2585
|
+
];
|
|
2586
|
+
for (const [c, intens, pos] of envLights) {
|
|
2587
|
+
const l = new THREE.PointLight(c, intens, 25);
|
|
2588
|
+
l.position.set(...pos);
|
|
2589
|
+
envScene.add(l);
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
const envMap = pmremGenerator.fromScene(envScene, 0.04).texture;
|
|
1098
2593
|
scene.environment = envMap;
|
|
1099
2594
|
pmremGenerator.dispose();
|
|
1100
2595
|
|
|
1101
|
-
// ──
|
|
1102
|
-
scene.add(new THREE.AmbientLight(
|
|
1103
|
-
const hemi = new THREE.HemisphereLight(
|
|
2596
|
+
// ── Cinematic Lighting (dark studio with chrome highlights) ──
|
|
2597
|
+
scene.add(new THREE.AmbientLight(0x060610, 0.15));
|
|
2598
|
+
const hemi = new THREE.HemisphereLight(0x8899bb, 0x020208, 0.3);
|
|
1104
2599
|
scene.add(hemi);
|
|
1105
2600
|
|
|
1106
|
-
|
|
1107
|
-
keyLight.
|
|
2601
|
+
// Key light — hard white for chrome specular
|
|
2602
|
+
const keyLight = new THREE.DirectionalLight(0xffffff, 3.0);
|
|
2603
|
+
keyLight.position.set(4, 10, 6);
|
|
1108
2604
|
scene.add(keyLight);
|
|
1109
2605
|
|
|
1110
|
-
|
|
1111
|
-
fillLight.
|
|
2606
|
+
// Fill light — cool blue, very dim
|
|
2607
|
+
const fillLight = new THREE.DirectionalLight(0x2244aa, 0.8);
|
|
2608
|
+
fillLight.position.set(-6, 3, 4);
|
|
1112
2609
|
scene.add(fillLight);
|
|
1113
2610
|
|
|
1114
|
-
|
|
1115
|
-
rimLight.
|
|
2611
|
+
// Rim light — strong cold backlight for silhouette pop
|
|
2612
|
+
const rimLight = new THREE.SpotLight(0x4488ff, 8.0, 25, 0.5, 0.4, 1);
|
|
2613
|
+
rimLight.position.set(0, 7, -10);
|
|
2614
|
+
rimLight.target.position.set(0, 3, 0);
|
|
1116
2615
|
scene.add(rimLight);
|
|
2616
|
+
scene.add(rimLight.target);
|
|
1117
2617
|
|
|
1118
|
-
|
|
2618
|
+
// Accent kicker from below-right for chrome underside reflections
|
|
2619
|
+
const kickLight = new THREE.PointLight(0x4466aa, 1.5, 15);
|
|
2620
|
+
kickLight.position.set(4, -1, 3);
|
|
2621
|
+
scene.add(kickLight);
|
|
2622
|
+
|
|
2623
|
+
// Subtle glow from below for dramatic underlight
|
|
2624
|
+
const underGlow = new THREE.PointLight(GLOW, 0.3, 10);
|
|
1119
2625
|
underGlow.position.set(0, -2, 0);
|
|
1120
2626
|
scene.add(underGlow);
|
|
1121
2627
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
const bodySpot = new THREE.SpotLight(0xaec6ff, 1.1, 26, Math.PI * 0.36, 0.45, 1.1);
|
|
1127
|
-
bodySpot.position.set(0, 9.5, 5.5);
|
|
1128
|
-
bodySpot.target.position.set(0, 2.6, 0);
|
|
2628
|
+
// Body spot — focused on torso
|
|
2629
|
+
const bodySpot = new THREE.SpotLight(0xdde8ff, 1.5, 28, Math.PI * 0.35, 0.4, 1.0);
|
|
2630
|
+
bodySpot.position.set(0, 10, 5);
|
|
2631
|
+
bodySpot.target.position.set(0, 3, 0);
|
|
1129
2632
|
scene.add(bodySpot);
|
|
1130
2633
|
scene.add(bodySpot.target);
|
|
1131
2634
|
|
|
1132
|
-
const sideAccent = new THREE.PointLight(0xb026ff, 0.4, 15);
|
|
1133
|
-
sideAccent.position.set(6, 3, -3);
|
|
1134
|
-
scene.add(sideAccent);
|
|
1135
|
-
|
|
1136
2635
|
const sideAccent2 = new THREE.PointLight(CYAN, 0.3, 15);
|
|
1137
2636
|
sideAccent2.position.set(-6, 3, -3);
|
|
1138
2637
|
scene.add(sideAccent2);
|
|
1139
2638
|
|
|
1140
|
-
// Ground
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
);
|
|
2639
|
+
// Ground — large reflective floor with subtle texture
|
|
2640
|
+
const groundGeo = new THREE.PlaneGeometry(80, 80);
|
|
2641
|
+
const groundCanvas = document.createElement("canvas");
|
|
2642
|
+
groundCanvas.width = 512; groundCanvas.height = 512;
|
|
2643
|
+
const gCtx = groundCanvas.getContext("2d");
|
|
2644
|
+
gCtx.fillStyle = "#020204";
|
|
2645
|
+
gCtx.fillRect(0, 0, 512, 512);
|
|
2646
|
+
// Subtle grid lines on the floor
|
|
2647
|
+
gCtx.strokeStyle = "rgba(40,50,80,0.08)";
|
|
2648
|
+
gCtx.lineWidth = 1;
|
|
2649
|
+
for (let i = 0; i <= 512; i += 32) {
|
|
2650
|
+
gCtx.beginPath(); gCtx.moveTo(i, 0); gCtx.lineTo(i, 512); gCtx.stroke();
|
|
2651
|
+
gCtx.beginPath(); gCtx.moveTo(0, i); gCtx.lineTo(512, i); gCtx.stroke();
|
|
2652
|
+
}
|
|
2653
|
+
const groundTex = new THREE.CanvasTexture(groundCanvas);
|
|
2654
|
+
groundTex.wrapS = groundTex.wrapT = THREE.RepeatWrapping;
|
|
2655
|
+
groundTex.repeat.set(8, 8);
|
|
2656
|
+
const groundMat = new P({
|
|
2657
|
+
map: groundTex,
|
|
2658
|
+
color: 0x030306,
|
|
2659
|
+
roughness: 0.22,
|
|
2660
|
+
metalness: 0.55,
|
|
2661
|
+
clearcoat: 0.65,
|
|
2662
|
+
clearcoatRoughness: 0.12,
|
|
2663
|
+
envMapIntensity: 1.2,
|
|
2664
|
+
});
|
|
2665
|
+
const ground = new THREE.Mesh(groundGeo, groundMat);
|
|
1145
2666
|
ground.rotation.x = -Math.PI / 2;
|
|
1146
|
-
ground.position.y = -2.
|
|
2667
|
+
ground.position.y = -2.82;
|
|
1147
2668
|
scene.add(ground);
|
|
1148
2669
|
|
|
2670
|
+
// Fog gradient — subtle depth fade for the far floor edges
|
|
2671
|
+
scene.fog = new THREE.FogExp2(0x020206, 0.018);
|
|
2672
|
+
|
|
1149
2673
|
platformGroup = buildPlatform();
|
|
1150
2674
|
scene.add(platformGroup);
|
|
1151
2675
|
|
|
1152
2676
|
robotGroup = new THREE.Group();
|
|
1153
|
-
robotGroup.add(buildChassis());
|
|
1154
2677
|
scene.add(robotGroup);
|
|
1155
2678
|
|
|
1156
2679
|
attachmentGroup = new THREE.Group();
|
|
@@ -1165,11 +2688,30 @@ export function initForgeScene() {
|
|
|
1165
2688
|
energyStreams = createEnergyStreams();
|
|
1166
2689
|
scene.add(energyStreams);
|
|
1167
2690
|
|
|
1168
|
-
//
|
|
2691
|
+
// Build chassis asynchronously so the browser can paint a frame first
|
|
2692
|
+
const _chassisReady = new Promise(resolve => {
|
|
2693
|
+
requestAnimationFrame(() => {
|
|
2694
|
+
const t0 = performance.now();
|
|
2695
|
+
robotGroup.add(buildChassis());
|
|
2696
|
+
console.log(`[forge] chassis built in ${(performance.now() - t0).toFixed(0)}ms, geo cache: ${_geoCache.size} entries`);
|
|
2697
|
+
resolve();
|
|
2698
|
+
});
|
|
2699
|
+
});
|
|
2700
|
+
|
|
2701
|
+
// ── Post-processing: SSAO → bloom → outline → film grain → output ──
|
|
1169
2702
|
composer = new EffectComposer(renderer);
|
|
1170
2703
|
composer.addPass(new RenderPass(scene, camera));
|
|
1171
2704
|
|
|
1172
|
-
|
|
2705
|
+
try {
|
|
2706
|
+
ssaoPass = new SSAOPass(scene, camera, w, h);
|
|
2707
|
+
ssaoPass.kernelRadius = 12;
|
|
2708
|
+
ssaoPass.minDistance = 0.003;
|
|
2709
|
+
ssaoPass.maxDistance = 0.12;
|
|
2710
|
+
ssaoPass.output = SSAOPass.OUTPUT.Default;
|
|
2711
|
+
composer.addPass(ssaoPass);
|
|
2712
|
+
} catch { /* SSAO unavailable — continue without it */ }
|
|
2713
|
+
|
|
2714
|
+
bloomPass = new UnrealBloomPass(new THREE.Vector2(w, h), 0.7, 0.35, 0.85);
|
|
1173
2715
|
composer.addPass(bloomPass);
|
|
1174
2716
|
|
|
1175
2717
|
outlinePass = new OutlinePass(new THREE.Vector2(w, h), scene, camera);
|
|
@@ -1180,6 +2722,11 @@ export function initForgeScene() {
|
|
|
1180
2722
|
outlinePass.hiddenEdgeColor.set(GLOW);
|
|
1181
2723
|
composer.addPass(outlinePass);
|
|
1182
2724
|
|
|
2725
|
+
filmGrainPass = new ShaderPass(FilmGrainShader);
|
|
2726
|
+
composer.addPass(filmGrainPass);
|
|
2727
|
+
|
|
2728
|
+
composer.addPass(new OutputPass());
|
|
2729
|
+
|
|
1183
2730
|
viewportEl().addEventListener("click", onCanvasClick);
|
|
1184
2731
|
viewportEl().addEventListener("pointermove", onCanvasHover);
|
|
1185
2732
|
viewportEl().addEventListener("pointerleave", () => {
|
|
@@ -1205,8 +2752,13 @@ export function initForgeScene() {
|
|
|
1205
2752
|
setupAutoRotatePause();
|
|
1206
2753
|
animate();
|
|
1207
2754
|
|
|
1208
|
-
|
|
1209
|
-
|
|
2755
|
+
window.__forgeSetSpeaking = setAgentSpeaking;
|
|
2756
|
+
|
|
2757
|
+
_chassisReady.then(() => {
|
|
2758
|
+
playCinematicIntro(() => {
|
|
2759
|
+
window.__forgeSceneReady = true;
|
|
2760
|
+
window.dispatchEvent(new CustomEvent("forge:ready"));
|
|
2761
|
+
});
|
|
1210
2762
|
});
|
|
1211
2763
|
|
|
1212
2764
|
initialized = true;
|