MySafeCode commited on
Commit
1b21cbf
·
verified ·
1 Parent(s): f8c885c

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +751 -668
index.html CHANGED
@@ -1,682 +1,765 @@
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>SID Chip Tune Studio</title>
7
- <style>
8
- * {
9
- margin: 0;
10
- padding: 0;
11
- box-sizing: border-box;
12
- }
13
-
14
- :root {
15
- --c64-blue: #352879;
16
- --c64-lightblue: #6c5eb5;
17
- --c64-cyan: #00a0a0;
18
- --c64-purple: #a0a0ff;
19
- --c64-pink: #ff00ff;
20
- --c64-yellow: #ffff00;
21
- --c64-orange: #ff7700;
22
- --c64-green: #00ff00;
23
- --c64-red: #ff0000;
24
- --c64-white: #ffffff;
25
- --c64-gray: #7869c4;
26
- --bg-dark: #0a0a0f;
27
- --bg-medium: #1a1a2e;
28
- --bg-light: #16213e;
29
- }
30
-
31
- body {
32
- font-family: 'Courier New', monospace;
33
- background: linear-gradient(135deg, var(--bg-dark) 0%, var(--bg-medium) 100%);
34
- color: var(--c64-white);
35
- min-height: 100vh;
36
- overflow-x: hidden;
37
- position: relative;
38
- }
39
-
40
- body::before {
41
- content: '';
42
- position: fixed;
43
- top: 0;
44
- left: 0;
45
- right: 0;
46
- bottom: 0;
47
- background:
48
- repeating-linear-gradient(0deg,
49
- transparent,
50
- transparent 2px,
51
- rgba(0, 160, 160, 0.03) 2px,
52
- rgba(0, 160, 160, 0.03) 4px);
53
- pointer-events: none;
54
- z-index: 1;
55
- }
56
-
57
- .container {
58
- max-width: 1400px;
59
- margin: 0 auto;
60
- padding: 20px;
61
- position: relative;
62
- z-index: 2;
63
- }
64
-
65
- header {
66
- text-align: center;
67
- padding: 30px 0;
68
- background: linear-gradient(135deg, var(--c64-blue), var(--c64-lightblue));
69
- border-radius: 15px;
70
- margin-bottom: 30px;
71
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
72
- position: relative;
73
- overflow: hidden;
74
- }
75
-
76
- header::before {
77
- content: '';
78
- position: absolute;
79
- top: -50%;
80
- left: -50%;
81
- width: 200%;
82
- height: 200%;
83
- background: repeating-linear-gradient(45deg,
84
- transparent,
85
- transparent 10px,
86
- rgba(255, 255, 255, 0.05) 10px,
87
- rgba(255, 255, 255, 0.05) 20px);
88
- animation: slide 20s linear infinite;
89
- }
90
-
91
- @keyframes slide {
92
- 0% { transform: translate(0, 0); }
93
- 100% { transform: translate(50px, 50px); }
94
- }
95
-
96
- h1 {
97
- font-size: 3em;
98
- text-shadow: 3px 3px 0 var(--c64-purple),
99
- 6px 6px 0 var(--c64-pink),
100
- 9px 9px 20px rgba(0, 0, 0, 0.5);
101
- position: relative;
102
- z-index: 1;
103
- letter-spacing: 5px;
104
- margin-bottom: 10px;
105
- }
106
-
107
- .subtitle {
108
- font-size: 1.2em;
109
- color: var(--c64-yellow);
110
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
111
- position: relative;
112
- z-index: 1;
113
- }
114
-
115
- .credits {
116
- margin-top: 15px;
117
- font-size: 0.9em;
118
- color: var(--c64-cyan);
119
- position: relative;
120
- z-index: 1;
121
- }
122
-
123
- .credits a {
124
- color: var(--c64-yellow);
125
- text-decoration: none;
126
- transition: all 0.3s;
127
- }
128
-
129
- .credits a:hover {
130
- color: var(--c64-orange);
131
- text-shadow: 0 0 10px var(--c64-orange);
132
- }
133
-
134
- .controls {
135
- display: grid;
136
- grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
137
- gap: 20px;
138
- margin-bottom: 30px;
139
- }
140
-
141
- .control-group {
142
- background: linear-gradient(135deg, var(--bg-light), var(--bg-medium));
143
- padding: 20px;
144
- border-radius: 10px;
145
- border: 2px solid var(--c64-cyan);
146
- box-shadow: 0 5px 15px rgba(0, 160, 160, 0.3);
147
- transition: transform 0.3s, box-shadow 0.3s;
148
- }
149
-
150
- .control-group:hover {
151
- transform: translateY(-2px);
152
- box-shadow: 0 8px 25px rgba(0, 160, 160, 0.5);
153
- }
154
-
155
- .control-group h3 {
156
- color: var(--c64-cyan);
157
- margin-bottom: 15px;
158
- font-size: 1.2em;
159
- text-transform: uppercase;
160
- letter-spacing: 2px;
161
- }
162
-
163
- .btn {
164
- background: linear-gradient(135deg, var(--c64-purple), var(--c64-pink));
165
- color: var(--c64-white);
166
- border: none;
167
- padding: 12px 25px;
168
- border-radius: 8px;
169
- font-size: 1em;
170
- font-weight: bold;
171
- cursor: pointer;
172
- transition: all 0.3s;
173
- text-transform: uppercase;
174
- letter-spacing: 1px;
175
- box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
176
- margin: 5px;
177
- }
178
-
179
- .btn:hover {
180
- transform: translateY(-2px);
181
- box-shadow: 0 6px 20px rgba(255, 0, 255, 0.4);
182
- background: linear-gradient(135deg, var(--c64-pink), var(--c64-purple));
183
- }
184
-
185
- .btn:active {
186
- transform: translateY(0);
187
- }
188
-
189
- .btn.active {
190
- background: linear-gradient(135deg, var(--c64-green), var(--c64-yellow));
191
- box-shadow: 0 0 20px rgba(0, 255, 0, 0.5);
192
- }
193
-
194
- .slider-container {
195
- margin: 10px 0;
196
- }
197
-
198
- .slider-label {
199
- display: flex;
200
- justify-content: space-between;
201
- margin-bottom: 5px;
202
- font-size: 0.9em;
203
- color: var(--c64-yellow);
204
- }
205
-
206
- input[type="range"] {
207
- width: 100%;
208
- height: 8px;
209
- background: linear-gradient(90deg, var(--c64-blue), var(--c64-cyan));
210
- border-radius: 5px;
211
- outline: none;
212
- -webkit-appearance: none;
213
- }
214
-
215
- input[type="range"]::-webkit-slider-thumb {
216
- -webkit-appearance: none;
217
- width: 20px;
218
- height: 20px;
219
- background: var(--c64-yellow);
220
- border-radius: 50%;
221
- cursor: pointer;
222
- box-shadow: 0 0 10px rgba(255, 255, 0, 0.5);
223
- transition: all 0.3s;
224
- }
225
-
226
- input[type="range"]::-webkit-slider-thumb:hover {
227
- background: var(--c64-orange);
228
- box-shadow: 0 0 20px rgba(255, 119, 0, 0.8);
229
- }
230
-
231
- .tracks-container {
232
- background: linear-gradient(135deg, var(--bg-light), var(--bg-medium));
233
- border-radius: 15px;
234
- padding: 20px;
235
- border: 3px solid var(--c64-cyan);
236
- box-shadow: 0 10px 30px rgba(0, 160, 160, 0.3);
237
- }
238
-
239
- .tracks-header {
240
- display: grid;
241
- grid-template-columns: 80px repeat(4, 1fr) 100px;
242
- gap: 10px;
243
- margin-bottom: 15px;
244
- padding: 10px;
245
- background: rgba(0, 0, 0, 0.3);
246
- border-radius: 8px;
247
- }
248
-
249
- .header-cell {
250
- color: var(--c64-yellow);
251
- font-weight: bold;
252
- text-align: center;
253
- font-size: 0.9em;
254
- text-transform: uppercase;
255
- letter-spacing: 1px;
256
- }
257
-
258
- .track-row {
259
- display: grid;
260
- grid-template-columns: 80px repeat(4, 1fr) 100px;
261
- gap: 10px;
262
- margin-bottom: 10px;
263
- padding: 10px;
264
- background: rgba(0, 0, 0, 0.2);
265
- border-radius: 8px;
266
- transition: all 0.3s;
267
- align-items: center;
268
- }
269
-
270
- .track-row:hover {
271
- background: rgba(0, 160, 160, 0.1);
272
- transform: translateX(5px);
273
- }
274
-
275
- .track-row.playing {
276
- background: rgba(255, 255, 0, 0.1);
277
- box-shadow: 0 0 15px rgba(255, 255, 0, 0.3);
278
- animation: pulse 0.5s ease-in-out infinite alternate;
279
- }
280
-
281
- @keyframes pulse {
282
- from { opacity: 0.8; }
283
- to { opacity: 1; }
284
- }
285
-
286
- .track-number {
287
- color: var(--c64-cyan);
288
- font-weight: bold;
289
- text-align: center;
290
- font-size: 1.2em;
291
- }
292
-
293
- .instrument-cell {
294
- display: flex;
295
- align-items: center;
296
- justify-content: center;
297
- gap: 5px;
298
- }
299
-
300
- .instrument-indicator {
301
- width: 12px;
302
- height: 12px;
303
- border-radius: 50%;
304
- background: var(--c64-gray);
305
- transition: all 0.3s;
306
- }
307
-
308
- .instrument-indicator.active {
309
- box-shadow: 0 0 10px currentColor;
310
- animation: blink 0.5s ease-in-out infinite;
311
- }
312
-
313
- .instrument-indicator.lead { background: var(--c64-yellow); }
314
- .instrument-indicator.bass { background: var(--c64-red); }
315
- .instrument-indicator.arpeggio { background: var(--c64-green); }
316
- .instrument-indicator.drums { background: var(--c64-purple); }
317
-
318
- @keyframes blink {
319
- 0%, 100% { opacity: 1; }
320
- 50% { opacity: 0.3; }
321
- }
322
-
323
- .track-toggle {
324
- background: var(--c64-blue);
325
- color: var(--c64-white);
326
- border: 2px solid var(--c64-cyan);
327
- padding: 8px 15px;
328
- border-radius: 5px;
329
- cursor: pointer;
330
- transition: all 0.3s;
331
- font-size: 0.9em;
332
- font-weight: bold;
333
- }
334
-
335
- .track-toggle:hover {
336
- background: var(--c64-cyan);
337
- transform: scale(1.05);
338
- }
339
-
340
- .track-toggle.enabled {
341
- background: var(--c64-green);
342
- border-color: var(--c64-yellow);
343
- }
344
-
345
- .visualizer {
346
- margin-top: 30px;
347
- padding: 20px;
348
- background: linear-gradient(135deg, var(--bg-light), var(--bg-medium));
349
- border-radius: 15px;
350
- border: 2px solid var(--c64-cyan);
351
- height: 200px;
352
- position: relative;
353
- overflow: hidden;
354
- }
355
-
356
- .visualizer canvas {
357
- width: 100%;
358
- height: 100%;
359
- display: block;
360
- }
361
-
362
- .pattern-display {
363
- position: absolute;
364
- top: 10px;
365
- right: 10px;
366
- background: rgba(0, 0, 0, 0.7);
367
- padding: 10px;
368
- border-radius: 5px;
369
- color: var(--c64-yellow);
370
- font-size: 0.9em;
371
- }
372
-
373
- .pattern-selector {
374
- background: linear-gradient(135deg, var(--bg-light), var(--bg-medium));
375
- padding: 15px;
376
- border-radius: 10px;
377
- border: 2px solid var(--c64-cyan);
378
- margin-bottom: 20px;
379
- }
380
-
381
- .pattern-selector h3 {
382
- color: var(--c64-cyan);
383
- margin-bottom: 10px;
384
- font-size: 1.1em;
385
- text-transform: uppercase;
386
- letter-spacing: 2px;
387
- }
388
-
389
- .pattern-grid {
390
- display: grid;
391
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
392
- gap: 10px;
393
- }
394
-
395
- .pattern-btn {
396
- background: linear-gradient(135deg, var(--c64-blue), var(--c64-purple));
397
- color: var(--c64-white);
398
- border: 2px solid var(--c64-cyan);
399
- padding: 10px 15px;
400
- border-radius: 8px;
401
- cursor: pointer;
402
- transition: all 0.3s;
403
- font-size: 0.9em;
404
- font-weight: bold;
405
- text-align: center;
406
- }
407
-
408
- .pattern-btn:hover {
409
- background: linear-gradient(135deg, var(--c64-cyan), var(--c64-blue));
410
- transform: scale(1.05);
411
- box-shadow: 0 5px 15px rgba(0, 160, 160, 0.4);
412
- }
413
-
414
- .pattern-btn.active {
415
- background: linear-gradient(135deg, var(--c64-green), var(--c64-yellow));
416
- border-color: var(--c64-yellow);
417
- box-shadow: 0 0 20px rgba(0, 255, 0, 0.5);
418
- }
419
-
420
- @media (max-width: 768px) {
421
- h1 { font-size: 2em; }
422
- .tracks-header, .track-row {
423
- grid-template-columns: 60px repeat(4, 1fr) 80px;
424
- font-size: 0.8em;
425
- }
426
- .controls { grid-template-columns: 1fr; }
427
- .pattern-grid { grid-template-columns: repeat(2, 1fr); }
428
- }
429
-
430
- .loading-overlay {
431
- position: fixed;
432
- top: 0;
433
- left: 0;
434
- right: 0;
435
- bottom: 0;
436
- background: rgba(0, 0, 0, 0.9);
437
- display: flex;
438
- align-items: center;
439
- justify-content: center;
440
- z-index: 1000;
441
- opacity: 0;
442
- pointer-events: none;
443
- transition: opacity 0.3s;
444
- }
445
-
446
- .loading-overlay.active {
447
- opacity: 1;
448
- pointer-events: all;
449
- }
450
-
451
- .loading-text {
452
- color: var(--c64-cyan);
453
- font-size: 2em;
454
- animation: flash 0.5s ease-in-out infinite;
455
- }
456
-
457
- @keyframes flash {
458
- 0%, 100% { opacity: 1; }
459
- 50% { opacity: 0.5; }
460
- }
461
-
462
- .info-panel {
463
- background: rgba(0, 0, 0, 0.5);
464
- padding: 10px;
465
- border-radius: 8px;
466
- margin-top: 10px;
467
- font-size: 0.9em;
468
- color: var(--c64-cyan);
469
- border: 1px solid var(--c64-cyan);
470
- }
471
-
472
- .info-panel p {
473
- margin: 5px 0;
474
- }
475
- </style>
476
  </head>
