ape-claw 0.1.6 → 0.1.8
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 +157 -8
- package/docs/operator/01-quickstart.md +81 -72
- package/docs/operator/03-cli-reference.md +46 -5
- package/package.json +1 -1
- package/src/cli.mjs +551 -15
- package/src/lib/openclaw-paths.mjs +65 -0
- package/src/server/index.mjs +8 -2
- package/src/server/middleware/auth.mjs +10 -0
- package/src/server/routes/forge-agent.mjs +438 -58
- package/src/server/routes/openclaw-env.mjs +206 -0
- package/src/server/routes/skills.mjs +80 -3
- package/ui/forge/css/forge.css +153 -2
- package/ui/forge/index.html +40 -6
- package/ui/forge/js/forge-attachments.js +616 -377
- package/ui/forge/js/forge-chat.js +201 -90
- package/ui/forge/js/forge-data.js +305 -11
- package/ui/forge/js/forge-scene.js +178 -18
- package/ui/index.html +3 -3
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* forge-attachments.js —
|
|
3
|
-
*
|
|
2
|
+
* forge-attachments.js — 200-slot body-mounted hardpoint system
|
|
3
|
+
* with hyperrealistic components. Every slot is welded to a specific
|
|
4
|
+
* anatomical zone of the robot — no floating parts.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Robot anatomy (Y axis):
|
|
7
|
+
* Crown: Y 7.5 Feet: Y -2.6
|
|
8
|
+
* Head: Y 6.2–7.2 Shins: Y -0.8 – -1.8
|
|
9
|
+
* Neck: Y 5.8–6.2 Knees: Y -0.6
|
|
10
|
+
* Shoulders: Y 5.0–5.6 Thighs: Y 0.0 – 0.8
|
|
11
|
+
* Chest: Y 3.2–5.2 Waist: Y 1.2 – 1.8
|
|
12
|
+
* Back: Y 2.0–5.2 Hips: Y 0.2 – 1.0
|
|
13
|
+
* Arms: X ±2.0–2.4 Calves: Y -1.0 – -2.2
|
|
14
|
+
*
|
|
15
|
+
* Region → primaryCap mapping (sums to 200):
|
|
16
|
+
* Security 20 · Analytics 24 · Automation 16 · DevTools 16
|
|
17
|
+
* NFT 14 · Social 12 · Storage 12 · Productivity 12
|
|
18
|
+
* Bridge 10 · Trading 10 · Governance 10 · Wallet 10
|
|
19
|
+
* DeFi 10 · Development 12 · Writing 10 · Communication 12
|
|
8
20
|
*/
|
|
9
21
|
import * as THREE from "three";
|
|
10
22
|
import { RoundedBoxGeometry } from "three/addons/geometries/RoundedBoxGeometry.js";
|
|
11
23
|
import { MAT } from "./forge-scene.js";
|
|
12
24
|
|
|
13
|
-
/*
|
|
14
|
-
Category
|
|
15
|
-
|
|
25
|
+
/* ─────────────────────────────────────────────────────────────
|
|
26
|
+
Category colour palette
|
|
27
|
+
───────────────────────────────────────────────────────────── */
|
|
16
28
|
export const CATEGORY_COLORS = {
|
|
17
29
|
Security: "#FFB347",
|
|
18
30
|
Analytics: "#63d7ff",
|
|
19
|
-
Automation: "#
|
|
31
|
+
Automation: "#00ff88",
|
|
20
32
|
DevTools: "#4169E1",
|
|
21
33
|
NFT: "#b026ff",
|
|
22
34
|
Social: "#FF7F50",
|
|
@@ -27,7 +39,7 @@ export const CATEGORY_COLORS = {
|
|
|
27
39
|
Governance: "#4B0082",
|
|
28
40
|
Wallet: "#50C878",
|
|
29
41
|
DeFi: "#32CD32",
|
|
30
|
-
Development: "#
|
|
42
|
+
Development: "#4488FF",
|
|
31
43
|
Writing: "#FFF8DC",
|
|
32
44
|
Communication: "#00BFFF",
|
|
33
45
|
};
|
|
@@ -40,350 +52,616 @@ for (const [k, v] of Object.entries(CATEGORY_COLORS)) {
|
|
|
40
52
|
function matFor(cat) {
|
|
41
53
|
const key = cat.toLowerCase();
|
|
42
54
|
const mat = MAT[key] ? MAT[key].clone() : MAT.armor.clone();
|
|
43
|
-
if (typeof mat.emissiveIntensity === "number")
|
|
44
|
-
mat.emissiveIntensity *= 0.72;
|
|
45
|
-
}
|
|
55
|
+
if (typeof mat.emissiveIntensity === "number") mat.emissiveIntensity *= 0.72;
|
|
46
56
|
return mat;
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
/*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
center.y + yOff,
|
|
63
|
-
center.z + Math.sin(angle) * r,
|
|
64
|
-
);
|
|
59
|
+
/* ─────────────────────────────────────────────────────────────
|
|
60
|
+
Shared geometry constants
|
|
61
|
+
───────────────────────────────────────────────────────────── */
|
|
62
|
+
const FRAME_C = 0x1a2230;
|
|
63
|
+
const RIVET_C = 0xd4dce8;
|
|
64
|
+
const HEAT_C = 0xff4400;
|
|
65
|
+
const PIPE_C = 0x2a3545;
|
|
66
|
+
|
|
67
|
+
function rMat(extra = {}) {
|
|
68
|
+
return new THREE.MeshPhysicalMaterial({
|
|
69
|
+
color: RIVET_C, metalness: 0.96, roughness: 0.05,
|
|
70
|
+
clearcoat: 1.0, clearcoatRoughness: 0.0, ...extra,
|
|
71
|
+
});
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Analytics: { center: [0, 7.2, 0], primaryCap: 20, overflowR: 1.0, vSpread: 1.5 },
|
|
74
|
-
Automation: { center: [-2.1, 3.0, 0], primaryCap: 12, overflowR: 1.2, vSpread: 2.5 },
|
|
75
|
-
DevTools: { center: [2.1, 3.0, 0], primaryCap: 12, overflowR: 1.2, vSpread: 2.5 },
|
|
76
|
-
NFT: { center: [2.3, 5.5, -0.3], primaryCap: 10, overflowR: 1.1, vSpread: 2.0 },
|
|
77
|
-
Social: { center: [-2.1, 2.0, 0.2], primaryCap: 8, overflowR: 1.0, vSpread: 1.8 },
|
|
78
|
-
Storage: { center: [0, 3.0, -0.9], primaryCap: 8, overflowR: 1.0, vSpread: 1.5 },
|
|
79
|
-
Productivity: { center: [0, 4.5, 0.78], primaryCap: 9, overflowR: 0.9, vSpread: 1.5 },
|
|
80
|
-
Bridge: { center: [-2.0, 5.5, -0.2],primaryCap: 6, overflowR: 1.0, vSpread: 2.0 },
|
|
81
|
-
Trading: { center: [-0.55, -0.8, 0.4],primaryCap: 6, overflowR: 1.0, vSpread: 2.0 },
|
|
82
|
-
Governance: { center: [0, 6.7, 0.4], primaryCap: 6, overflowR: 0.8, vSpread: 1.2 },
|
|
83
|
-
Wallet: { center: [0, 2.35, 0.55], primaryCap: 6, overflowR: 0.8, vSpread: 1.5 },
|
|
84
|
-
DeFi: { center: [0.55, -0.8, 0.4],primaryCap: 6, overflowR: 1.0, vSpread: 2.0 },
|
|
85
|
-
Development: { center: [2.1, 4.0, 0.2], primaryCap: 8, overflowR: 1.0, vSpread: 2.0 },
|
|
86
|
-
Writing: { center: [2.1, 2.0, 0.2], primaryCap: 6, overflowR: 0.9, vSpread: 1.5 },
|
|
87
|
-
Communication: { center: [0, 7.5, 0], primaryCap: 6, overflowR: 0.8, vSpread: 1.2 },
|
|
88
|
-
};
|
|
74
|
+
function frameMat(base) {
|
|
75
|
+
const m = base.clone();
|
|
76
|
+
m.color = new THREE.Color(FRAME_C);
|
|
77
|
+
m.emissiveIntensity = (m.emissiveIntensity || 0) * 0.3;
|
|
78
|
+
return m;
|
|
79
|
+
}
|
|
89
80
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
function glowMat(base, mult = 2.5) {
|
|
82
|
+
const m = base.clone();
|
|
83
|
+
m.emissiveIntensity = (m.emissiveIntensity || 0.5) * mult;
|
|
84
|
+
m.transparent = true;
|
|
85
|
+
m.opacity = 0.65;
|
|
86
|
+
return m;
|
|
87
|
+
}
|
|
96
88
|
|
|
89
|
+
/* ─────────────────────────────────────────────────────────────
|
|
90
|
+
■ SHAPE LIBRARY (11 hyperrealistic components)
|
|
91
|
+
───────────────────────────────────────────────────────────── */
|
|
92
|
+
|
|
93
|
+
/** Bevelled armour slab with frame rails, glow stripe, and rivets */
|
|
97
94
|
function armorPlate(mat, w, h, d) {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const bodyGeo =
|
|
101
|
-
? new RoundedBoxGeometry(w, h, d, 2,
|
|
95
|
+
const g = new THREE.Group();
|
|
96
|
+
const minD = Math.min(w, h, d);
|
|
97
|
+
const bodyGeo = minD > 0.08
|
|
98
|
+
? new RoundedBoxGeometry(w, h, d, 2, minD * 0.13)
|
|
102
99
|
: new THREE.BoxGeometry(w, h, d);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const rv = new THREE.Mesh(rGeo, rMat);
|
|
121
|
-
rv.position.set(rx * w * 0.4, ry * h * 0.35, d / 2 + 0.01);
|
|
122
|
-
group.add(rv);
|
|
100
|
+
g.add(new THREE.Mesh(bodyGeo, mat));
|
|
101
|
+
|
|
102
|
+
const fm = frameMat(mat);
|
|
103
|
+
const bw = 0.014;
|
|
104
|
+
g.add(new THREE.Mesh(new THREE.BoxGeometry(w + bw * 2, bw, d + bw), fm).translateY( h / 2));
|
|
105
|
+
g.add(new THREE.Mesh(new THREE.BoxGeometry(w + bw * 2, bw, d + bw), fm).translateY(-h / 2));
|
|
106
|
+
g.add(new THREE.Mesh(new THREE.BoxGeometry(bw, h, d + bw), fm).translateX( w / 2));
|
|
107
|
+
g.add(new THREE.Mesh(new THREE.BoxGeometry(bw, h, d + bw), fm).translateX(-w / 2));
|
|
108
|
+
|
|
109
|
+
const gm = glowMat(mat, 2.8);
|
|
110
|
+
g.add(new THREE.Mesh(new THREE.BoxGeometry(w * 0.55, 0.013, d * 0.28), gm).translateZ(d / 2 + 0.005));
|
|
111
|
+
|
|
112
|
+
const rv = new THREE.Mesh(new THREE.SphereGeometry(0.016, 8, 6), rMat());
|
|
113
|
+
for (const [rx, ry] of [[-1,-1],[1,-1],[-1,1],[1,1]]) {
|
|
114
|
+
const r2 = rv.clone();
|
|
115
|
+
r2.position.set(rx * w * 0.38, ry * h * 0.33, d / 2 + 0.009);
|
|
116
|
+
g.add(r2);
|
|
123
117
|
}
|
|
124
|
-
return
|
|
118
|
+
return g;
|
|
125
119
|
}
|
|
126
120
|
|
|
121
|
+
/** Hexagonal tech plate with inner glow disc and apex sensor node */
|
|
127
122
|
function hexPlate(mat, r, h) {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
edge.rotation.x = Math.PI / 2; edge.position.y = h / 2 + 0.005;
|
|
143
|
-
group.add(edge);
|
|
144
|
-
return group;
|
|
123
|
+
const g = new THREE.Group();
|
|
124
|
+
g.add(new THREE.Mesh(new THREE.CylinderGeometry(r, r, h, 6), mat));
|
|
125
|
+
const inner = new THREE.Mesh(
|
|
126
|
+
new THREE.CylinderGeometry(r * 0.52, r * 0.52, h + 0.01, 6),
|
|
127
|
+
glowMat(mat, 1.9),
|
|
128
|
+
);
|
|
129
|
+
g.add(inner);
|
|
130
|
+
const dot = new THREE.Mesh(new THREE.SphereGeometry(r * 0.19, 14, 9), glowMat(mat, 3.2));
|
|
131
|
+
dot.translateY(h / 2 + 0.01);
|
|
132
|
+
g.add(dot);
|
|
133
|
+
const edge = new THREE.Mesh(new THREE.TorusGeometry(r, r * 0.055, 10, 6), glowMat(mat, 2.1));
|
|
134
|
+
edge.rotation.x = Math.PI / 2; edge.position.y = h / 2 + 0.004;
|
|
135
|
+
g.add(edge);
|
|
136
|
+
return g;
|
|
145
137
|
}
|
|
146
138
|
|
|
139
|
+
/** Spinning energy core — octahedron + dual orbiting rings + bracket */
|
|
147
140
|
function energyModule(mat, r) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
group.add(ring);
|
|
159
|
-
const ring2 = new THREE.Mesh(new THREE.TorusGeometry(r * 0.8, r * 0.06, 10, 24), mat.clone());
|
|
160
|
-
ring2.material.transparent = true; ring2.material.opacity = 0.4;
|
|
161
|
-
ring2.rotation.z = Math.PI / 2;
|
|
162
|
-
group.add(ring2);
|
|
163
|
-
const bMat = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.6, roughness: 0.4, clearcoat: 0.3, clearcoatRoughness: 0.4 });
|
|
164
|
-
group.add(new THREE.Mesh(new THREE.BoxGeometry(0.03, r * 1.2, 0.03), bMat).translateX(-r * 0.9));
|
|
165
|
-
return group;
|
|
141
|
+
const g = new THREE.Group();
|
|
142
|
+
g.add(new THREE.Mesh(new THREE.OctahedronGeometry(r * 0.62, 1), mat));
|
|
143
|
+
g.add(new THREE.Mesh(new THREE.SphereGeometry(r * 0.28, 16, 12), glowMat(mat, 3.5)));
|
|
144
|
+
const ring1 = new THREE.Mesh(new THREE.TorusGeometry(r, r * 0.1, 12, 32), glowMat(mat, 1.6));
|
|
145
|
+
ring1.rotation.x = Math.PI / 2; g.add(ring1);
|
|
146
|
+
const ring2 = new THREE.Mesh(new THREE.TorusGeometry(r * 0.78, r * 0.06, 10, 24), glowMat(mat, 1.3));
|
|
147
|
+
ring2.rotation.z = Math.PI / 2; g.add(ring2);
|
|
148
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.65, roughness: 0.35, clearcoat: 0.4 });
|
|
149
|
+
g.add(new THREE.Mesh(new THREE.BoxGeometry(0.028, r * 1.25, 0.028), bm).translateX(-r * 0.88));
|
|
150
|
+
return g;
|
|
166
151
|
}
|
|
167
152
|
|
|
153
|
+
/** Swept blade fin with edge-glow wires and mounting pin */
|
|
168
154
|
function bladeFin(mat, h, w) {
|
|
169
|
-
const
|
|
155
|
+
const g = new THREE.Group();
|
|
170
156
|
const shape = new THREE.Shape();
|
|
171
157
|
shape.moveTo(0, h / 2);
|
|
158
|
+
shape.bezierCurveTo(w * 0.6, h * 0.1, w * 0.7, -h * 0.3, w / 2, -h / 2);
|
|
172
159
|
shape.lineTo(-w / 2, -h / 2);
|
|
173
|
-
shape.
|
|
160
|
+
shape.bezierCurveTo(-w * 0.1, -h * 0.1, 0, h * 0.2, 0, h / 2);
|
|
174
161
|
shape.closePath();
|
|
175
|
-
const geo = new THREE.ExtrudeGeometry(shape, { depth: 0.
|
|
176
|
-
|
|
177
|
-
// Edge glow trim
|
|
178
|
-
const eMat = mat.clone();
|
|
179
|
-
eMat.emissiveIntensity = (eMat.emissiveIntensity || 0.5) * 3;
|
|
180
|
-
eMat.transparent = true; eMat.opacity = 0.6;
|
|
162
|
+
const geo = new THREE.ExtrudeGeometry(shape, { depth: 0.038, bevelEnabled: true, bevelSize: 0.008, bevelThickness: 0.008, bevelSegments: 2 });
|
|
163
|
+
g.add(new THREE.Mesh(geo, mat));
|
|
181
164
|
const pts = [
|
|
182
|
-
new THREE.Vector3(0, h / 2, 0.
|
|
183
|
-
new THREE.Vector3(
|
|
184
|
-
new THREE.Vector3(w / 2, -h / 2, 0.
|
|
185
|
-
new THREE.Vector3(0, h / 2, 0.02),
|
|
165
|
+
new THREE.Vector3(0, h / 2, 0.022),
|
|
166
|
+
new THREE.Vector3(w * 0.45, -h / 4, 0.022),
|
|
167
|
+
new THREE.Vector3(w / 2, -h / 2, 0.022),
|
|
186
168
|
];
|
|
187
169
|
const lineGeo = new THREE.BufferGeometry().setFromPoints(pts);
|
|
188
|
-
|
|
170
|
+
g.add(new THREE.Line(lineGeo, new THREE.LineBasicMaterial({
|
|
189
171
|
color: mat.emissive?.getHex?.() || 0xffffff,
|
|
190
|
-
transparent: true, opacity: 0.
|
|
191
|
-
blending: THREE.AdditiveBlending,
|
|
172
|
+
transparent: true, opacity: 0.7, blending: THREE.AdditiveBlending,
|
|
192
173
|
})));
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
return
|
|
174
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.65, roughness: 0.38 });
|
|
175
|
+
g.add(new THREE.Mesh(new THREE.CylinderGeometry(0.024, 0.038, 0.055, 8), bm).translateY(-h / 2 - 0.028));
|
|
176
|
+
return g;
|
|
196
177
|
}
|
|
197
178
|
|
|
179
|
+
/** Shoulder/turret gun barrel with housing ring and muzzle glow */
|
|
198
180
|
function turretModule(mat, r) {
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return
|
|
181
|
+
const g = new THREE.Group();
|
|
182
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.72, roughness: 0.28, clearcoat: 0.5 });
|
|
183
|
+
const barrel = new THREE.Mesh(new THREE.CylinderGeometry(r * 0.17, r * 0.21, r * 2.6, 12), bm);
|
|
184
|
+
barrel.rotation.x = Math.PI / 2; barrel.position.y = r * 0.55;
|
|
185
|
+
g.add(barrel);
|
|
186
|
+
g.add(new THREE.Mesh(new THREE.CylinderGeometry(r * 0.62, r * 0.72, r * 0.38, 12), mat));
|
|
187
|
+
const ring = new THREE.Mesh(new THREE.TorusGeometry(r * 0.66, r * 0.055, 10, 24), glowMat(mat, 2.4));
|
|
188
|
+
ring.rotation.x = Math.PI / 2; g.add(ring);
|
|
189
|
+
const muzzle = new THREE.Mesh(new THREE.TorusGeometry(r * 0.15, r * 0.04, 8, 16), glowMat(mat, 4.0));
|
|
190
|
+
muzzle.rotation.x = Math.PI / 2; muzzle.position.set(0, r * 0.55, r * 1.35); g.add(muzzle);
|
|
191
|
+
return g;
|
|
210
192
|
}
|
|
211
193
|
|
|
194
|
+
/** Hemisphere sensor dome with rotating inner ring and antenna spike */
|
|
212
195
|
function sensorDome(mat, r) {
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
196
|
+
const g = new THREE.Group();
|
|
197
|
+
g.add(new THREE.Mesh(new THREE.SphereGeometry(r, 24, 16, 0, Math.PI * 2, 0, Math.PI / 2), mat));
|
|
198
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.62, roughness: 0.42, clearcoat: 0.35 });
|
|
199
|
+
g.add(new THREE.Mesh(new THREE.CylinderGeometry(r * 1.08, r * 1.08, r * 0.14, 16), bm));
|
|
200
|
+
const spike = new THREE.Mesh(new THREE.ConeGeometry(r * 0.07, r * 1.3, 8), mat.clone());
|
|
201
|
+
spike.translateY(r * 0.9); g.add(spike);
|
|
202
|
+
const scanRing = new THREE.Mesh(new THREE.TorusGeometry(r * 0.68, r * 0.035, 10, 24), glowMat(mat, 2.8));
|
|
203
|
+
scanRing.rotation.x = Math.PI / 2; scanRing.position.y = r * 0.3; g.add(scanRing);
|
|
204
|
+
return g;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Cluster of three missile/rocket tubes with blast-guard plate */
|
|
208
|
+
function missilePod(mat, r) {
|
|
209
|
+
const g = new THREE.Group();
|
|
210
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: PIPE_C, metalness: 0.75, roughness: 0.25, clearcoat: 0.6 });
|
|
211
|
+
const capMat = rMat({ color: HEAT_C, emissive: new THREE.Color(HEAT_C), emissiveIntensity: 1.2 });
|
|
212
|
+
const offsets = [[0, r * 0.55], [-r * 0.5, -r * 0.28], [r * 0.5, -r * 0.28]];
|
|
213
|
+
for (const [ox, oy] of offsets) {
|
|
214
|
+
const tube = new THREE.Mesh(new THREE.CylinderGeometry(r * 0.22, r * 0.22, r * 1.8, 8), bm);
|
|
215
|
+
tube.position.set(ox, oy, 0); g.add(tube);
|
|
216
|
+
const cap = new THREE.Mesh(new THREE.CircleGeometry(r * 0.18, 8), capMat);
|
|
217
|
+
cap.position.set(ox, oy - r * 0.9, r * 0.24); cap.rotation.x = Math.PI / 2; g.add(cap);
|
|
218
|
+
}
|
|
219
|
+
const plate = new THREE.Mesh(new THREE.BoxGeometry(r * 1.6, r * 1.6, r * 0.22), mat);
|
|
220
|
+
plate.position.z = -r * 0.12; g.add(plate);
|
|
221
|
+
const ring = new THREE.Mesh(new THREE.TorusGeometry(r * 0.65, r * 0.04, 8, 18), glowMat(mat, 2.0));
|
|
222
|
+
ring.rotation.x = Math.PI / 2; g.add(ring);
|
|
223
|
+
return g;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Exhaust thruster nozzle with heat shield ribs and afterburner glow */
|
|
227
|
+
function thrusterNozzle(mat, r) {
|
|
228
|
+
const g = new THREE.Group();
|
|
229
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.8, roughness: 0.2, clearcoat: 0.7 });
|
|
230
|
+
g.add(new THREE.Mesh(new THREE.CylinderGeometry(r * 0.55, r * 0.9, r * 1.1, 16), bm));
|
|
231
|
+
const heatMat = new THREE.MeshPhysicalMaterial({
|
|
232
|
+
color: HEAT_C, emissive: new THREE.Color(0xff6600),
|
|
233
|
+
emissiveIntensity: 2.0, transparent: true, opacity: 0.55, metalness: 0.4, roughness: 0.6,
|
|
234
|
+
});
|
|
235
|
+
const nozzleInner = new THREE.Mesh(new THREE.CylinderGeometry(r * 0.42, r * 0.82, r * 1.15, 16), heatMat);
|
|
236
|
+
g.add(nozzleInner);
|
|
237
|
+
for (let i = 0; i < 6; i++) {
|
|
238
|
+
const rib = new THREE.Mesh(new THREE.BoxGeometry(r * 0.06, r * 1.15, r * 0.12), bm);
|
|
239
|
+
rib.rotation.y = (i / 6) * Math.PI * 2;
|
|
240
|
+
rib.position.set(Math.cos((i / 6) * Math.PI * 2) * r * 0.72, 0, Math.sin((i / 6) * Math.PI * 2) * r * 0.72);
|
|
241
|
+
g.add(rib);
|
|
242
|
+
}
|
|
243
|
+
const glow = new THREE.Mesh(new THREE.CircleGeometry(r * 0.45, 16), glowMat(mat, 5.0));
|
|
244
|
+
glow.position.y = -r * 0.56; glow.rotation.x = Math.PI / 2; g.add(glow);
|
|
245
|
+
return g;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Data uplink spike — tapered rod with three orbiting signal rings */
|
|
249
|
+
function dataSpike(mat, h) {
|
|
250
|
+
const g = new THREE.Group();
|
|
251
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.78, roughness: 0.22, clearcoat: 0.8 });
|
|
252
|
+
g.add(new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.06, h, 8), bm));
|
|
253
|
+
g.add(new THREE.Mesh(new THREE.ConeGeometry(0.04, h * 0.28, 8), mat).translateY(h * 0.64));
|
|
254
|
+
const ringOffsets = [0.28, 0.52, 0.72];
|
|
255
|
+
for (const t of ringOffsets) {
|
|
256
|
+
const nr = 0.11 + t * 0.06;
|
|
257
|
+
const ring = new THREE.Mesh(new THREE.TorusGeometry(nr, 0.013, 7, 20), glowMat(mat, 3.0 - t));
|
|
258
|
+
ring.rotation.x = Math.PI / 2; ring.position.y = h * (t - 0.5); g.add(ring);
|
|
259
|
+
}
|
|
260
|
+
const base = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 0.045, 10), mat);
|
|
261
|
+
base.translateY(-h * 0.5 - 0.022); g.add(base);
|
|
262
|
+
return g;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Glowing reactor core — icosahedron with equatorial ring array */
|
|
266
|
+
function reactorCore(mat, r) {
|
|
267
|
+
const g = new THREE.Group();
|
|
268
|
+
const coreMat = glowMat(mat, 3.8);
|
|
269
|
+
coreMat.opacity = 0.9;
|
|
270
|
+
g.add(new THREE.Mesh(new THREE.IcosahedronGeometry(r * 0.58, 1), coreMat));
|
|
271
|
+
const shell = mat.clone(); shell.transparent = true; shell.opacity = 0.18; shell.side = THREE.BackSide;
|
|
272
|
+
g.add(new THREE.Mesh(new THREE.IcosahedronGeometry(r * 0.82, 1), shell));
|
|
273
|
+
for (let i = 0; i < 3; i++) {
|
|
274
|
+
const angle = (i / 3) * Math.PI;
|
|
275
|
+
const orb = new THREE.Mesh(new THREE.TorusGeometry(r, r * 0.07, 9, 28), glowMat(mat, 2.0 + i * 0.4));
|
|
276
|
+
orb.rotation.set(angle, angle * 0.7, 0); orb.transparent = true; orb.opacity = 0.5; g.add(orb);
|
|
277
|
+
}
|
|
278
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.7, roughness: 0.3 });
|
|
279
|
+
for (let i = 0; i < 4; i++) {
|
|
280
|
+
const a = (i / 4) * Math.PI * 2;
|
|
281
|
+
const strut = new THREE.Mesh(new THREE.CylinderGeometry(0.018, 0.018, r * 0.75, 6), bm);
|
|
282
|
+
strut.position.set(Math.cos(a) * r * 0.88, 0, Math.sin(a) * r * 0.88);
|
|
283
|
+
strut.rotation.z = -Math.PI / 2; strut.rotation.y = a; g.add(strut);
|
|
284
|
+
}
|
|
285
|
+
return g;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Articulated shoulder cannon with scope housing and charging rune */
|
|
289
|
+
function shoulderCannon(mat, scale = 1.0) {
|
|
290
|
+
const s = scale;
|
|
291
|
+
const g = new THREE.Group();
|
|
292
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.78, roughness: 0.22, clearcoat: 0.6 });
|
|
293
|
+
const housing = new THREE.Mesh(new RoundedBoxGeometry(0.55 * s, 0.3 * s, 0.4 * s, 2, 0.04 * s), mat);
|
|
294
|
+
g.add(housing);
|
|
295
|
+
const barrel = new THREE.Mesh(new THREE.CylinderGeometry(0.065 * s, 0.08 * s, 0.9 * s, 12), bm);
|
|
296
|
+
barrel.rotation.x = Math.PI / 2; barrel.position.set(0, 0.05 * s, 0.65 * s); g.add(barrel);
|
|
297
|
+
const scope = new THREE.Mesh(new THREE.CylinderGeometry(0.04 * s, 0.04 * s, 0.35 * s, 8), bm);
|
|
298
|
+
scope.rotation.x = Math.PI / 2; scope.position.set(0, 0.2 * s, 0.4 * s); g.add(scope);
|
|
299
|
+
const muzzle = new THREE.Mesh(new THREE.TorusGeometry(0.055 * s, 0.016 * s, 8, 16), glowMat(mat, 4.5));
|
|
300
|
+
muzzle.rotation.x = Math.PI / 2; muzzle.position.set(0, 0.05 * s, 1.12 * s); g.add(muzzle);
|
|
301
|
+
const rune = new THREE.Mesh(new THREE.TorusGeometry(0.18 * s, 0.012 * s, 7, 18), glowMat(mat, 3.0));
|
|
302
|
+
rune.rotation.x = Math.PI / 2; rune.position.set(0, 0.05 * s, 0.22 * s); g.add(rune);
|
|
303
|
+
return g;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Knee/joint guard — curved convex plate with sensor cluster grid */
|
|
307
|
+
function kneeGuard(mat, w, h) {
|
|
308
|
+
const g = new THREE.Group();
|
|
309
|
+
const shape = new THREE.Shape();
|
|
310
|
+
shape.moveTo(-w / 2, -h / 2);
|
|
311
|
+
shape.bezierCurveTo(-w / 2, h * 0.1, 0, h * 0.6, 0, h / 2);
|
|
312
|
+
shape.bezierCurveTo(0, h * 0.6, w / 2, h * 0.1, w / 2, -h / 2);
|
|
313
|
+
shape.lineTo(-w / 2, -h / 2);
|
|
314
|
+
const geo = new THREE.ExtrudeGeometry(shape, { depth: 0.14, bevelEnabled: true, bevelSize: 0.02, bevelThickness: 0.02, bevelSegments: 3 });
|
|
315
|
+
g.add(new THREE.Mesh(geo, mat));
|
|
316
|
+
const fm = frameMat(mat);
|
|
317
|
+
const ridge = new THREE.Mesh(new THREE.BoxGeometry(w * 0.85, 0.018, 0.16), fm);
|
|
318
|
+
ridge.position.set(0, 0, 0.16); g.add(ridge);
|
|
319
|
+
const dotGeo = new THREE.CircleGeometry(0.022, 8);
|
|
320
|
+
const dotMat = glowMat(mat, 3.5);
|
|
321
|
+
for (let i = -1; i <= 1; i++) {
|
|
322
|
+
const d = new THREE.Mesh(dotGeo, dotMat.clone());
|
|
323
|
+
d.position.set(i * w * 0.22, h * 0.05, 0.165); d.rotation.x = -Math.PI / 12; g.add(d);
|
|
324
|
+
}
|
|
325
|
+
return g;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Boot/foot anchor — thick platform plate with magnetic claw pads */
|
|
329
|
+
function footAnchor(mat, w, h) {
|
|
330
|
+
const g = new THREE.Group();
|
|
331
|
+
const bm = new THREE.MeshPhysicalMaterial({ color: FRAME_C, metalness: 0.82, roughness: 0.18, clearcoat: 0.7 });
|
|
332
|
+
g.add(new THREE.Mesh(new RoundedBoxGeometry(w, h * 0.22, w * 0.65, 2, 0.035), mat));
|
|
333
|
+
const toe = new THREE.Mesh(new RoundedBoxGeometry(w * 0.72, h * 0.15, w * 0.35, 2, 0.025), bm);
|
|
334
|
+
toe.position.set(0, -h * 0.035, w * 0.42); g.add(toe);
|
|
335
|
+
const clawMat = rMat({ color: 0x1a2230 });
|
|
336
|
+
const cGeo = new THREE.SphereGeometry(0.045, 8, 6);
|
|
337
|
+
for (const cx of [-w * 0.3, 0, w * 0.3]) {
|
|
338
|
+
const claw = new THREE.Mesh(cGeo, clawMat);
|
|
339
|
+
claw.position.set(cx, -h * 0.12, w * 0.36); g.add(claw);
|
|
340
|
+
}
|
|
341
|
+
const glow = new THREE.Mesh(new THREE.PlaneGeometry(w * 0.55, w * 0.32), glowMat(mat, 2.2));
|
|
342
|
+
glow.position.set(0, -h * 0.1, w * 0.38); glow.rotation.x = -Math.PI / 2; g.add(glow);
|
|
343
|
+
return g;
|
|
225
344
|
}
|
|
226
345
|
|
|
346
|
+
/* ─────────────────────────────────────────────────────────────
|
|
347
|
+
Region config — centre anchors + primaryCap = 200 total
|
|
348
|
+
───────────────────────────────────────────────────────────── */
|
|
349
|
+
const REGIONS = {
|
|
350
|
+
Security: { center: [0, 3.5, -0.92], primaryCap: 20, overflowR: 1.2, vSpread: 2.5 },
|
|
351
|
+
Analytics: { center: [0, 7.1, 0.0 ], primaryCap: 24, overflowR: 0.9, vSpread: 1.2 },
|
|
352
|
+
Automation: { center: [-2.1, 3.0, 0.0 ], primaryCap: 16, overflowR: 1.0, vSpread: 2.8 },
|
|
353
|
+
DevTools: { center: [ 2.1, 3.0, 0.0 ], primaryCap: 16, overflowR: 1.0, vSpread: 2.8 },
|
|
354
|
+
NFT: { center: [ 2.3, 5.2, -0.2 ], primaryCap: 14, overflowR: 1.0, vSpread: 1.8 },
|
|
355
|
+
Social: { center: [-2.3, 5.2, -0.2 ], primaryCap: 12, overflowR: 1.0, vSpread: 1.8 },
|
|
356
|
+
Storage: { center: [0, 1.5, -0.78], primaryCap: 12, overflowR: 1.0, vSpread: 1.5 },
|
|
357
|
+
Productivity: { center: [0, 4.2, 0.88], primaryCap: 12, overflowR: 0.9, vSpread: 2.0 },
|
|
358
|
+
Bridge: { center: [-1.2, 5.6, 0.55], primaryCap: 10, overflowR: 0.9, vSpread: 1.4 },
|
|
359
|
+
Trading: { center: [ 1.2, 5.6, 0.55], primaryCap: 10, overflowR: 0.9, vSpread: 1.4 },
|
|
360
|
+
Governance: { center: [0, 6.7, 0.35], primaryCap: 10, overflowR: 0.8, vSpread: 1.0 },
|
|
361
|
+
Wallet: { center: [0, 1.2, 0.62], primaryCap: 10, overflowR: 0.8, vSpread: 1.2 },
|
|
362
|
+
DeFi: { center: [0, -0.6, 0.45], primaryCap: 10, overflowR: 0.9, vSpread: 2.0 },
|
|
363
|
+
Development: { center: [ 2.1, 4.5, 0.22], primaryCap: 12, overflowR: 1.0, vSpread: 2.0 },
|
|
364
|
+
Writing: { center: [-2.1, 2.2, 0.0 ], primaryCap: 10, overflowR: 0.9, vSpread: 1.8 },
|
|
365
|
+
Communication: { center: [0, 7.6, 0.0 ], primaryCap: 12, overflowR: 0.8, vSpread: 1.0 },
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/* ─────────────────────────────────────────────────────────────
|
|
369
|
+
PRIMARY placement builders — body-welded hardpoints
|
|
370
|
+
───────────────────────────────────────────────────────────── */
|
|
227
371
|
const PRIMARY = {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
mesh
|
|
372
|
+
|
|
373
|
+
/* ── SECURITY (20) ── back torso armour grid 5×4 */
|
|
374
|
+
Security(mat, idx) {
|
|
375
|
+
const col = idx % 4;
|
|
376
|
+
const row = Math.floor(idx / 4);
|
|
377
|
+
let mesh;
|
|
378
|
+
if (idx % 5 === 0) mesh = turretModule(mat, 0.13);
|
|
379
|
+
else if (idx % 7 === 0) mesh = sensorDome(mat, 0.1);
|
|
380
|
+
else mesh = armorPlate(mat, 0.52, 0.22, 0.07);
|
|
381
|
+
mesh.position.set(-0.42 + col * 0.28, 2.0 + row * 0.82, -0.92);
|
|
234
382
|
return mesh;
|
|
235
383
|
},
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
const
|
|
243
|
-
|
|
384
|
+
|
|
385
|
+
/* ── ANALYTICS (24) ── crown halo — three concentric rings */
|
|
386
|
+
Analytics(mat, idx) {
|
|
387
|
+
const ringIdx = Math.floor(idx / 8);
|
|
388
|
+
const posInRing = idx % 8;
|
|
389
|
+
const ringCount = 8;
|
|
390
|
+
const angle = (posInRing / ringCount) * Math.PI * 2;
|
|
391
|
+
const radii = [0.38, 0.62, 0.9];
|
|
392
|
+
const yOffs = [0.0, 0.22, 0.48];
|
|
393
|
+
const r = radii[ringIdx] || 0.9;
|
|
394
|
+
const y = 7.1 + (yOffs[ringIdx] || 0);
|
|
395
|
+
let mesh;
|
|
396
|
+
if (idx % 4 === 0) mesh = sensorDome(mat, 0.1);
|
|
397
|
+
else if (idx % 6 === 0) mesh = dataSpike(mat, 0.32);
|
|
398
|
+
else mesh = bladeFin(mat, 0.3, 0.12);
|
|
399
|
+
mesh.position.set(Math.cos(angle) * r, y, Math.sin(angle) * r);
|
|
244
400
|
mesh.rotation.y = -angle;
|
|
245
401
|
return mesh;
|
|
246
402
|
},
|
|
403
|
+
|
|
404
|
+
/* ── AUTOMATION (16) ── left arm full column — shoulder → wrist */
|
|
247
405
|
Automation(mat, idx) {
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
406
|
+
const col = Math.floor(idx / 8);
|
|
407
|
+
const row = idx % 8;
|
|
408
|
+
let mesh;
|
|
409
|
+
if (idx % 4 === 0) mesh = hexPlate(mat, 0.16, 0.09);
|
|
410
|
+
else if (idx % 7 === 0) mesh = missilePod(mat, 0.1);
|
|
411
|
+
else mesh = armorPlate(mat, 0.1, 0.34, 0.4);
|
|
412
|
+
mesh.position.set(-2.08 - col * 0.22, 1.1 + row * 0.5, 0.05);
|
|
252
413
|
mesh.rotation.z = Math.PI / 2;
|
|
253
414
|
return mesh;
|
|
254
415
|
},
|
|
416
|
+
|
|
417
|
+
/* ── DEVTOOLS (16) ── right arm full column — shoulder → wrist */
|
|
255
418
|
DevTools(mat, idx) {
|
|
256
|
-
const col = idx
|
|
257
|
-
const row =
|
|
258
|
-
|
|
259
|
-
|
|
419
|
+
const col = Math.floor(idx / 8);
|
|
420
|
+
const row = idx % 8;
|
|
421
|
+
let mesh;
|
|
422
|
+
if (idx % 4 === 0) mesh = hexPlate(mat, 0.16, 0.09);
|
|
423
|
+
else if (idx % 7 === 0) mesh = dataSpike(mat, 0.36);
|
|
424
|
+
else mesh = armorPlate(mat, 0.1, 0.34, 0.4);
|
|
425
|
+
mesh.position.set(2.08 + col * 0.22, 1.1 + row * 0.5, 0.05);
|
|
426
|
+
mesh.rotation.z = Math.PI / 2;
|
|
260
427
|
return mesh;
|
|
261
428
|
},
|
|
429
|
+
|
|
430
|
+
/* ── NFT (14) ── right shoulder cluster — arc + stack */
|
|
262
431
|
NFT(mat, idx) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
mesh
|
|
432
|
+
let mesh;
|
|
433
|
+
if (idx % 3 === 0) mesh = reactorCore(mat, 0.18);
|
|
434
|
+
else if (idx % 5 === 0) mesh = shoulderCannon(mat, 0.55);
|
|
435
|
+
else mesh = energyModule(mat, 0.19);
|
|
436
|
+
const arc = Math.floor(idx / 5);
|
|
437
|
+
const posInArc = idx % 5;
|
|
438
|
+
const angle = (posInArc / 5) * Math.PI * 1.1 - 0.3;
|
|
439
|
+
const r = 0.32 + arc * 0.25;
|
|
440
|
+
mesh.position.set(1.95 + Math.cos(angle) * r, 5.1 + arc * 0.38, -0.22 + Math.sin(angle) * 0.28);
|
|
267
441
|
return mesh;
|
|
268
442
|
},
|
|
443
|
+
|
|
444
|
+
/* ── SOCIAL (12) ── left shoulder cluster — mirrored arc */
|
|
269
445
|
Social(mat, idx) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
mesh
|
|
446
|
+
let mesh;
|
|
447
|
+
if (idx % 3 === 0) mesh = sensorDome(mat, 0.11);
|
|
448
|
+
else if (idx % 4 === 0) mesh = hexPlate(mat, 0.14, 0.08);
|
|
449
|
+
else mesh = bladeFin(mat, 0.28, 0.12);
|
|
450
|
+
const arc = Math.floor(idx / 4);
|
|
451
|
+
const posInArc = idx % 4;
|
|
452
|
+
const angle = (posInArc / 4) * Math.PI * 1.1 - 0.2;
|
|
453
|
+
const r = 0.3 + arc * 0.22;
|
|
454
|
+
mesh.position.set(-1.95 - Math.cos(angle) * r, 5.1 + arc * 0.38, -0.2 + Math.sin(angle) * 0.28);
|
|
274
455
|
return mesh;
|
|
275
456
|
},
|
|
457
|
+
|
|
458
|
+
/* ── STORAGE (12) ── waist band — full 360° drum pods */
|
|
276
459
|
Storage(mat, idx) {
|
|
460
|
+
const angle = (idx / 12) * Math.PI * 2;
|
|
461
|
+
const r = 0.72;
|
|
462
|
+
const g = new THREE.Group();
|
|
463
|
+
const bm = mat.clone();
|
|
464
|
+
g.add(new THREE.Mesh(new THREE.CylinderGeometry(0.13, 0.13, 0.38, 8), bm));
|
|
465
|
+
const gm = glowMat(mat, 2.0);
|
|
466
|
+
const ring = new THREE.Mesh(new THREE.TorusGeometry(0.135, 0.018, 7, 14), gm);
|
|
467
|
+
ring.rotation.x = Math.PI / 2; ring.position.y = 0.17; g.add(ring);
|
|
468
|
+
g.position.set(Math.cos(angle) * r, 1.5, Math.sin(angle) * r);
|
|
469
|
+
g.rotation.y = -angle;
|
|
470
|
+
return g;
|
|
471
|
+
},
|
|
472
|
+
|
|
473
|
+
/* ── PRODUCTIVITY (12) ── chest front — 4×3 panel grid */
|
|
474
|
+
Productivity(mat, idx) {
|
|
277
475
|
const col = idx % 4;
|
|
278
476
|
const row = Math.floor(idx / 4);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
ring.rotation.x = Math.PI / 2; ring.position.y = 0.18;
|
|
285
|
-
group.add(ring);
|
|
286
|
-
group.position.set(-0.6 + col * 0.42, 3.0 + row * 0.48, -0.92);
|
|
287
|
-
return group;
|
|
288
|
-
},
|
|
289
|
-
Productivity(mat, idx) {
|
|
290
|
-
const ring = Math.floor(idx / 3);
|
|
291
|
-
const triIdx = idx % 3;
|
|
292
|
-
const offsets = [[0, 0.18, 0], [-0.16, -0.09, 0], [0.16, -0.09, 0]];
|
|
293
|
-
const [ox, oy] = offsets[triIdx];
|
|
294
|
-
const mesh = bladeFin(mat, 0.24, 0.2);
|
|
295
|
-
mesh.position.set(ox, 4.5 + oy + ring * 0.48, 0.8 + ring * 0.12);
|
|
477
|
+
let mesh;
|
|
478
|
+
if (idx % 5 === 0) mesh = sensorDome(mat, 0.09);
|
|
479
|
+
else if (idx % 3 === 0) mesh = hexPlate(mat, 0.13, 0.07);
|
|
480
|
+
else mesh = armorPlate(mat, 0.34, 0.18, 0.06);
|
|
481
|
+
mesh.position.set(-0.5 + col * 0.34, 3.05 + row * 0.58, 0.88);
|
|
296
482
|
return mesh;
|
|
297
483
|
},
|
|
484
|
+
|
|
485
|
+
/* ── BRIDGE (10) ── left collar/upper chest — curved ramp */
|
|
298
486
|
Bridge(mat, idx) {
|
|
299
|
-
|
|
300
|
-
|
|
487
|
+
let mesh;
|
|
488
|
+
if (idx % 3 === 0) mesh = energyModule(mat, 0.17);
|
|
489
|
+
else if (idx % 4 === 0) mesh = thrusterNozzle(mat, 0.13);
|
|
490
|
+
else mesh = armorPlate(mat, 0.38, 0.16, 0.08);
|
|
491
|
+
const t = idx / 9;
|
|
492
|
+
mesh.position.set(-0.6 - t * 0.55, 5.6 - t * 0.55, 0.55 - t * 0.18);
|
|
493
|
+
mesh.rotation.y = t * 0.3;
|
|
301
494
|
return mesh;
|
|
302
495
|
},
|
|
496
|
+
|
|
497
|
+
/* ── TRADING (10) ── right collar/upper chest — mirrored ramp */
|
|
303
498
|
Trading(mat, idx) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
mesh
|
|
308
|
-
|
|
499
|
+
let mesh;
|
|
500
|
+
if (idx % 3 === 0) mesh = energyModule(mat, 0.17);
|
|
501
|
+
else if (idx % 4 === 0) mesh = missilePod(mat, 0.12);
|
|
502
|
+
else mesh = armorPlate(mat, 0.38, 0.16, 0.08);
|
|
503
|
+
const t = idx / 9;
|
|
504
|
+
mesh.position.set(0.6 + t * 0.55, 5.6 - t * 0.55, 0.55 - t * 0.18);
|
|
505
|
+
mesh.rotation.y = -t * 0.3;
|
|
309
506
|
return mesh;
|
|
310
507
|
},
|
|
508
|
+
|
|
509
|
+
/* ── GOVERNANCE (10) ── head sides + crown band */
|
|
311
510
|
Governance(mat, idx) {
|
|
312
|
-
|
|
313
|
-
|
|
511
|
+
let mesh;
|
|
512
|
+
if (idx === 0) mesh = shoulderCannon(mat, 0.42);
|
|
513
|
+
else if (idx % 3 === 0) mesh = sensorDome(mat, 0.08);
|
|
514
|
+
else mesh = armorPlate(mat, 0.28, 0.09, 0.06);
|
|
515
|
+
const side = idx % 2 === 0 ? 1 : -1;
|
|
516
|
+
const row = Math.floor(idx / 2);
|
|
517
|
+
mesh.position.set(side * 0.52, 6.7 - row * 0.19, 0.3 + row * 0.04);
|
|
314
518
|
return mesh;
|
|
315
519
|
},
|
|
520
|
+
|
|
521
|
+
/* ── WALLET (10) ── hip-belt band — evenly spaced on front 180° */
|
|
316
522
|
Wallet(mat, idx) {
|
|
317
|
-
const angle = (idx /
|
|
318
|
-
const r =
|
|
319
|
-
|
|
320
|
-
|
|
523
|
+
const angle = ((idx / 9) - 0.5) * Math.PI;
|
|
524
|
+
const r = 0.58;
|
|
525
|
+
let mesh;
|
|
526
|
+
if (idx === 0) mesh = reactorCore(mat, 0.14);
|
|
527
|
+
else if (idx % 3 === 0) mesh = hexPlate(mat, 0.12, 0.07);
|
|
528
|
+
else mesh = armorPlate(mat, 0.3, 0.14, 0.07);
|
|
529
|
+
mesh.position.set(Math.cos(angle) * r, 1.2, 0.62 + Math.sin(angle) * r * 0.5);
|
|
530
|
+
mesh.rotation.y = -angle;
|
|
321
531
|
return mesh;
|
|
322
532
|
},
|
|
533
|
+
|
|
534
|
+
/* ── DEFI (10) ── thigh + shin armour — bilateral leg panels */
|
|
323
535
|
DeFi(mat, idx) {
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
mesh
|
|
536
|
+
const side = idx % 2 === 0 ? -1 : 1;
|
|
537
|
+
const stack = Math.floor(idx / 2);
|
|
538
|
+
let mesh;
|
|
539
|
+
if (stack === 0) mesh = kneeGuard(mat, 0.38, 0.28);
|
|
540
|
+
else if (idx % 3 === 0) mesh = armorPlate(mat, 0.34, 0.2, 0.07);
|
|
541
|
+
else mesh = hexPlate(mat, 0.14, 0.08);
|
|
542
|
+
mesh.position.set(side * 0.56, 0.22 - stack * 0.55, 0.44);
|
|
329
543
|
return mesh;
|
|
330
544
|
},
|
|
545
|
+
|
|
546
|
+
/* ── DEVELOPMENT (12) ── right torso side — vertical stack */
|
|
331
547
|
Development(mat, idx) {
|
|
332
|
-
const
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
548
|
+
const row = idx % 6;
|
|
549
|
+
const col = Math.floor(idx / 6);
|
|
550
|
+
let mesh;
|
|
551
|
+
if (idx % 4 === 0) mesh = dataSpike(mat, 0.4);
|
|
552
|
+
else if (idx % 3 === 0) mesh = hexPlate(mat, 0.14, 0.08);
|
|
553
|
+
else mesh = armorPlate(mat, 0.12, 0.42, 0.48);
|
|
554
|
+
mesh.position.set(2.08 + col * 0.2, 2.5 + row * 0.52, 0.22);
|
|
336
555
|
return mesh;
|
|
337
556
|
},
|
|
557
|
+
|
|
558
|
+
/* ── WRITING (10) ── left forearm bracers — 2 cols × 5 rows */
|
|
338
559
|
Writing(mat, idx) {
|
|
339
|
-
const
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
mesh
|
|
560
|
+
const col = idx % 2;
|
|
561
|
+
const row = Math.floor(idx / 2);
|
|
562
|
+
let mesh;
|
|
563
|
+
if (idx % 4 === 0) mesh = bladeFin(mat, 0.26, 0.1);
|
|
564
|
+
else mesh = armorPlate(mat, 0.1, 0.3, 0.38);
|
|
565
|
+
mesh.position.set(-2.08 - col * 0.18, 1.2 + row * 0.52, 0.0);
|
|
566
|
+
mesh.rotation.z = Math.PI / 2;
|
|
344
567
|
return mesh;
|
|
345
568
|
},
|
|
569
|
+
|
|
570
|
+
/* ── COMMUNICATION (12) ── antenna array above head */
|
|
346
571
|
Communication(mat, idx) {
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
572
|
+
if (idx === 0) {
|
|
573
|
+
const d = sensorDome(mat, 0.16);
|
|
574
|
+
d.position.set(0, 7.55, 0);
|
|
575
|
+
return d;
|
|
576
|
+
}
|
|
577
|
+
if (idx % 3 === 0) {
|
|
578
|
+
const rc = reactorCore(mat, 0.12);
|
|
579
|
+
rc.position.set(0, 7.55 + idx * 0.38, 0);
|
|
580
|
+
return rc;
|
|
581
|
+
}
|
|
582
|
+
const angle = ((idx - 1) / 11) * Math.PI * 2;
|
|
583
|
+
const r = 0.22 + (idx % 3) * 0.12;
|
|
584
|
+
const spike = dataSpike(mat, 0.38 + idx * 0.06);
|
|
585
|
+
spike.position.set(Math.cos(angle) * r, 7.6 + idx * 0.3, Math.sin(angle) * r);
|
|
586
|
+
return spike;
|
|
350
587
|
},
|
|
351
588
|
};
|
|
352
589
|
|
|
353
|
-
/*
|
|
354
|
-
Overflow
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
590
|
+
/* ─────────────────────────────────────────────────────────────
|
|
591
|
+
Overflow: snap to nearest body-surface slab for this region
|
|
592
|
+
Uses a deterministic grid around the region anchor, NOT open
|
|
593
|
+
orbital space — parts stay tightly against the robot body.
|
|
594
|
+
───────────────────────────────────────────────────────────── */
|
|
595
|
+
|
|
596
|
+
/* Surface-normal hint for snapping overflow to body faces */
|
|
597
|
+
const REGION_SURFACE_NORMAL = {
|
|
598
|
+
Security: new THREE.Vector3( 0, 0, -1),
|
|
599
|
+
Analytics: new THREE.Vector3( 0, 1, 0),
|
|
600
|
+
Automation: new THREE.Vector3(-1, 0, 0),
|
|
601
|
+
DevTools: new THREE.Vector3( 1, 0, 0),
|
|
602
|
+
NFT: new THREE.Vector3( 1, 0.3, 0),
|
|
603
|
+
Social: new THREE.Vector3(-1, 0.3, 0),
|
|
604
|
+
Storage: new THREE.Vector3( 0, -0.2,-1),
|
|
605
|
+
Productivity: new THREE.Vector3( 0, 0, 1),
|
|
606
|
+
Bridge: new THREE.Vector3(-0.7,0.3, 0.5),
|
|
607
|
+
Trading: new THREE.Vector3( 0.7,0.3, 0.5),
|
|
608
|
+
Governance: new THREE.Vector3( 0, 0.6, 0.5),
|
|
609
|
+
Wallet: new THREE.Vector3( 0, 0, 1),
|
|
610
|
+
DeFi: new THREE.Vector3( 0, 0, 1),
|
|
611
|
+
Development: new THREE.Vector3( 1, 0, 0),
|
|
612
|
+
Writing: new THREE.Vector3(-1, 0, 0),
|
|
613
|
+
Communication: new THREE.Vector3( 0, 1, 0),
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
function buildOverflow(mat, overflowIdx, cat) {
|
|
617
|
+
const region = REGIONS[cat] || REGIONS.Security;
|
|
618
|
+
const norm = (REGION_SURFACE_NORMAL[cat] || REGION_SURFACE_NORMAL.Security).clone().normalize();
|
|
363
619
|
const center = new THREE.Vector3(...region.center);
|
|
364
|
-
|
|
365
|
-
|
|
620
|
+
|
|
621
|
+
/* Tile overflow parts in a tight grid on the body surface */
|
|
622
|
+
const gridW = 4;
|
|
623
|
+
const gCol = overflowIdx % gridW;
|
|
624
|
+
const gRow = Math.floor(overflowIdx / gridW);
|
|
625
|
+
const step = 0.28;
|
|
626
|
+
|
|
627
|
+
/* Two tangent axes perpendicular to surface normal */
|
|
628
|
+
const up = Math.abs(norm.y) < 0.9 ? new THREE.Vector3(0, 1, 0) : new THREE.Vector3(1, 0, 0);
|
|
629
|
+
const tan1 = new THREE.Vector3().crossVectors(norm, up).normalize();
|
|
630
|
+
const tan2 = new THREE.Vector3().crossVectors(norm, tan1).normalize();
|
|
631
|
+
|
|
632
|
+
const offset = new THREE.Vector3()
|
|
633
|
+
.addScaledVector(tan1, (gCol - (gridW - 1) / 2) * step)
|
|
634
|
+
.addScaledVector(tan2, gRow * step);
|
|
635
|
+
|
|
636
|
+
/* Small compact parts for overflow */
|
|
637
|
+
const shapes = [
|
|
638
|
+
() => hexPlate(mat, 0.09, 0.05),
|
|
639
|
+
() => { const m = energyModule(mat, 0.09); m.rotation.x = Math.PI / 2; return m; },
|
|
640
|
+
() => armorPlate(mat, 0.2, 0.12, 0.04),
|
|
641
|
+
() => dataSpike(mat, 0.22),
|
|
642
|
+
() => sensorDome(mat, 0.07),
|
|
643
|
+
];
|
|
644
|
+
const mesh = shapes[overflowIdx % shapes.length]();
|
|
645
|
+
mesh.position.copy(center).add(offset);
|
|
366
646
|
return mesh;
|
|
367
647
|
}
|
|
368
648
|
|
|
369
|
-
/*
|
|
370
|
-
|
|
371
|
-
|
|
649
|
+
/* ─────────────────────────────────────────────────────────────
|
|
650
|
+
Central dispatcher
|
|
651
|
+
───────────────────────────────────────────────────────────── */
|
|
372
652
|
function buildOne(cat, mat, idx, count) {
|
|
373
653
|
const region = REGIONS[cat];
|
|
374
|
-
if (!region)
|
|
375
|
-
return buildOverflow(mat, idx, REGIONS.Security);
|
|
376
|
-
}
|
|
654
|
+
if (!region) return buildOverflow(mat, idx, "Security");
|
|
377
655
|
const cap = region.primaryCap;
|
|
378
656
|
if (idx < cap && PRIMARY[cat]) {
|
|
379
657
|
return PRIMARY[cat](mat, idx, Math.min(count, cap));
|
|
380
658
|
}
|
|
381
|
-
return buildOverflow(mat, idx - cap,
|
|
659
|
+
return buildOverflow(mat, idx - cap, cat);
|
|
382
660
|
}
|
|
383
661
|
|
|
384
|
-
/*
|
|
385
|
-
|
|
386
|
-
|
|
662
|
+
/* ─────────────────────────────────────────────────────────────
|
|
663
|
+
Public API — build attachments from live skill list
|
|
664
|
+
───────────────────────────────────────────────────────────── */
|
|
387
665
|
export function buildAttachmentsFromSkills(skills, parentGroup) {
|
|
388
666
|
const byCategory = {};
|
|
389
667
|
skills.forEach(s => {
|
|
@@ -396,34 +674,34 @@ export function buildAttachmentsFromSkills(skills, parentGroup) {
|
|
|
396
674
|
|
|
397
675
|
for (const [cat, catSkills] of Object.entries(byCategory)) {
|
|
398
676
|
catSkills.forEach((skill, idx) => {
|
|
399
|
-
const mat
|
|
677
|
+
const mat = matFor(cat);
|
|
400
678
|
const built = buildOne(cat, mat, idx, catSkills.length);
|
|
401
679
|
const group = new THREE.Group();
|
|
402
680
|
|
|
403
681
|
group.userData = {
|
|
404
|
-
skillSlug:
|
|
405
|
-
skillName:
|
|
406
|
-
category:
|
|
407
|
-
description:
|
|
408
|
-
onchain:
|
|
409
|
-
vettedOk:
|
|
410
|
-
risk_tier:
|
|
682
|
+
skillSlug: skill.slug,
|
|
683
|
+
skillName: skill.name,
|
|
684
|
+
category: cat,
|
|
685
|
+
description: skill.description,
|
|
686
|
+
onchain: skill.onchain || !!skill.onchainTokenId,
|
|
687
|
+
vettedOk: skill.vetted_ok || skill.vettedOk,
|
|
688
|
+
risk_tier: skill.risk_tier || skill.riskTier,
|
|
411
689
|
documentation_md: skill.documentation_md,
|
|
412
|
-
fileName:
|
|
413
|
-
source:
|
|
414
|
-
categoryIndex:
|
|
415
|
-
isOverflow:
|
|
690
|
+
fileName: skill.fileName,
|
|
691
|
+
source: skill.source,
|
|
692
|
+
categoryIndex: idx,
|
|
693
|
+
isOverflow: idx >= (REGIONS[cat]?.primaryCap || 0),
|
|
416
694
|
};
|
|
417
695
|
|
|
696
|
+
/* Copy sub-meshes + world position from the builder group */
|
|
418
697
|
if (built.isGroup) {
|
|
419
698
|
built.children.forEach(c => group.add(c.clone()));
|
|
420
699
|
group.position.copy(built.position);
|
|
700
|
+
group.rotation.copy(built.rotation);
|
|
421
701
|
} else {
|
|
422
702
|
group.add(built);
|
|
423
|
-
if (built.position) {
|
|
424
|
-
|
|
425
|
-
built.position.set(0, 0, 0);
|
|
426
|
-
}
|
|
703
|
+
if (built.position) { group.position.copy(built.position); built.position.set(0, 0, 0); }
|
|
704
|
+
if (built.rotation) { group.rotation.copy(built.rotation); built.rotation.set(0, 0, 0); }
|
|
427
705
|
}
|
|
428
706
|
|
|
429
707
|
parentGroup.add(group);
|
|
@@ -434,34 +712,28 @@ export function buildAttachmentsFromSkills(skills, parentGroup) {
|
|
|
434
712
|
return attachments;
|
|
435
713
|
}
|
|
436
714
|
|
|
437
|
-
/*
|
|
438
|
-
Clear
|
|
439
|
-
|
|
715
|
+
/* ─────────────────────────────────────────────────────────────
|
|
716
|
+
Clear helpers
|
|
717
|
+
───────────────────────────────────────────────────────────── */
|
|
440
718
|
export function clearAttachments(attachmentGroup, energyNetworkGroup) {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const child = energyNetworkGroup.children[0];
|
|
454
|
-
child.traverse(c => {
|
|
455
|
-
if (c.geometry) c.geometry.dispose();
|
|
456
|
-
if (c.material) c.material.dispose();
|
|
457
|
-
});
|
|
458
|
-
energyNetworkGroup.remove(child);
|
|
719
|
+
for (const grp of [attachmentGroup, energyNetworkGroup]) {
|
|
720
|
+
while (grp.children.length) {
|
|
721
|
+
const child = grp.children[0];
|
|
722
|
+
child.traverse(c => {
|
|
723
|
+
if (c.geometry) c.geometry.dispose();
|
|
724
|
+
if (c.material) {
|
|
725
|
+
if (Array.isArray(c.material)) c.material.forEach(m => m.dispose());
|
|
726
|
+
else c.material.dispose();
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
grp.remove(child);
|
|
730
|
+
}
|
|
459
731
|
}
|
|
460
732
|
}
|
|
461
733
|
|
|
462
|
-
/*
|
|
463
|
-
Energy network — lines
|
|
464
|
-
|
|
734
|
+
/* ─────────────────────────────────────────────────────────────
|
|
735
|
+
Energy network — low-opacity lines linking category clusters
|
|
736
|
+
───────────────────────────────────────────────────────────── */
|
|
465
737
|
export function buildEnergyNetwork(attachments, parentGroup) {
|
|
466
738
|
const byCategory = {};
|
|
467
739
|
attachments.forEach(a => {
|
|
@@ -470,140 +742,107 @@ export function buildEnergyNetwork(attachments, parentGroup) {
|
|
|
470
742
|
byCategory[cat].push(a);
|
|
471
743
|
});
|
|
472
744
|
|
|
473
|
-
const corePos = new THREE.Vector3(0, 4.
|
|
745
|
+
const corePos = new THREE.Vector3(0, 4.2, 0.65);
|
|
474
746
|
|
|
475
747
|
for (const [cat, atts] of Object.entries(byCategory)) {
|
|
476
|
-
const color = CATEGORY_COLORS_HEX[cat] ||
|
|
477
|
-
const
|
|
478
|
-
color, transparent: true, opacity: 0.
|
|
748
|
+
const color = CATEGORY_COLORS_HEX[cat] || 0xaaaaaa;
|
|
749
|
+
const lineMat = new THREE.LineBasicMaterial({
|
|
750
|
+
color, transparent: true, opacity: 0.11,
|
|
479
751
|
blending: THREE.AdditiveBlending,
|
|
480
752
|
});
|
|
481
753
|
|
|
482
|
-
const
|
|
483
|
-
for (let i = 0; i <
|
|
754
|
+
const maxChain = Math.min(atts.length - 1, 80);
|
|
755
|
+
for (let i = 0; i < maxChain; i++) {
|
|
484
756
|
const geo = new THREE.BufferGeometry().setFromPoints([atts[i].position, atts[i + 1].position]);
|
|
485
|
-
const line = new THREE.Line(geo,
|
|
757
|
+
const line = new THREE.Line(geo, lineMat.clone());
|
|
486
758
|
line.userData.category = cat;
|
|
487
759
|
parentGroup.add(line);
|
|
488
760
|
}
|
|
489
761
|
|
|
490
762
|
if (atts.length > 0) {
|
|
491
|
-
const hubMat =
|
|
492
|
-
hubMat.opacity = 0.1;
|
|
763
|
+
const hubMat = lineMat.clone(); hubMat.opacity = 0.07;
|
|
493
764
|
const geo = new THREE.BufferGeometry().setFromPoints([atts[0].position, corePos]);
|
|
494
|
-
|
|
495
|
-
line.userData.category = cat;
|
|
496
|
-
parentGroup.add(line);
|
|
765
|
+
parentGroup.add(new THREE.Line(geo, hubMat));
|
|
497
766
|
}
|
|
498
767
|
}
|
|
499
768
|
}
|
|
500
769
|
|
|
501
|
-
/*
|
|
502
|
-
|
|
503
|
-
|
|
770
|
+
/* ─────────────────────────────────────────────────────────────
|
|
771
|
+
Per-category idle animations
|
|
772
|
+
───────────────────────────────────────────────────────────── */
|
|
504
773
|
const IDLE = {
|
|
505
774
|
Security(att, time, idx) {
|
|
506
|
-
att.traverse(c => {
|
|
507
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.6 + 1.0 * Math.max(0, Math.sin(time * 2 + idx * 0.4));
|
|
508
|
-
});
|
|
775
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.5 + 0.9 * Math.max(0, Math.sin(time * 2.1 + idx * 0.38)); });
|
|
509
776
|
},
|
|
510
777
|
Analytics(att, time, idx) {
|
|
511
778
|
att.rotation.y += 0.003;
|
|
512
|
-
att.traverse(c => {
|
|
513
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.8 + 0.6 * Math.sin(time * 3 + idx);
|
|
514
|
-
});
|
|
779
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.7 + 0.6 * Math.sin(time * 3.2 + idx); });
|
|
515
780
|
},
|
|
516
781
|
Automation(att, time, idx) {
|
|
517
|
-
att.traverse(c => {
|
|
518
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.6 + 0.8 * Math.max(0, Math.sin(time * 3 - idx * 1.2));
|
|
519
|
-
});
|
|
782
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.5 + 0.85 * Math.max(0, Math.sin(time * 2.8 - idx * 1.1)); });
|
|
520
783
|
},
|
|
521
|
-
DevTools(att, time) {
|
|
522
|
-
att.traverse(c => {
|
|
523
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.8 + 0.6 * Math.sin(time * 4);
|
|
524
|
-
});
|
|
784
|
+
DevTools(att, time, idx) {
|
|
785
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.7 + 0.7 * Math.sin(time * 4.0 + idx * 0.2); });
|
|
525
786
|
},
|
|
526
787
|
NFT(att, time) {
|
|
527
|
-
att.rotation.y += 0.
|
|
528
|
-
att.traverse(c => {
|
|
529
|
-
if (c.isMesh) c.material.emissiveIntensity = 1.0 + 0.8 * Math.sin(time * 1.5);
|
|
530
|
-
});
|
|
788
|
+
att.rotation.y += 0.009;
|
|
789
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.9 + 0.85 * Math.abs(Math.sin(time * 1.4)); });
|
|
531
790
|
},
|
|
532
|
-
Social(att, time) {
|
|
533
|
-
att.rotation.y += 0.
|
|
534
|
-
att.traverse(c => {
|
|
535
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.8 + 0.4 * Math.sin(time * 2);
|
|
536
|
-
});
|
|
791
|
+
Social(att, time, idx) {
|
|
792
|
+
att.rotation.y += 0.011;
|
|
793
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.7 + 0.45 * Math.sin(time * 2.2 + idx * 0.5); });
|
|
537
794
|
},
|
|
538
|
-
Storage(att, time) {
|
|
539
|
-
att.traverse(c => {
|
|
540
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.5 + 0.5 * Math.sin(time * 1.2);
|
|
541
|
-
});
|
|
795
|
+
Storage(att, time, idx) {
|
|
796
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.4 + 0.55 * Math.sin(time * 1.1 + idx * 0.6); });
|
|
542
797
|
},
|
|
543
|
-
Productivity(att, time) {
|
|
544
|
-
att.rotation.z += 0.
|
|
545
|
-
att.traverse(c => {
|
|
546
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.6 + 0.4 * Math.sin(time * 2.5);
|
|
547
|
-
});
|
|
798
|
+
Productivity(att, time, idx) {
|
|
799
|
+
att.rotation.z += 0.004;
|
|
800
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.55 + 0.45 * Math.sin(time * 2.6 + idx * 0.4); });
|
|
548
801
|
},
|
|
549
|
-
Bridge(att, time) {
|
|
550
|
-
att.rotation.y += 0.
|
|
551
|
-
att.traverse(c => {
|
|
552
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.8 + 0.5 * Math.sin(time * 2.5);
|
|
553
|
-
});
|
|
802
|
+
Bridge(att, time, idx) {
|
|
803
|
+
att.rotation.y += 0.007;
|
|
804
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.7 + 0.55 * Math.sin(time * 2.4 + idx * 0.55); });
|
|
554
805
|
},
|
|
555
806
|
Trading(att, time) {
|
|
556
807
|
att.traverse(c => {
|
|
557
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.6 + 1.
|
|
808
|
+
if (c.isMesh) c.material.emissiveIntensity = 0.6 + 1.1 * (Math.random() > 0.92 ? 1 : 0.15);
|
|
558
809
|
});
|
|
559
810
|
},
|
|
560
|
-
Governance(att, time) {
|
|
561
|
-
att.traverse(c => {
|
|
562
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.8 + 0.4 * Math.sin(time * 1.5);
|
|
563
|
-
});
|
|
811
|
+
Governance(att, time, idx) {
|
|
812
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.7 + 0.5 * Math.sin(time * 1.6 + idx * 0.7); });
|
|
564
813
|
},
|
|
565
|
-
Wallet(att, time) {
|
|
566
|
-
att.traverse(c => {
|
|
567
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.5 + 0.7 * (0.5 + 0.5 * Math.sin(time * 4.2));
|
|
568
|
-
});
|
|
814
|
+
Wallet(att, time, idx) {
|
|
815
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.45 + 0.75 * (0.5 + 0.5 * Math.sin(time * 4.4 + idx * 0.5)); });
|
|
569
816
|
},
|
|
570
|
-
DeFi(att, time) {
|
|
571
|
-
att.traverse(c => {
|
|
572
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.6 + 0.5 * Math.sin(time * 2);
|
|
573
|
-
});
|
|
817
|
+
DeFi(att, time, idx) {
|
|
818
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.55 + 0.55 * Math.sin(time * 2.1 + idx * 0.6); });
|
|
574
819
|
},
|
|
575
|
-
Development(att, time) {
|
|
576
|
-
att.traverse(c => {
|
|
577
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.8 + 0.6 * Math.sin(time * 3);
|
|
578
|
-
});
|
|
820
|
+
Development(att, time, idx) {
|
|
821
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.7 + 0.65 * Math.sin(time * 3.1 + idx * 0.35); });
|
|
579
822
|
},
|
|
580
|
-
Writing(att, time) {
|
|
581
|
-
att.traverse(c => {
|
|
582
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.5 + 0.3 * Math.sin(time * 1.8);
|
|
583
|
-
});
|
|
823
|
+
Writing(att, time, idx) {
|
|
824
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.45 + 0.35 * Math.sin(time * 1.9 + idx * 0.45); });
|
|
584
825
|
},
|
|
585
|
-
Communication(att, time) {
|
|
586
|
-
att.
|
|
587
|
-
|
|
588
|
-
});
|
|
826
|
+
Communication(att, time, idx) {
|
|
827
|
+
att.rotation.y += 0.005;
|
|
828
|
+
att.traverse(c => { if (c.isMesh) c.material.emissiveIntensity = 0.7 + 0.75 * Math.abs(Math.sin(time * 3.0 + idx * 0.3)); });
|
|
589
829
|
},
|
|
590
830
|
};
|
|
591
831
|
|
|
592
832
|
function overflowIdle(att, time, idx) {
|
|
593
|
-
att.rotation.y += 0.
|
|
833
|
+
att.rotation.y += 0.003;
|
|
594
834
|
att.traverse(c => {
|
|
595
|
-
if (c.isMesh) c.material.emissiveIntensity = 0.
|
|
835
|
+
if (c.isMesh) c.material.emissiveIntensity = 0.5 + 0.55 * Math.sin(time * 1.6 + idx * 0.32);
|
|
596
836
|
});
|
|
597
837
|
}
|
|
598
838
|
|
|
599
839
|
export function makeIdleAnimator(attachments) {
|
|
600
840
|
return function idleAnimator(time) {
|
|
601
841
|
for (const att of attachments) {
|
|
602
|
-
const cat = att.userData.category;
|
|
603
842
|
if (att.userData.isOverflow) {
|
|
604
843
|
overflowIdle(att, time, att.userData.categoryIndex);
|
|
605
844
|
} else {
|
|
606
|
-
const fn = IDLE[
|
|
845
|
+
const fn = IDLE[att.userData.category];
|
|
607
846
|
if (fn) fn(att, time, att.userData.categoryIndex);
|
|
608
847
|
}
|
|
609
848
|
}
|