JamesToth commited on
Commit
3aa7e84
·
verified ·
1 Parent(s): d5c284a

Create index.html

Browse files
Files changed (1) hide show
  1. index.html +423 -0
index.html ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Pro CPU Particle System</title>
7
+ <style>
8
+ body { margin: 0; overflow: hidden; background: #000; font-family: 'Courier New', Courier, monospace; }
9
+
10
+ #hud {
11
+ position: absolute;
12
+ top: 20px;
13
+ left: 20px;
14
+ color: rgba(255, 255, 255, 0.8);
15
+ pointer-events: none;
16
+ z-index: 10;
17
+ }
18
+
19
+ h1 { margin: 0; font-size: 14px; text-transform: uppercase; letter-spacing: 2px; }
20
+ .meta { font-size: 10px; color: #555; margin-top: 5px; }
21
+
22
+ #gesture-alert {
23
+ position: absolute;
24
+ top: 50%; left: 50%;
25
+ transform: translate(-50%, -50%);
26
+ font-size: 2rem;
27
+ color: white;
28
+ opacity: 0;
29
+ transition: opacity 0.5s;
30
+ text-shadow: 0 0 20px rgba(0,255,255,0.8);
31
+ pointer-events: none;
32
+ }
33
+
34
+ #loading {
35
+ position: absolute;
36
+ bottom: 20px; left: 20px;
37
+ color: #00ff88;
38
+ font-size: 12px;
39
+ }
40
+
41
+ video { display: none; }
42
+ </style>
43
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
44
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
45
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
46
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
47
+ </head>
48
+ <body>
49
+
50
+ <div id="hud">
51
+ <h1>Kinetic Particle Engine</h1>
52
+ <div class="meta">CPU OPTIMIZED // PHYSICS ENABLED</div>
53
+ <div class="meta" id="mode-display">MODE: GALAXY</div>
54
+ <div class="meta" style="margin-top:15px; color:#888;">
55
+ [GESTURES]<br>
56
+ • SWIPE LEFT/RIGHT: Change Shape<br>
57
+ • PINCH: Scale<br>
58
+ • FIST: Gravity Well (Attract)
59
+ </div>
60
+ </div>
61
+
62
+ <div id="gesture-alert">SWIPE DETECTED</div>
63
+ <div id="loading">SYSTEM INITIALIZING...</div>
64
+ <video id="input-video"></video>
65
+
66
+ <script>
67
+ /**
68
+ * ------------------------------------------------------------------
69
+ * 1. UTILITIES & GENERATORS
70
+ * ------------------------------------------------------------------
71
+ */
72
+
73
+ // Generate a soft glow texture programmatically (saves loading images)
74
+ function createGlowTexture() {
75
+ const canvas = document.createElement('canvas');
76
+ canvas.width = 32; canvas.height = 32;
77
+ const ctx = canvas.getContext('2d');
78
+ const grad = ctx.createRadialGradient(16, 16, 0, 16, 16, 16);
79
+ grad.addColorStop(0, 'rgba(255, 255, 255, 1)');
80
+ grad.addColorStop(0.2, 'rgba(255, 255, 255, 0.8)');
81
+ grad.addColorStop(0.5, 'rgba(255, 255, 255, 0.2)');
82
+ grad.addColorStop(1, 'rgba(0, 0, 0, 0)');
83
+ ctx.fillStyle = grad;
84
+ ctx.fillRect(0, 0, 32, 32);
85
+ const texture = new THREE.CanvasTexture(canvas);
86
+ return texture;
87
+ }
88
+
89
+ // Shape Formulas
90
+ const Shapes = {
91
+ galaxy: (i, total) => {
92
+ const arms = 3;
93
+ const spin = i / total * arms;
94
+ const r = (i / total) * 8;
95
+ const angle = spin * Math.PI * 2;
96
+ return {
97
+ x: Math.cos(angle) * r,
98
+ y: (Math.random() - 0.5) * (10 - r), // Thicker center
99
+ z: Math.sin(angle) * r
100
+ };
101
+ },
102
+ sphere: (i, total) => {
103
+ const phi = Math.acos(-1 + (2 * i) / total);
104
+ const theta = Math.sqrt(total * Math.PI) * phi;
105
+ const r = 5;
106
+ return {
107
+ x: r * Math.cos(theta) * Math.sin(phi),
108
+ y: r * Math.sin(theta) * Math.sin(phi),
109
+ z: r * Math.cos(phi)
110
+ };
111
+ },
112
+ heart: (i, total) => {
113
+ const t = Math.random() * Math.PI * 2;
114
+ // 3D Heart approximation
115
+ const x = 16 * Math.pow(Math.sin(t), 3);
116
+ const y = 13 * Math.cos(t) - 5 * Math.cos(2*t) - 2 * Math.cos(3*t) - Math.cos(4*t);
117
+ const scale = 0.35;
118
+ return {
119
+ x: x * scale,
120
+ y: y * scale,
121
+ z: (Math.random()-0.5) * 4
122
+ };
123
+ },
124
+ tornado: (i, total) => {
125
+ const angle = i * 0.1;
126
+ const y = (i / total) * 12 - 6;
127
+ const r = 1 + (y + 6) * 0.3;
128
+ return {
129
+ x: Math.cos(angle) * r,
130
+ y: y,
131
+ z: Math.sin(angle) * r
132
+ };
133
+ }
134
+ };
135
+
136
+ /**
137
+ * ------------------------------------------------------------------
138
+ * 2. CORE ENGINE SETUP
139
+ * ------------------------------------------------------------------
140
+ */
141
+ const PARTICLE_COUNT = 12000; // Optimized for CPU physics
142
+ const scene = new THREE.Scene();
143
+ scene.fog = new THREE.FogExp2(0x000000, 0.04);
144
+
145
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
146
+ camera.position.z = 12;
147
+
148
+ const renderer = new THREE.WebGLRenderer({ antialias: false, powerPreference: "high-performance" });
149
+ renderer.setSize(window.innerWidth, window.innerHeight);
150
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
151
+ document.body.appendChild(renderer.domElement);
152
+
153
+ /**
154
+ * ------------------------------------------------------------------
155
+ * 3. PARTICLE SYSTEM (PHYSICS BASED)
156
+ * ------------------------------------------------------------------
157
+ */
158
+ const geometry = new THREE.BufferGeometry();
159
+
160
+ // Arrays for Double Buffering / Physics
161
+ const posArray = new Float32Array(PARTICLE_COUNT * 3);
162
+ const targetArray = new Float32Array(PARTICLE_COUNT * 3);
163
+ const velocityArray = new Float32Array(PARTICLE_COUNT * 3); // Velocity for physics
164
+ const colorsArray = new Float32Array(PARTICLE_COUNT * 3);
165
+
166
+ const baseColor = new THREE.Color(0x00aaff);
167
+
168
+ for(let i=0; i<PARTICLE_COUNT; i++) {
169
+ const i3 = i*3;
170
+ posArray[i3] = (Math.random()-0.5)*20;
171
+ posArray[i3+1] = (Math.random()-0.5)*20;
172
+ posArray[i3+2] = (Math.random()-0.5)*20;
173
+
174
+ targetArray[i3] = posArray[i3];
175
+
176
+ colorsArray[i3] = baseColor.r;
177
+ colorsArray[i3+1] = baseColor.g;
178
+ colorsArray[i3+2] = baseColor.b;
179
+ }
180
+
181
+ geometry.setAttribute('position', new THREE.BufferAttribute(posArray, 3));
182
+ geometry.setAttribute('color', new THREE.BufferAttribute(colorsArray, 3));
183
+
184
+ const material = new THREE.PointsMaterial({
185
+ size: 0.15,
186
+ map: createGlowTexture(), // Use generated texture
187
+ vertexColors: true,
188
+ transparent: true,
189
+ opacity: 0.9,
190
+ blending: THREE.AdditiveBlending,
191
+ depthWrite: false
192
+ });
193
+
194
+ const particles = new THREE.Points(geometry, material);
195
+ scene.add(particles);
196
+
197
+ /**
198
+ * ------------------------------------------------------------------
199
+ * 4. LOGIC & STATE
200
+ * ------------------------------------------------------------------
201
+ */
202
+ const AppState = {
203
+ currentShape: 'galaxy',
204
+ shapeKeys: Object.keys(Shapes),
205
+ shapeIndex: 0,
206
+ hand: { x: 0, y: 0, z: 0, active: false, pinch: 0, fist: false },
207
+ handVelocity: { x: 0, y: 0 }
208
+ };
209
+
210
+ function switchShape(direction) {
211
+ if(direction === 'next') {
212
+ AppState.shapeIndex = (AppState.shapeIndex + 1) % AppState.shapeKeys.length;
213
+ } else {
214
+ AppState.shapeIndex = (AppState.shapeIndex - 1 + AppState.shapeKeys.length) % AppState.shapeKeys.length;
215
+ }
216
+
217
+ AppState.currentShape = AppState.shapeKeys[AppState.shapeIndex];
218
+ document.getElementById('mode-display').innerText = `MODE: ${AppState.currentShape.toUpperCase()}`;
219
+
220
+ // Show UI feedback
221
+ const alert = document.getElementById('gesture-alert');
222
+ alert.innerText = `${AppState.currentShape.toUpperCase()}`;
223
+ alert.style.opacity = 1;
224
+ setTimeout(() => alert.style.opacity = 0, 1000);
225
+
226
+ // Recalculate targets
227
+ const func = Shapes[AppState.currentShape];
228
+ for(let i=0; i<PARTICLE_COUNT; i++) {
229
+ const p = func(i, PARTICLE_COUNT);
230
+ targetArray[i*3] = p.x;
231
+ targetArray[i*3+1] = p.y;
232
+ targetArray[i*3+2] = p.z;
233
+ }
234
+ }
235
+
236
+ // Init first shape
237
+ switchShape('next');
238
+
239
+ /**
240
+ * ------------------------------------------------------------------
241
+ * 5. COMPUTER VISION (THROTTLED)
242
+ * ------------------------------------------------------------------
243
+ */
244
+ const videoElement = document.getElementById('input-video');
245
+ let lastHandX = 0;
246
+
247
+ // We will only process MediaPipe every 2 frames to save CPU for rendering
248
+ let frameSkipCounter = 0;
249
+
250
+ const hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
251
+ hands.setOptions({
252
+ maxNumHands: 1,
253
+ modelComplexity: 0, // Lite model for speed
254
+ minDetectionConfidence: 0.5,
255
+ minTrackingConfidence: 0.5
256
+ });
257
+
258
+ hands.onResults(results => {
259
+ const loading = document.getElementById('loading');
260
+ if(loading) loading.style.display = 'none';
261
+
262
+ if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
263
+ const landmarks = results.multiHandLandmarks[0];
264
+ AppState.hand.active = true;
265
+
266
+ // 1. Position Mapping (Normalized -1 to 1)
267
+ // Invert X because webcam is mirrored
268
+ const hx = -(landmarks[9].x - 0.5) * 10;
269
+ const hy = -(landmarks[9].y - 0.5) * 8;
270
+
271
+ // Calculate Velocity for gestures
272
+ AppState.handVelocity.x = hx - lastHandX;
273
+ lastHandX = hx;
274
+
275
+ // Smooth hand position
276
+ AppState.hand.x += (hx - AppState.hand.x) * 0.2;
277
+ AppState.hand.y += (hy - AppState.hand.y) * 0.2;
278
+
279
+ // 2. Pinch Detection (Thumb Tip vs Index Tip)
280
+ const pinchDist = Math.hypot(landmarks[4].x - landmarks[8].x, landmarks[4].y - landmarks[8].y);
281
+ AppState.hand.pinch = pinchDist; // <0.05 is pinch
282
+
283
+ // 3. Fist Detection (Tip to Wrist)
284
+ const wrist = landmarks[0];
285
+ const midTip = landmarks[12];
286
+ const dist = Math.hypot(wrist.x - midTip.x, wrist.y - midTip.y);
287
+ AppState.hand.fist = dist < 0.2; // Threshold for fist
288
+
289
+ // 4. Swipe Detection
290
+ if (Math.abs(AppState.handVelocity.x) > 0.6) {
291
+ // Debounce swipe
292
+ if (!AppState.swiping) {
293
+ AppState.swiping = true;
294
+ switchShape(AppState.handVelocity.x > 0 ? 'next' : 'prev');
295
+ setTimeout(() => AppState.swiping = false, 500);
296
+ }
297
+ }
298
+
299
+ } else {
300
+ AppState.hand.active = false;
301
+ }
302
+ });
303
+
304
+ const cameraUtils = new Camera(videoElement, {
305
+ onFrame: async () => {
306
+ // Throttling Logic: Run AI every 2nd frame only
307
+ frameSkipCounter++;
308
+ if(frameSkipCounter % 2 === 0) {
309
+ await hands.send({image: videoElement});
310
+ }
311
+ },
312
+ width: 640,
313
+ height: 480
314
+ });
315
+ cameraUtils.start();
316
+
317
+ /**
318
+ * ------------------------------------------------------------------
319
+ * 6. PHYSICS LOOP
320
+ * ------------------------------------------------------------------
321
+ */
322
+ const clock = new THREE.Clock();
323
+
324
+ function animate() {
325
+ requestAnimationFrame(animate);
326
+
327
+ const time = clock.getElapsedTime();
328
+ const positions = particles.geometry.attributes.position.array;
329
+ const colors = particles.geometry.attributes.color.array;
330
+
331
+ // Interaction Variables
332
+ let globalScale = 1.0;
333
+ if(AppState.hand.active) {
334
+ // Map pinch to scale: 0.02->0.5, 0.2->1.5
335
+ globalScale = THREE.MathUtils.mapLinear(AppState.hand.pinch, 0.02, 0.2, 0.5, 1.8);
336
+ }
337
+
338
+ // Particle Loop
339
+ for (let i = 0; i < PARTICLE_COUNT; i++) {
340
+ const i3 = i * 3;
341
+
342
+ // 1. Get Target
343
+ let tx = targetArray[i3] * globalScale;
344
+ let ty = targetArray[i3+1] * globalScale;
345
+ let tz = targetArray[i3+2] * globalScale;
346
+
347
+ // 2. Hand Repulsion / Attraction Physics
348
+ if (AppState.hand.active) {
349
+ const dx = positions[i3] - AppState.hand.x;
350
+ const dy = positions[i3+1] - AppState.hand.y;
351
+ const distSq = dx*dx + dy*dy;
352
+
353
+ // Repulsion Radius
354
+ if (distSq < 4) {
355
+ const force = (4 - distSq) * 0.1;
356
+
357
+ if(AppState.hand.fist) {
358
+ // Attraction (Black Hole)
359
+ tx -= dx * force * 5;
360
+ ty -= dy * force * 5;
361
+ } else {
362
+ // Repulsion (Push away)
363
+ tx += dx * force;
364
+ ty += dy * force;
365
+ }
366
+ }
367
+ }
368
+
369
+ // 3. Spring Physics Integration
370
+ // Acceleration = (Target - Current) * SpringStrength
371
+ const ax = (tx - positions[i3]) * 0.05; // Spring strength
372
+ const ay = (ty - positions[i3+1]) * 0.05;
373
+ const az = (tz - positions[i3+2]) * 0.05;
374
+
375
+ // Update Velocity (Inertia)
376
+ velocityArray[i3] += ax;
377
+ velocityArray[i3+1] += ay;
378
+ velocityArray[i3+2] += az;
379
+
380
+ // Friction (Damping)
381
+ velocityArray[i3] *= 0.92;
382
+ velocityArray[i3+1] *= 0.92;
383
+ velocityArray[i3+2] *= 0.92;
384
+
385
+ // Apply Velocity
386
+ positions[i3] += velocityArray[i3];
387
+ positions[i3+1] += velocityArray[i3+1];
388
+ positions[i3+2] += velocityArray[i3+2];
389
+
390
+ // 4. Dynamic Coloring based on Speed
391
+ const speed = Math.abs(velocityArray[i3]) + Math.abs(velocityArray[i3+1]);
392
+ if (speed > 0.1) {
393
+ // Moving fast -> White/Cyan
394
+ colors[i3] = 0.8; colors[i3+1] = 1.0; colors[i3+2] = 1.0;
395
+ } else {
396
+ // Resting -> Base Color
397
+ colors[i3] += (baseColor.r - colors[i3]) * 0.1;
398
+ colors[i3+1] += (baseColor.g - colors[i3+1]) * 0.1;
399
+ colors[i3+2] += (baseColor.b - colors[i3+2]) * 0.1;
400
+ }
401
+ }
402
+
403
+ // Global idle rotation
404
+ if(!AppState.hand.active) {
405
+ particles.rotation.y = time * 0.1;
406
+ }
407
+
408
+ particles.geometry.attributes.position.needsUpdate = true;
409
+ particles.geometry.attributes.color.needsUpdate = true;
410
+ renderer.render(scene, camera);
411
+ }
412
+
413
+ animate();
414
+
415
+ // Resize Handler
416
+ window.addEventListener('resize', () => {
417
+ camera.aspect = window.innerWidth / window.innerHeight;
418
+ camera.updateProjectionMatrix();
419
+ renderer.setSize(window.innerWidth, window.innerHeight);
420
+ });
421
+ </script>
422
+ </body>
423
+ </html>