477
  <body>
478
- <div class="loading-overlay" id="loadingOverlay">
479
- <div class="loading-text">INITIALIZING SID CHIP...</div>
480
- </div>
481
-
482
- <div class="container">
483
- <header>
484
- <h1>🎵 SID CHIP TUNE STUDIO 🎵</h1>
485
- <div class="subtitle">8-Bit Music Synthesizer with Pattern Library</div>
486
- <div class="credits">
487
- Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a>
488
- </div>
489
- </header>
490
-
491
- <div class="pattern-selector">
492
- <h3>📚 Pattern Library</h3>
493
- <div class="pattern-grid" id="patternGrid">
494
- <button class="pattern-btn active" onclick="loadPattern('arcade')">🕹️ Arcade</button>
495
- <button class="pattern-btn" onclick="loadPattern('demoscene')">🎨 Demoscene</button>
496
- <button class="pattern-btn" onclick="loadPattern('techno')">🎛️ Techno</button>
497
- <button class="pattern-btn" onclick="loadPattern('chiptune')">🎵 Chiptune</button>
498
- <button class="pattern-btn" onclick="loadPattern('platformer')">🏃 Platformer</button>
499
- <button class="pattern-btn" onclick="loadPattern('shooter')">🚀 Shooter</button>
500
- <button class="pattern-btn" onclick="loadPattern('puzzle')">🧩 Puzzle</button>
501
- <button class="pattern-btn" onclick="loadPattern('random')">🎲 Random</button>
502
- </div>
503
- <div class="info-panel">
504
- <p>Current Pattern: <span id="currentPatternName" style="color: var(--c64-yellow);">Arcade</span></p>
505
- <p id="patternDescription">Classic arcade game style with upbeat rhythms</p>
506
- </div>
507
  </div>
508
 
509
- <div class="controls">
510
- <div class="control-group">
511
- <h3>🎛️ Master Controls</h3>
512
- <button class="btn" id="playBtn" onclick="togglePlay()">▶ PLAY</button>
513
- <button class="btn" id="stopBtn" onclick="stopMusic()">⬛ STOP</button>
514
- <button class="btn" id="randomBtn" onclick="randomizeTracks()">🎲 RANDOM</button>
515
- </div>
516
-
517
- <div class="control-group">
518
- <h3>🎵 Tempo & Volume</h3>
519
- <div class="slider-container">
520
- <div class="slider-label">
521
- <span>BPM</span>
522
- <span id="bpmValue">120</span>
523
- </div>
524
- <input type="range" id="bpmSlider" min="60" max="200" value="120" oninput="updateBPM(this.value)">
525
  </div>
526
- <div class="slider-container">
527
- <div class="slider-label">
528
- <span>Volume</span>
529
- <span id="volumeValue">70%</span>
530
- </div>
531
- <input type="range" id="volumeSlider" min="0" max="100" value="70" oninput="updateVolume(this.value)">
 
 
 
 
 
 
 
532
  </div>
533
- </div>
534
-
535
- <div class="control-group">
536
- <h3>🎹 Instruments</h3>
537
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 5px;">
538
- <button class="btn active" id="leadBtn" onclick="toggleInstrument('lead')">LEAD</button>
539
- <button class="btn active" id="bassBtn" onclick="toggleInstrument('bass')">BASS</button>
540
- <button class="btn active" id="arpBtn" onclick="toggleInstrument('arpeggio')">ARP</button>
541
- <button class="btn active" id="drumBtn" onclick="toggleInstrument('drums')">DRUMS</button>
 
 
 
 
 
542
  </div>
543
- </div>
544
- </div>
545
 
546
- <div class="tracks-container">
547
- <div class="tracks-header">
548
- <div class="header-cell">TRACK</div>
549
- <div class="header-cell">LEAD</div>
550
- <div class="header-cell">BASS</div>
551
- <div class="header-cell">ARPEGGIO</div>
552
- <div class="header-cell">DRUMS</div>
553
- <div class="header-cell">ENABLE</div>
554
- </div>
555
- <div id="tracksGrid"></div>
 
 
 
 
 
 
 
 
 
556
  </div>
557
 
558
- <div class="visualizer">
559
- <canvas id="visualizer"></canvas>
560
- <div class="pattern-display">
561
- <div>Pattern: <span id="currentPattern">0</span>/10</div>
562
- <div>Beat: <span id="currentBeat">0</span>/4</div>
563
- </div>
 
 
 
564
  </div>
565
- </div>
566
-
567
- <script>
568
- // Audio Context and Global Variables
569
- let audioContext;
570
- let isPlaying = false;
571
- let currentTrack = 0;
572
- let currentBeat = 0;
573
- let bpm = 120;
574
- let masterVolume = 0.7;
575
- let intervalId;
576
- let analyser;
577
- let dataArray;
578
- let animationId;
579
- let currentPattern = 'arcade';
580
-
581
- // Instrument states
582
- const instruments = {
583
- lead: true,
584
- bass: true,
585
- arpeggio: true,
586
- drums: true
587
- };
588
-
589
- // Track states
590
- const trackStates = [];
591
-
592
- // Note frequencies
593
- const noteFrequencies = {
594
- 'C1': 32.70, 'C#1': 34.65, 'D1': 36.71, 'D#1': 38.89, 'E1': 41.20, 'F1': 43.65, 'F#1': 46.25, 'G1': 49.00, 'G#1': 51.91, 'A1': 55.00, 'A#1': 58.27, 'B1': 61.74,
595
- 'C2': 65.41, 'C#2': 69.30, 'D2': 73.42, 'D#2': 77.78, 'E2': 82.41, 'F2': 87.31, 'F#2': 92.50, 'G2': 98.00, 'G#2': 103.83, 'A2': 110.00, 'A#2': 116.54, 'B2': 123.47,
596
- 'C3': 130.81, 'C#3': 138.59, 'D3': 146.83, 'D#3': 155.56, 'E3': 164.81, 'F3': 174.61, 'F#3': 185.00, 'G3': 196.00, 'G#3': 207.65, 'A3': 220.00, 'A#3': 233.08, 'B3': 246.94,
597
- 'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13, 'E4': 329.63, 'F4': 349.23, 'F#4': 369.99, 'G4': 392.00, 'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88,
598
- 'C5': 523.25, 'C#5': 554.37, 'D5': 587.33, 'D#5': 622.25, 'E5': 659.25, 'F5': 698.46, 'F#5': 739.99, 'G5': 783.99, 'G#5': 830.61, 'A5': 880.00, 'A#5': 932.33, 'B5': 987.77,
599
- 'C6': 1046.50, 'D6': 1174.66, 'E6': 1318.51, 'G6': 1567.98
600
- };
601
-
602
- // Pattern Library
603
- const patternLibrary = {
604
- arcade: {
605
- name: "Arcade",
606
- description: "Classic arcade game style with upbeat rhythms",
607
- tracks: [
608
- {
609
- lead: ['E4', null, 'G4', null, 'A4', null, 'C5', null, 'E5', null, 'C5', null, 'A4', null, 'G4', null],
610
- bass: ['C2', null, 'C2', null, 'G2', null, 'G2', null, 'C3', null, 'C3', null, 'G2', null, 'G2', null],
611
- arpeggio: ['C3', 'E3', 'G3', 'C4', 'E4', 'G4', 'C5', 'E5', null, null, null, null, null, null, null, null],
612
- drums: ['C1', null, null, 'C#1', 'C1', null, 'D1', null, 'C1', null, null, 'C#1', 'C1', null, 'D1', null]
613
- },
614
- {
615
- lead: ['G4', null, 'B4', null, 'D5', null, 'G5', null, 'B5', null, 'G5', null, 'D5', null, 'B4', null],
616
- bass: ['G2', null, 'G2', null, 'D3', null, 'D3', null, 'G3', null, 'G3', null, 'D3', null, 'D3', null],
617
- arpeggio: ['G3', 'B3', 'D4', 'G4', 'B4', 'D5', 'G5', 'B5', null, null, null, null, null, null, null, null],
618
- drums: ['C1', null, 'C#1', null, 'C1', null, 'D1', null, 'C1', null, 'C#1', null, 'C1', null, 'D1', null]
619
- },
620
- {
621
- lead: ['A4', null, 'C5', null, 'E5', null, 'A5', null, 'E5', null, 'C5', null, 'A4', null, 'E4', null],
622
- bass: ['A2', null, 'A2', null, 'E3', null, 'E3', null, 'A3', null, 'A3', null, 'E3', null, 'E3', null],
623
- arpeggio: ['A3', 'C4', 'E4', 'A4', 'C5', 'E5', 'A5', 'C6', null, null, null, null, null, null, null, null],
624
- drums: ['D1', null, null, 'C#1', 'D1', null, 'C1', null, 'D1', null, null, 'C#1', 'D1', null, 'C1', null]
625
- },
626
- {
627
- lead: ['F4', null, 'A4', null, 'C5', null, 'F5', null, 'A5', null, 'F5', null, 'C5', null, 'A4', null],
628
- bass: ['F2', null, 'F2', null, 'C3', null, 'C3', null, 'F3', null, 'F3', null, 'C3', null, 'C3', null],
629
- arpeggio: ['F3', 'A3', 'C4', 'F4', 'A4', 'C5', 'F5', 'A5', null, null, null, null, null, null, null, null],
630
- drums: ['C1', 'C#1', null, null, 'C1', 'C#1', null, null, 'C1', 'C#1', null, null, 'C1', 'C#1', null, null]
631
- },
632
- {
633
- lead: ['C5', null, 'E5', null, 'G5', null, 'C6', null, 'G5', null, 'E5', null, 'C5', null, 'G4', null],
634
- bass: ['C3', null, 'C3', null, 'G3', null, 'G3', null, 'C4', null, 'C4', null, 'G3', null, 'G3', null],
635
- arpeggio: ['C4', 'E4', 'G4', 'C5', 'E5', 'G5', 'C6', 'E6', null, null, null, null, null, null, null, null],
636
- drums: ['C#1', null, 'D1', null, 'C#1', null, 'D1', null, 'C#1', null, 'D1', null, 'C#1', null, 'D1', null]
637
- },
638
- {
639
- lead: ['D5', null, 'F5', null, 'A5', null, 'D6', null, 'A5', null, 'F5', null, 'D5', null, 'A4', null],
640
- bass: ['D3', null, 'D3', null, 'A3', null, 'A3', null, 'D4', null, 'D4', null, 'A3', null, 'A3', null],
641
- arpeggio: ['D4', 'F4', 'A4', 'D5', 'F5', 'A5', 'D6', 'F6', null, null, null, null, null, null, null, null],
642
- drums: ['D1', 'C#1', 'C1', null, 'D1', 'C#1', 'C1', null, 'D1', 'C#1', 'C1', null, 'D1', 'C#1', 'C1', null]
643
- },
644
- {
645
- lead: ['E5', null, 'G5', null, 'B5', null, 'E6', null, 'B5', null, 'G5', null, 'E5', null, 'B4', null],
646
- bass: ['E3', null, 'E3', null, 'B3', null, 'B3', null, 'E4', null, 'E4', null, 'B3', null, 'B3', null],
647
- arpeggio: ['E4', 'G4', 'B4', 'E5', 'G5', 'B5', 'E6', 'G6', null, null, null, null, null, null, null, null],
648
- drums: ['C1', null, null, 'D1', 'C1', null, null, 'D1', 'C1', null, null, 'D1', 'C1', null, null, 'D1']
649
- },
650
- {
651
- lead: ['G5', null, 'B5', null, 'D6', null, 'G6', null, 'D6', null, 'B5', null, 'G5', null, 'D5', null],
652
- bass: ['G3', null, 'G3', null, 'D4', null, 'D4', null, 'G4', null, 'G4', null, 'D4', null, 'D4', null],
653
- arpeggio: ['G4', 'B4', 'D5', 'G5', 'B5', 'D6', 'G6', 'B6', null, null, null, null, null, null, null, null],
654
- drums: ['D1', null, 'C#1', null, 'D1', null, 'C#1', null, 'D1', null, 'C#1', null, 'D1', null, 'C#1', null]
655
- },
656
- {
657
- lead: ['A5', null, 'C6', null, 'E6', null, 'A6', null, 'E6', null, 'C6', null, 'A5', null, 'E5', null],
658
- bass: ['A3', null, 'A3', null, 'E4', null, 'E4', null, 'A4', null, 'A4', null, 'E4', null, 'E4', null],
659
- arpeggio: ['A4', 'C5', 'E5', 'A5', 'C6', 'E6', 'A6', 'C7', null, null, null, null, null, null, null, null],
660
- drums: ['C#1', 'D1', null, null, 'C#1', 'D1', null, null, 'C#1', 'D1', null, null, 'C#1', 'D1', null, null]
661
- },
662
- {
663
- lead: ['F5', null, 'A5', null, 'C6', null, 'F6', null, 'C6', null, 'A5', null, 'F5', null, 'C5', null],
664
- bass: ['F3', null, 'F3', null, 'C4', null, 'C4', null, 'F4', null, 'F4', null, 'C4', null, 'C4', null],
665
- arpeggio: ['F4', 'A4', 'C5', 'F5', 'A5', 'C6', 'F6', 'A6', null, null, null, null, null, null, null, null],
666
- drums: ['D1', null, 'C1', null, 'D1', null, 'C1', null, 'D1', null, 'C1', null, 'D1', null, 'C1', null]
667
- }
668
- ]
669
- },
670
- demoscene: {
671
- name: "Demoscene",
672
- description: "Complex demoscene style with fast arpeggios and effects",
673
- tracks: [
674
- {
675
- lead: ['C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'D5', 'E5', 'F5', 'G5', 'A5', 'B5', 'C6', 'D6'],
676
- bass: ['C1', null, 'G1', null, 'C2', null, 'G2', null, 'C3', null, 'G3', null, 'C4', null, 'G4', null],
677
- arpeggio: ['C2', 'E2', 'G2', 'C3', 'E3', 'G3', 'C4', 'E4', 'G4', 'C5', 'E5', 'G5', 'C6', 'E6', 'G6', 'C7'],
678
- drums: ['C1', 'C#1', 'D1', 'C#1', 'C1', 'C#1', 'D1', 'C#1', 'C1', 'C#1', 'D1', 'C#1', 'C1', 'C#1', 'D1', 'C#1']
679
- },
680
- {
681
- lead: ['D4', 'E4', 'F4', 'G4', 'A4', 'B4', 'C5', 'D5', 'E5', 'F5', 'G5', 'A5', 'B5', 'C6', 'D6', 'E6'],
682
- bass: ['D1', null, 'A1', null, 'D2', null, 'A2', null, 'D3', null
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>3D Physics Playground</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ overflow: hidden;
18
+ position: relative;
19
+ }
20
+
21
+ #canvas {
22
+ display: block;
23
+ cursor: grab;
24
+ }
25
+
26
+ #canvas:active {
27
+ cursor: grabbing;
28
+ }
29
+
30
+ .ui-panel {
31
+ position: absolute;
32
+ top: 20px;
33
+ left: 20px;
34
+ background: rgba(255, 255, 255, 0.95);
35
+ backdrop-filter: blur(10px);
36
+ border-radius: 15px;
37
+ padding: 20px;
38
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
39
+ min-width: 280px;
40
+ z-index: 100;
41
+ transition: transform 0.3s ease;
42
+ }
43
+
44
+ .ui-panel:hover {
45
+ transform: translateY(-2px);
46
+ box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
47
+ }
48
+
49
+ .panel-header {
50
+ display: flex;
51
+ align-items: center;
52
+ margin-bottom: 20px;
53
+ padding-bottom: 15px;
54
+ border-bottom: 2px solid #e0e0e0;
55
+ }
56
+
57
+ .panel-title {
58
+ font-size: 1.4em;
59
+ font-weight: bold;
60
+ background: linear-gradient(135deg, #667eea, #764ba2);
61
+ -webkit-background-clip: text;
62
+ -webkit-text-fill-color: transparent;
63
+ flex: 1;
64
+ }
65
+
66
+ .controls-group {
67
+ margin-bottom: 20px;
68
+ }
69
+
70
+ .control-label {
71
+ display: block;
72
+ margin-bottom: 8px;
73
+ color: #555;
74
+ font-size: 0.9em;
75
+ font-weight: 600;
76
+ text-transform: uppercase;
77
+ letter-spacing: 0.5px;
78
+ }
79
+
80
+ .button-grid {
81
+ display: grid;
82
+ grid-template-columns: repeat(2, 1fr);
83
+ gap: 10px;
84
+ margin-bottom: 15px;
85
+ }
86
+
87
+ .btn {
88
+ background: linear-gradient(135deg, #667eea, #764ba2);
89
+ color: white;
90
+ border: none;
91
+ padding: 12px 20px;
92
+ border-radius: 8px;
93
+ cursor: pointer;
94
+ font-size: 0.95em;
95
+ font-weight: 600;
96
+ transition: all 0.3s ease;
97
+ position: relative;
98
+ overflow: hidden;
99
+ }
100
+
101
+ .btn::before {
102
+ content: '';
103
+ position: absolute;
104
+ top: 50%;
105
+ left: 50%;
106
+ width: 0;
107
+ height: 0;
108
+ border-radius: 50%;
109
+ background: rgba(255, 255, 255, 0.3);
110
+ transform: translate(-50%, -50%);
111
+ transition: width 0.6s, height 0.6s;
112
+ }
113
+
114
+ .btn:hover::before {
115
+ width: 300px;
116
+ height: 300px;
117
+ }
118
+
119
+ .btn:hover {
120
+ transform: translateY(-2px);
121
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
122
+ }
123
+
124
+ .btn:active {
125
+ transform: translateY(0);
126
+ }
127
+
128
+ .btn.active {
129
+ background: linear-gradient(135deg, #f093fb, #f5576c);
130
+ }
131
+
132
+ .slider-container {
133
+ margin-bottom: 15px;
134
+ }
135
+
136
+ .slider {
137
+ width: 100%;
138
+ height: 6px;
139
+ border-radius: 3px;
140
+ background: #e0e0e0;
141
+ outline: none;
142
+ -webkit-appearance: none;
143
+ }
144
+
145
+ .slider::-webkit-slider-thumb {
146
+ -webkit-appearance: none;
147
+ appearance: none;
148
+ width: 20px;
149
+ height: 20px;
150
+ border-radius: 50%;
151
+ background: linear-gradient(135deg, #667eea, #764ba2);
152
+ cursor: pointer;
153
+ transition: all 0.3s ease;
154
+ }
155
+
156
+ .slider::-webkit-slider-thumb:hover {
157
+ transform: scale(1.2);
158
+ box-shadow: 0 0 10px rgba(102, 126, 234, 0.5);
159
+ }
160
+
161
+ .slider-value {
162
+ display: inline-block;
163
+ margin-left: 10px;
164
+ color: #667eea;
165
+ font-weight: bold;
166
+ }
167
+
168
+ .stats {
169
+ background: rgba(0, 0, 0, 0.7);
170
+ color: white;
171
+ padding: 15px;
172
+ border-radius: 10px;
173
+ font-family: 'Courier New', monospace;
174
+ font-size: 0.9em;
175
+ line-height: 1.6;
176
+ }
177
+
178
+ .stat-row {
179
+ display: flex;
180
+ justify-content: space-between;
181
+ margin-bottom: 5px;
182
+ }
183
+
184
+ .stat-label {
185
+ color: #aaa;
186
+ }
187
+
188
+ .stat-value {
189
+ color: #4fc3f7;
190
+ font-weight: bold;
191
+ }
192
+
193
+ .info-panel {
194
+ position: absolute;
195
+ bottom: 20px;
196
+ left: 20px;
197
+ background: rgba(255, 255, 255, 0.95);
198
+ backdrop-filter: blur(10px);
199
+ padding: 15px 20px;
200
+ border-radius: 10px;
201
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
202
+ max-width: 400px;
203
+ }
204
+
205
+ .info-text {
206
+ color: #666;
207
+ font-size: 0.9em;
208
+ line-height: 1.5;
209
+ }
210
+
211
+ .info-text strong {
212
+ color: #667eea;
213
+ }
214
+
215
+ .color-picker {
216
+ display: flex;
217
+ gap: 8px;
218
+ flex-wrap: wrap;
219
+ margin-top: 10px;
220
+ }
221
+
222
+ .color-option {
223
+ width: 30px;
224
+ height: 30px;
225
+ border-radius: 50%;
226
+ cursor: pointer;
227
+ transition: all 0.3s ease;
228
+ border: 2px solid transparent;
229
+ }
230
+
231
+ .color-option:hover {
232
+ transform: scale(1.1);
233
+ border-color: #333;
234
+ }
235
+
236
+ .color-option.selected {
237
+ border-color: #667eea;
238
+ box-shadow: 0 0 10px currentColor;
239
+ }
240
+
241
+ @media (max-width: 768px) {
242
+ .ui-panel {
243
+ left: 10px;
244
+ top: 10px;
245
+ min-width: 250px;
246
+ padding: 15px;
247
+ }
248
+
249
+ .button-grid {
250
+ grid-template-columns: 1fr;
251
+ }
252
+
253
+ .info-panel {
254
+ display: none;
255
+ }
256
+ }
257
+
258
+ .loading {
259
+ position: fixed;
260
+ top: 0;
261
+ left: 0;
262
+ right: 0;
263
+ bottom: 0;
264
+ background: linear-gradient(135deg, #667eea, #764ba2);
265
+ display: flex;
266
+ align-items: center;
267
+ justify-content: center;
268
+ z-index: 1000;
269
+ transition: opacity 0.5s ease;
270
+ }
271
+
272
+ .loading.hidden {
273
+ opacity: 0;
274
+ pointer-events: none;
275
+ }
276
+
277
+ .loading-text {
278
+ color: white;
279
+ font-size: 2em;
280
+ font-weight: bold;
281
+ animation: pulse 1.5s ease-in-out infinite;
282
+ }
283
+
284
+ @keyframes pulse {
285
+ 0%, 100% { opacity: 1; }
286
+ 50% { opacity: 0.5; }
287
+ }
288
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  </head>
290
  <body>
291
+ <div class="loading" id="loading">
292
+ <div class="loading-text">Loading Physics Engine...</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  </div>
294
 
295
+ <canvas id="canvas"></canvas>
296
+
297
+ <div class="ui-panel">
298
+ <div class="panel-header">
299
+ <div class="panel-title">🎮 Physics Controls</div>
300
+ </div>
301
+
302
+ <div class="controls-group">
303
+ <label class="control-label">Spawn Objects</label>
304
+ <div class="button-grid">
305
+ <button class="btn" onclick="spawnObject('sphere')">⚪ Sphere</button>
306
+ <button class="btn" onclick="spawnObject('box')">📦 Box</button>
307
+ <button class="btn" onclick="spawnObject('cylinder')">🥤 Cylinder</button>
308
+ <button class="btn" onclick="spawnObject('cone')">🔺 Cone</button>
309
+ </div>
 
310
  </div>
311
+
312
+ <div class="controls-group">
313
+ <label class="control-label">Object Color</label>
314
+ <div class="color-picker">
315
+ <div class="color-option selected" style="background: #ff6b6b" onclick="selectColor('#ff6b6b')"></div>
316
+ <div class="color-option" style="background: #4ecdc4" onclick="selectColor('#4ecdc4')"></div>
317
+ <div class="color-option" style="background: #45b7d1" onclick="selectColor('#45b7d1')"></div>
318
+ <div class="color-option" style="background: #96ceb4" onclick="selectColor('#96ceb4')"></div>
319
+ <div class="color-option" style="background: #feca57" onclick="selectColor('#feca57')"></div>
320
+ <div class="color-option" style="background: #dfe6e9" onclick="selectColor('#dfe6e9')"></div>
321
+ <div class="color-option" style="background: #a29bfe" onclick="selectColor('#a29bfe')"></div>
322
+ <div class="color-option" style="background: #fd79a8" onclick="selectColor('#fd79a8')"></div>
323
+ </div>
324
  </div>
325
+
326
+ <div class="controls-group">
327
+ <div class="slider-container">
328
+ <label class="control-label">Gravity <span class="slider-value" id="gravityValue">-9.8</span></label>
329
+ <input type="range" class="slider" id="gravitySlider" min="-20" max="0" value="-9.8" step="0.1" oninput="updateGravity(this.value)">
330
+ </div>
331
+ <div class="slider-container">
332
+ <label class="control-label">Bounciness <span class="slider-value" id="bounceValue">0.7</span></label>
333
+ <input type="range" class="slider" id="bounceSlider" min="0" max="1" value="0.7" step="0.1" oninput="updateBounciness(this.value)">
334
+ </div>
335
+ <div class="slider-container">
336
+ <label class="control-label">Object Size <span class="slider-value" id="sizeValue">1.0</span></label>
337
+ <input type="range" class="slider" id="sizeSlider" min="0.5" max="3" value="1" step="0.1" oninput="updateSize(this.value)">
338
+ </div>
339
  </div>
 
 
340
 
341
+ <div class="controls-group">
342
+ <button class="btn" onclick="clearAllObjects()" style="width: 100%; background: linear-gradient(135deg, #f093fb, #f5576c);">🗑️ Clear All</button>
343
+ <button class="btn" onclick="togglePause()" id="pauseBtn" style="width: 100%; margin-top: 10px;">⏸️ Pause</button>
344
+ </div>
345
+
346
+ <div class="stats">
347
+ <div class="stat-row">
348
+ <span class="stat-label">Objects:</span>
349
+ <span class="stat-value" id="objectCount">0</span>
350
+ </div>
351
+ <div class="stat-row">
352
+ <span class="stat-label">FPS:</span>
353
+ <span class="stat-value" id="fps">60</span>
354
+ </div>
355
+ <div class="stat-row">
356
+ <span class="stat-label">Physics:</span>
357
+ <span class="stat-value" id="physicsStatus">Active</span>
358
+ </div>
359
+ </div>
360
  </div>
361
 
362
+ <div class="info-panel">
363
+ <div class="info-text">
364
+ <strong>Controls:</strong><br>
365
+ Click to spawn objects<br>
366
+ Drag to rotate camera<br>
367
+ • Scroll to zoom<br>
368
+ • Space to pause physics<br>
369
+ • R to reset camera
370
+ </div>
371
  </div>
372
+
373
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
374
+ <script>
375
+ // Global variables
376
+ let scene, camera, renderer, world;
377
+ let objects = [];
378
+ let isPaused = false;
379
+ let selectedColor = '#ff6b6b';
380
+ let objectSize = 1;
381
+ let bounciness = 0.7;
382
+ let gravity = -9.8;
383
+ let frameCount = 0;
384
+ let lastTime = performance.now();
385
+ let fps = 60;
386
+
387
+ // Physics properties
388
+ const physicsObjects = [];
389
+
390
+ // Initialize the scene
391
+ function init() {
392
+ // Create scene
393
+ scene = new THREE.Scene();
394
+ scene.fog = new THREE.Fog(0x000000, 10, 50);
395
+
396
+ // Create camera
397
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
398
+ camera.position.set(0, 10, 20);
399
+ camera.lookAt(0, 0, 0);
400
+
401
+ // Create renderer
402
+ renderer = new THREE.WebGLRenderer({
403
+ canvas: document.getElementById('canvas'),
404
+ antialias: true,
405
+ alpha: true
406
+ });
407
+ renderer.setSize(window.innerWidth, window.innerHeight);
408
+ renderer.shadowMap.enabled = true;
409
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
410
+
411
+ // Lighting
412
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
413
+ scene.add(ambientLight);
414
+
415
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
416
+ directionalLight.position.set(5, 10, 5);
417
+ directionalLight.castShadow = true;
418
+ directionalLight.shadow.camera.left = -20;
419
+ directionalLight.shadow.camera.right = 20;
420
+ directionalLight.shadow.camera.top = 20;
421
+ directionalLight.shadow.camera.bottom = -20;
422
+ scene.add(directionalLight);
423
+
424
+ const pointLight = new THREE.PointLight(0x667eea, 0.5);
425
+ pointLight.position.set(-5, 5, -5);
426
+ scene.add(pointLight);
427
+
428
+ // Create ground
429
+ const groundGeometry = new THREE.PlaneGeometry(50, 50);
430
+ const groundMaterial = new THREE.MeshStandardMaterial({
431
+ color: 0x2c3e50,
432
+ roughness: 0.8,
433
+ metalness: 0.2
434
+ });
435
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
436
+ ground.rotation.x = -Math.PI / 2;
437
+ ground.receiveShadow = true;
438
+ scene.add(ground);
439
+
440
+ // Create walls
441
+ createWalls();
442
+
443
+ // Mouse controls
444
+ setupControls();
445
+
446
+ // Start animation
447
+ animate();
448
+
449
+ // Hide loading screen
450
+ setTimeout(() => {
451
+ document.getElementById('loading').classList.add('hidden');
452
+ }, 500);
453
+
454
+ // Spawn initial objects
455
+ setTimeout(() => {
456
+ for (let i = 0; i < 3; i++) {
457
+ setTimeout(() => spawnObject('sphere'), i * 200);
458
+ }
459
+ }, 600);
460
+ }
461
+
462
+ function createWalls() {
463
+ const wallMaterial = new THREE.MeshStandardMaterial({
464
+ color: 0x34495e,
465
+ transparent: true,
466
+ opacity: 0.3
467
+ });
468
+
469
+ // Back wall
470
+ const backWall = new THREE.Mesh(
471
+ new THREE.PlaneGeometry(50, 20),
472
+ wallMaterial
473
+ );
474
+ backWall.position.z = -25;
475
+ backWall.position.y = 10;
476
+ scene.add(backWall);
477
+
478
+ // Side walls
479
+ const leftWall = new THREE.Mesh(
480
+ new THREE.PlaneGeometry(50, 20),
481
+ wallMaterial
482
+ );
483
+ leftWall.rotation.y = Math.PI / 2;
484
+ leftWall.position.x = -25;
485
+ leftWall.position.y = 10;
486
+ scene.add(leftWall);
487
+
488
+ const rightWall = new THREE.Mesh(
489
+ new THREE.PlaneGeometry(50, 20),
490
+ wallMaterial
491
+ );
492
+ rightWall.rotation.y = -Math.PI / 2;
493
+ rightWall.position.x = 25;
494
+ rightWall.position.y = 10;
495
+ scene.add(rightWall);
496
+ }
497
+
498
+ function setupControls() {
499
+ let mouseX = 0, mouseY = 0;
500
+ let targetRotationX = 0, targetRotationY = 0;
501
+ let isDragging = false;
502
+
503
+ renderer.domElement.addEventListener('mousedown', (e) => {
504
+ if (e.button === 0) {
505
+ isDragging = true;
506
+ mouseX = e.clientX;
507
+ mouseY = e.clientY;
508
+ }
509
+ });
510
+
511
+ renderer.domElement.addEventListener('mousemove', (e) => {
512
+ if (isDragging) {
513
+ const deltaX = e.clientX - mouseX;
514
+ const deltaY = e.clientY - mouseY;
515
+ targetRotationY += deltaX * 0.01;
516
+ targetRotationX += deltaY * 0.01;
517
+ targetRotationX = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, targetRotationX));
518
+ mouseX = e.clientX;
519
+ mouseY = e.clientY;
520
+ }
521
+ });
522
+
523
+ renderer.domElement.addEventListener('mouseup', () => {
524
+ isDragging = false;
525
+ });
526
+
527
+ renderer.domElement.addEventListener('click', (e) => {
528
+ if (!isDragging) {
529
+ const rect = renderer.domElement.getBoundingClientRect();
530
+ const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
531
+ const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
532
+
533
+ const vector = new THREE.Vector3(x, y, 0.5);
534
+ vector.unproject(camera);
535
+ const dir = vector.sub(camera.position).normalize();
536
+ const distance = -camera.position.y / dir.y;
537
+ const pos = camera.position.clone().add(dir.multiplyScalar(distance));
538
+
539
+ spawnObjectAt(pos);
540
+ }
541
+ });
542
+
543
+ renderer.domElement.addEventListener('wheel', (e) => {
544
+ camera.position.z += e.deltaY * 0.01;
545
+ camera.position.z = Math.max(5, Math.min(50, camera.position.z));
546
+ });
547
+
548
+ // Update camera rotation
549
+ function updateCamera() {
550
+ const radius = camera.position.length();
551
+ camera.position.x = radius * Math.sin(targetRotationY) * Math.cos(targetRotationX);
552
+ camera.position.y = radius * Math.sin(targetRotationX);
553
+ camera.position.z = radius * Math.cos(targetRotationY) * Math.cos(targetRotationX);
554
+ camera.lookAt(0, 0, 0);
555
+ requestAnimationFrame(updateCamera);
556
+ }
557
+ updateCamera();
558
+
559
+ // Keyboard controls
560
+ document.addEventListener('keydown', (e) => {
561
+ if (e.code === 'Space') {
562
+ e.preventDefault();
563
+ togglePause();
564
+ } else if (e.code === 'KeyR') {
565
+ camera.position.set(0, 10, 20);
566
+ targetRotationX = 0;
567
+ targetRotationY = 0;
568
+ }
569
+ });
570
+ }
571
+
572
+ function spawnObject(type) {
573
+ const x = (Math.random() - 0.5) * 10;
574
+ const y = 10 + Math.random() * 5;
575
+ const z = (Math.random() - 0.5) * 10;
576
+ spawnObjectAt(new THREE.Vector3(x, y, z), type);
577
+ }
578
+
579
+ function spawnObjectAt(position, type = 'sphere') {
580
+ let geometry;
581
+
582
+ switch(type) {
583
+ case 'box':
584
+ geometry = new THREE.BoxGeometry(objectSize, objectSize, objectSize);
585
+ break;
586
+ case 'cylinder':
587
+ geometry = new THREE.CylinderGeometry(objectSize/2, objectSize/2, objectSize, 32);
588
+ break;
589
+ case 'cone':
590
+ geometry = new THREE.ConeGeometry(objectSize/2, objectSize, 32);
591
+ break;
592
+ default:
593
+ geometry = new THREE.SphereGeometry(objectSize/2, 32, 32);
594
+ }
595
+
596
+ const material = new THREE.MeshStandardMaterial({
597
+ color: selectedColor,
598
+ roughness: 0.4,
599
+ metalness: 0.3
600
+ });
601
+
602
+ const mesh = new THREE.Mesh(geometry, material);
603
+ mesh.position.copy(position);
604
+ mesh.castShadow = true;
605
+ mesh.receiveShadow = true;
606
+ scene.add(mesh);
607
+
608
+ // Physics object
609
+ const physicsObj = {
610
+ mesh: mesh,
611
+ velocity: new THREE.Vector3(
612
+ (Math.random() - 0.5) * 2,
613
+ 0,
614
+ (Math.random() - 0.5) * 2
615
+ ),
616
+ angularVelocity: new THREE.Vector3(
617
+ (Math.random() - 0.5) * 0.1,
618
+ (Math.random() - 0.5) * 0.1,
619
+ (Math.random() - 0.5) * 0.1
620
+ ),
621
+ mass: objectSize
622
+ };
623
+
624
+ objects.push(physicsObj);
625
+ updateObjectCount();
626
+ }
627
+
628
+ function updatePhysics() {
629
+ if (isPaused) return;
630
+
631
+ const dt = 1/60;
632
+ const gravityVec = new THREE.Vector3(0, gravity, 0);
633
+
634
+ objects.forEach(obj => {
635
+ // Apply gravity
636
+ obj.velocity.add(gravityVec.clone().multiplyScalar(dt));
637
+
638
+ // Update position
639
+ obj.mesh.position.add(obj.velocity.clone().multiplyScalar(dt));
640
+
641
+ // Update rotation
642
+ obj.mesh.rotation.x += obj.angularVelocity.x;
643
+ obj.mesh.rotation.y += obj.angularVelocity.y;
644
+ obj.mesh.rotation.z += obj.angularVelocity.z;
645
+
646
+ // Ground collision
647
+ if (obj.mesh.position.y - objectSize/2 < 0) {
648
+ obj.mesh.position.y = objectSize/2;
649
+ obj.velocity.y *= -bounciness;
650
+ obj.angularVelocity.multiplyScalar(0.9);
651
+ }
652
+
653
+ // Wall collisions
654
+ const boundary = 25;
655
+ if (Math.abs(obj.mesh.position.x) > boundary - objectSize/2) {
656
+ obj.mesh.position.x = Math.sign(obj.mesh.position.x) * (boundary - objectSize/2);
657
+ obj.velocity.x *= -bounciness;
658
+ }
659
+ if (Math.abs(obj.mesh.position.z) > boundary - objectSize/2) {
660
+ obj.mesh.position.z = Math.sign(obj.mesh.position.z) * (boundary - objectSize/2);
661
+ obj.velocity.z *= -bounciness;
662
+ }
663
+
664
+ // Object-to-object collisions (simplified)
665
+ objects.forEach(other => {
666
+ if (obj !== other) {
667
+ const distance = obj.mesh.position.distanceTo(other.mesh.position);
668
+ const minDistance = objectSize;
669
+
670
+ if (distance < minDistance) {
671
+ const direction = obj.mesh.position.clone().sub(other.mesh.position).normalize();
672
+ obj.mesh.position.add(direction.multiplyScalar(minDistance - distance));
673
+
674
+ const relativeVelocity = obj.velocity.clone().sub(other.velocity);
675
+ const speed = relativeVelocity.dot(direction);
676
+
677
+ if (speed > 0) {
678
+ obj.velocity.sub(direction.multiplyScalar(speed * bounciness));
679
+ }
680
+ }
681
+ }
682
+ });
683
+
684
+ // Apply damping
685
+ obj.velocity.multiplyScalar(0.999);
686
+ obj.angularVelocity.multiplyScalar(0.995);
687
+ });
688
+ }
689
+
690
+ function animate() {
691
+ requestAnimationFrame(animate);
692
+
693
+ updatePhysics();
694
+ renderer.render(scene, camera);
695
+
696
+ // Calculate FPS
697
+ frameCount++;
698
+ const currentTime = performance.now();
699
+ if (currentTime >= lastTime + 1000) {
700
+ fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
701
+ document.getElementById('fps').textContent = fps;
702
+ frameCount = 0;
703
+ lastTime = currentTime;
704
+ }
705
+ }
706
+
707
+ function updateObjectCount() {
708
+ document.getElementById('objectCount').textContent = objects.length;
709
+ }
710
+
711
+ function clearAllObjects() {
712
+ objects.forEach(obj => {
713
+ scene.remove(obj.mesh);
714
+ obj.mesh.geometry.dispose();
715
+ obj.mesh.material.dispose();
716
+ });
717
+ objects = [];
718
+ updateObjectCount();
719
+ }
720
+
721
+ function togglePause() {
722
+ isPaused = !isPaused;
723
+ const btn = document.getElementById('pauseBtn');
724
+ btn.textContent = isPaused ? '▶️ Play' : '⏸️ Pause';
725
+ btn.classList.toggle('active', isPaused);
726
+ document.getElementById('physicsStatus').textContent = isPaused ? 'Paused' : 'Active';
727
+ }
728
+
729
+ function selectColor(color) {
730
+ selectedColor = color;
731
+ document.querySelectorAll('.color-option').forEach(el => {
732
+ el.classList.remove('selected');
733
+ if (el.style.background === color) {
734
+ el.classList.add('selected');
735
+ }
736
+ });
737
+ }
738
+
739
+ function updateGravity(value) {
740
+ gravity = parseFloat(value);
741
+ document.getElementById('gravityValue').textContent = value;
742
+ }
743
+
744
+ function updateBounciness(value) {
745
+ bounciness = parseFloat(value);
746
+ document.getElementById('bounceValue').textContent = value;
747
+ }
748
+
749
+ function updateSize(value) {
750
+ objectSize = parseFloat(value);
751
+ document.getElementById('sizeValue').textContent = value;
752
+ }
753
+
754
+ // Handle window resize
755
+ window.addEventListener('resize', () => {
756
+ camera.aspect = window.innerWidth / window.innerHeight;
757
+ camera.updateProjectionMatrix();
758
+ renderer.setSize(window.innerWidth, window.innerHeight);
759
+ });
760
+
761
+ // Initialize the application
762
+ init();
763
+ </script>
764
+ </body>
765
+ </html>