@neo-ui-kit/react 1.0.0 → 1.0.1
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 +12 -2
- package/dist/components/artwork/DatabaseArt/styles.css +58 -58
- package/dist/components/artwork/Landscape/styles.css +79 -79
- package/dist/components/artwork/Planet/styles.css +52 -52
- package/dist/components/artwork/QuantumField/UPSTREAM-LICENSE.txt +21 -21
- package/dist/components/artwork/ServerArt/styles.css +43 -43
- package/dist/components/artwork/Spectrum/styles.css +35 -35
- package/dist/components/controls/Autocomplete/styles.css +1 -1
- package/dist/components/controls/Checkbox/styles.css +58 -58
- package/dist/components/controls/InputBase/styles.css +374 -374
- package/dist/components/controls/Link/styles.css +50 -50
- package/dist/components/controls/SegmentedControl/styles.css +9 -9
- package/dist/components/controls/Switch/styles.css +54 -54
- package/dist/components/controls/Tabs/styles.css +239 -239
- package/dist/components/controls/ToggleButton/styles.css +49 -49
- package/dist/components/controls/shared.css +694 -694
- package/dist/components/editor/shared.css +1 -1
- package/dist/components/effects/Beacon/styles.css +38 -38
- package/dist/components/effects/Equalizer/styles.css +34 -34
- package/dist/components/effects/GlowText/styles.css +56 -56
- package/dist/components/effects/Marquee/styles.css +46 -46
- package/dist/components/effects/Reveal/styles.css +25 -25
- package/dist/components/effects/Shimmer/styles.css +48 -48
- package/dist/components/effects/Sparkles/styles.css +28 -28
- package/dist/components/effects/Spotlight/styles.css +45 -45
- package/dist/components/effects/Tilt/styles.css +67 -67
- package/dist/components/feedback/CollapsibleSection/styles.css +76 -76
- package/dist/components/feedback/DataTable/styles.css +522 -522
- package/dist/components/feedback/EmptyState/styles.css +80 -80
- package/dist/components/feedback/KeyValueList/styles.css +51 -51
- package/dist/components/feedback/MenuItem/styles.css +71 -71
- package/dist/components/feedback/Popover/styles.css +44 -44
- package/dist/components/feedback/Steps/styles.css +152 -152
- package/dist/components/feedback/Toast/styles.css +89 -89
- package/dist/components/forms/FormFields/styles.css +21 -21
- package/dist/components/foundation/ThemeProvider/styles.css +31 -31
- package/dist/components/layout/Avatar/styles.css +151 -151
- package/dist/components/layout/Card/styles.css +37 -37
- package/dist/components/layout/DialogActions/styles.css +31 -31
- package/dist/components/layout/DialogBody/styles.css +17 -17
- package/dist/components/layout/Illustration/styles.css +104 -104
- package/dist/components/layout/ScrollArea/styles.css +61 -61
- package/dist/components/layout/TabPanel/styles.css +16 -16
- package/dist/components/layout/shared.css +65 -65
- package/dist/components/media/AudioPlayer/styles.css +86 -86
- package/dist/components/media/LevelMeter/styles.css +30 -30
- package/dist/components/media/MelodyRoll/styles.css +196 -196
- package/dist/components/media/RotaryKnob/styles.css +319 -319
- package/dist/components/media/Sparkline/styles.css +69 -69
- package/dist/components/media/WaveDecoration/styles.css +1 -1
- package/dist/components/media/shared.css +7 -7
- package/dist/fonts/melodix.css +123 -123
- package/dist/index.js +2101 -2101
- package/dist/theme/base.css +210 -210
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5514,366 +5514,366 @@ function PlaybackTrack({ audio, playing, position: controlledPosition, disabled,
|
|
|
5514
5514
|
// src/components/artwork/QuantumField/reference.html?raw
|
|
5515
5515
|
var reference_default = `<!DOCTYPE html>
|
|
5516
5516
|
<!-- Quantum Fields Contributors, MIT License. Full notice: UPSTREAM-LICENSE.txt in this package. -->
|
|
5517
|
-
<html lang="en"
|
|
5518
|
-
|
|
5519
|
-
<head
|
|
5520
|
-
<meta charset="UTF-8"
|
|
5521
|
-
<title>Quantum Field: Interactive</title
|
|
5522
|
-
<style
|
|
5523
|
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap')
|
|
5524
|
-
|
|
5525
|
-
* {
|
|
5526
|
-
box-sizing: border-box
|
|
5527
|
-
}
|
|
5528
|
-
|
|
5529
|
-
:root {
|
|
5530
|
-
--ar: 167; --ag: 139; --ab: 250
|
|
5531
|
-
}
|
|
5532
|
-
|
|
5517
|
+
<html lang="en">
|
|
5518
|
+
|
|
5519
|
+
<head>
|
|
5520
|
+
<meta charset="UTF-8">
|
|
5521
|
+
<title>Quantum Field: Interactive</title>
|
|
5522
|
+
<style>
|
|
5523
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap');
|
|
5524
|
+
|
|
5525
|
+
* {
|
|
5526
|
+
box-sizing: border-box;
|
|
5527
|
+
}
|
|
5528
|
+
|
|
5529
|
+
:root {
|
|
5530
|
+
--ar: 167; --ag: 139; --ab: 250;
|
|
5531
|
+
}
|
|
5532
|
+
|
|
5533
5533
|
html, body {
|
|
5534
5534
|
margin: 0;
|
|
5535
5535
|
overflow: hidden;
|
|
5536
5536
|
background: transparent !important;
|
|
5537
5537
|
color-scheme: dark;
|
|
5538
5538
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
5539
|
-
}
|
|
5540
|
-
|
|
5541
|
-
canvas {
|
|
5542
|
-
display: block
|
|
5543
|
-
}
|
|
5544
|
-
|
|
5545
|
-
#scanlines {
|
|
5546
|
-
position: absolute
|
|
5547
|
-
top: 0
|
|
5548
|
-
left: 0
|
|
5549
|
-
width: 100
|
|
5550
|
-
height: 100
|
|
5551
|
-
background: radial-gradient(ellipse at center, transparent 0%, transparent 50%, rgba(0, 0, 0, 0.5) 100%)
|
|
5552
|
-
pointer-events: none
|
|
5553
|
-
z-index: 10
|
|
5554
|
-
}
|
|
5555
|
-
|
|
5556
|
-
#overlay {
|
|
5557
|
-
position: absolute
|
|
5558
|
-
top: 0
|
|
5559
|
-
left: 0
|
|
5560
|
-
width: 100
|
|
5561
|
-
height: 100
|
|
5562
|
-
display: flex
|
|
5563
|
-
flex-direction: column
|
|
5564
|
-
justify-content: center
|
|
5565
|
-
align-items: center
|
|
5566
|
-
background: linear-gradient(180deg, rgba(15, 10, 25, 0.97) 0%, rgba(10, 10, 20, 0.98) 100%)
|
|
5567
|
-
z-index: 100
|
|
5568
|
-
backdrop-filter: blur(20px)
|
|
5569
|
-
transition: opacity 1s ease
|
|
5570
|
-
pointer-events: none
|
|
5571
|
-
/* Allow clicks to pass through to canvas
|
|
5572
|
-
}
|
|
5573
|
-
|
|
5574
|
-
.hud-panel {
|
|
5575
|
-
background: linear-gradient(165deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.01) 100%)
|
|
5576
|
-
border: 1px solid rgba(255, 255, 255, 0.07)
|
|
5577
|
-
padding: 55px 75px
|
|
5578
|
-
text-align: center
|
|
5579
|
-
border-radius: 28px
|
|
5580
|
-
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5)
|
|
5581
|
-
pointer-events: auto
|
|
5582
|
-
/* Re-enable clicks for buttons
|
|
5583
|
-
}
|
|
5584
|
-
|
|
5585
|
-
h1 {
|
|
5586
|
-
font-family: 'Space Grotesk', sans-serif
|
|
5587
|
-
font-weight: 500
|
|
5588
|
-
letter-spacing: 1px
|
|
5589
|
-
font-size: 34px
|
|
5590
|
-
margin: 0 0 8px 0
|
|
5591
|
-
background: linear-gradient(135deg, #ffffff 0%, rgba(var(--ar),var(--ag),var(--ab),0.75) 40%, rgb(var(--ar),var(--ag),var(--ab)) 100%)
|
|
5592
|
-
-webkit-background-clip: text
|
|
5593
|
-
-webkit-text-fill-color: transparent
|
|
5594
|
-
background-clip: text
|
|
5595
|
-
}
|
|
5596
|
-
|
|
5597
|
-
.hud-panel p {
|
|
5598
|
-
color: rgba(255, 255, 255, 0.35)
|
|
5599
|
-
font-size: 13px
|
|
5600
|
-
margin-bottom: 40px
|
|
5601
|
-
letter-spacing: 0.5px
|
|
5602
|
-
font-weight: 300
|
|
5603
|
-
}
|
|
5604
|
-
|
|
5605
|
-
.btn-group {
|
|
5606
|
-
display: flex
|
|
5607
|
-
gap: 14px
|
|
5608
|
-
justify-content: center
|
|
5609
|
-
}
|
|
5610
|
-
|
|
5611
|
-
button
|
|
5612
|
-
.file-upload {
|
|
5613
|
-
background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.12) 0%, rgba(var(--ar),var(--ag),var(--ab),0.06) 100%)
|
|
5614
|
-
border: 1px solid rgba(var(--ar),var(--ag),var(--ab),0.25)
|
|
5615
|
-
color: rgba(255, 255, 255, 0.85)
|
|
5616
|
-
padding: 15px 30px
|
|
5617
|
-
font-size: 13px
|
|
5618
|
-
font-weight: 500
|
|
5619
|
-
cursor: pointer
|
|
5620
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1)
|
|
5621
|
-
letter-spacing: 0.3px
|
|
5622
|
-
border-radius: 14px
|
|
5623
|
-
font-family: 'Inter', sans-serif
|
|
5624
|
-
}
|
|
5625
|
-
|
|
5626
|
-
button:hover
|
|
5627
|
-
.file-upload:hover {
|
|
5628
|
-
background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.25) 0%, rgba(var(--ar),var(--ag),var(--ab),0.18) 100%)
|
|
5629
|
-
border-color: rgba(var(--ar),var(--ag),var(--ab),0.45)
|
|
5630
|
-
transform: translateY(-2px)
|
|
5631
|
-
box-shadow: 0 12px 35px rgba(139, 92, 246, 0.2)
|
|
5632
|
-
}
|
|
5633
|
-
|
|
5634
|
-
input[type="file"] {
|
|
5635
|
-
display: none
|
|
5636
|
-
}
|
|
5637
|
-
|
|
5638
|
-
#status-bar {
|
|
5639
|
-
position: absolute
|
|
5640
|
-
bottom: 80px
|
|
5641
|
-
left: 28px
|
|
5642
|
-
color: rgba(255, 255, 255, 0.3)
|
|
5643
|
-
font-size: 10px
|
|
5644
|
-
letter-spacing: 0.5px
|
|
5645
|
-
z-index: 200
|
|
5646
|
-
pointer-events: none
|
|
5647
|
-
}
|
|
5648
|
-
|
|
5649
|
-
#status-bar span {
|
|
5650
|
-
color: rgba(196, 181, 253, 0.7)
|
|
5651
|
-
font-weight: 500
|
|
5652
|
-
}
|
|
5653
|
-
|
|
5654
|
-
#interaction-hint {
|
|
5655
|
-
position: absolute
|
|
5656
|
-
top: 28px
|
|
5657
|
-
width: 100
|
|
5658
|
-
text-align: center
|
|
5659
|
-
color: rgba(255, 255, 255, 0.5)
|
|
5660
|
-
font-size: 11px
|
|
5661
|
-
letter-spacing: 2px
|
|
5662
|
-
pointer-events: none
|
|
5663
|
-
z-index: 9
|
|
5664
|
-
text-transform: lowercase
|
|
5665
|
-
}
|
|
5666
|
-
|
|
5667
|
-
#zoom-indicator {
|
|
5668
|
-
position: absolute
|
|
5669
|
-
bottom: 80px
|
|
5670
|
-
right: 28px
|
|
5671
|
-
color: rgba(255, 255, 255, 0.3)
|
|
5672
|
-
font-size: 10px
|
|
5673
|
-
z-index: 20
|
|
5674
|
-
pointer-events: none
|
|
5675
|
-
}
|
|
5676
|
-
|
|
5677
|
-
#zoom-indicator span {
|
|
5678
|
-
color: rgba(196, 181, 253, 0.7)
|
|
5679
|
-
}
|
|
5680
|
-
|
|
5681
|
-
#audio-controls {
|
|
5682
|
-
position: fixed
|
|
5683
|
-
bottom: 24px
|
|
5684
|
-
left: 50
|
|
5685
|
-
transform: translateX(-50%)
|
|
5686
|
-
display: flex
|
|
5687
|
-
gap: 10px
|
|
5688
|
-
align-items: center
|
|
5689
|
-
padding: 12px 20px
|
|
5690
|
-
background: linear-gradient(180deg, rgba(35, 28, 50, 0.88) 0%, rgba(22, 20, 38, 0.92) 100%)
|
|
5691
|
-
border: 1px solid rgba(255, 255, 255, 0.05)
|
|
5692
|
-
border-radius: 18px
|
|
5693
|
-
z-index: 200
|
|
5694
|
-
opacity: 0
|
|
5695
|
-
pointer-events: none
|
|
5696
|
-
transition: all 0.4s ease
|
|
5697
|
-
backdrop-filter: blur(25px)
|
|
5698
|
-
}
|
|
5699
|
-
|
|
5700
|
-
#audio-controls.visible {
|
|
5701
|
-
opacity: 1
|
|
5702
|
-
pointer-events: auto
|
|
5703
|
-
}
|
|
5704
|
-
|
|
5705
|
-
#audio-controls button
|
|
5706
|
-
#audio-controls .file-upload {
|
|
5707
|
-
padding: 9px 14px
|
|
5708
|
-
font-size: 11px
|
|
5709
|
-
background: rgba(255, 255, 255, 0.03)
|
|
5710
|
-
border: 1px solid rgba(255, 255, 255, 0.07)
|
|
5711
|
-
color: rgba(255, 255, 255, 0.65)
|
|
5712
|
-
border-radius: 10px
|
|
5713
|
-
}
|
|
5714
|
-
|
|
5715
|
-
#play-pause-btn {
|
|
5716
|
-
min-width: 78px
|
|
5717
|
-
background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.18) 0%, rgba(var(--ar),var(--ag),var(--ab),0.10) 100%) !important
|
|
5718
|
-
border-color: rgba(var(--ar),var(--ag),var(--ab),0.28) !important
|
|
5719
|
-
color: #e9d5ff !important
|
|
5720
|
-
}
|
|
5721
|
-
|
|
5722
|
-
#play-pause-btn.playing {
|
|
5723
|
-
background: linear-gradient(135deg, rgba(251, 113, 133, 0.18) 0%, rgba(244, 114, 182, 0.12) 100%) !important
|
|
5724
|
-
border-color: rgba(251, 113, 133, 0.35) !important
|
|
5725
|
-
}
|
|
5726
|
-
|
|
5727
|
-
#progress-container {
|
|
5728
|
-
width: 85px
|
|
5729
|
-
height: 4px
|
|
5730
|
-
background: rgba(255, 255, 255, 0.06)
|
|
5731
|
-
border-radius: 4px
|
|
5732
|
-
cursor: pointer
|
|
5733
|
-
}
|
|
5734
|
-
|
|
5735
|
-
#progress-bar {
|
|
5736
|
-
height: 100
|
|
5737
|
-
background: linear-gradient(90deg, rgb(var(--ar),var(--ag),var(--ab)), rgba(var(--ar),var(--ag),var(--ab),0.6), rgba(var(--ar),var(--ag),var(--ab),0.3))
|
|
5738
|
-
border-radius: 4px
|
|
5739
|
-
width: 0
|
|
5740
|
-
}
|
|
5741
|
-
|
|
5742
|
-
#time-display {
|
|
5743
|
-
color: rgba(255, 255, 255, 0.35)
|
|
5744
|
-
font-size: 10px
|
|
5745
|
-
min-width: 62px
|
|
5746
|
-
text-align: center
|
|
5747
|
-
}
|
|
5748
|
-
|
|
5749
|
-
#track-name {
|
|
5750
|
-
color: rgba(255, 255, 255, 0.3)
|
|
5751
|
-
font-size: 10px
|
|
5752
|
-
max-width: 85px
|
|
5753
|
-
overflow: hidden
|
|
5754
|
-
text-overflow: ellipsis
|
|
5755
|
-
white-space: nowrap
|
|
5756
|
-
}
|
|
5757
|
-
|
|
5539
|
+
}
|
|
5540
|
+
|
|
5541
|
+
canvas {
|
|
5542
|
+
display: block;
|
|
5543
|
+
}
|
|
5544
|
+
|
|
5545
|
+
#scanlines {
|
|
5546
|
+
position: absolute;
|
|
5547
|
+
top: 0;
|
|
5548
|
+
left: 0;
|
|
5549
|
+
width: 100%;
|
|
5550
|
+
height: 100%;
|
|
5551
|
+
background: radial-gradient(ellipse at center, transparent 0%, transparent 50%, rgba(0, 0, 0, 0.5) 100%);
|
|
5552
|
+
pointer-events: none;
|
|
5553
|
+
z-index: 10;
|
|
5554
|
+
}
|
|
5555
|
+
|
|
5556
|
+
#overlay {
|
|
5557
|
+
position: absolute;
|
|
5558
|
+
top: 0;
|
|
5559
|
+
left: 0;
|
|
5560
|
+
width: 100%;
|
|
5561
|
+
height: 100%;
|
|
5562
|
+
display: flex;
|
|
5563
|
+
flex-direction: column;
|
|
5564
|
+
justify-content: center;
|
|
5565
|
+
align-items: center;
|
|
5566
|
+
background: linear-gradient(180deg, rgba(15, 10, 25, 0.97) 0%, rgba(10, 10, 20, 0.98) 100%);
|
|
5567
|
+
z-index: 100;
|
|
5568
|
+
backdrop-filter: blur(20px);
|
|
5569
|
+
transition: opacity 1s ease;
|
|
5570
|
+
pointer-events: none;
|
|
5571
|
+
/* Allow clicks to pass through to canvas */
|
|
5572
|
+
}
|
|
5573
|
+
|
|
5574
|
+
.hud-panel {
|
|
5575
|
+
background: linear-gradient(165deg, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0.01) 100%);
|
|
5576
|
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
5577
|
+
padding: 55px 75px;
|
|
5578
|
+
text-align: center;
|
|
5579
|
+
border-radius: 28px;
|
|
5580
|
+
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);
|
|
5581
|
+
pointer-events: auto;
|
|
5582
|
+
/* Re-enable clicks for buttons */
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5585
|
+
h1 {
|
|
5586
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
5587
|
+
font-weight: 500;
|
|
5588
|
+
letter-spacing: 1px;
|
|
5589
|
+
font-size: 34px;
|
|
5590
|
+
margin: 0 0 8px 0;
|
|
5591
|
+
background: linear-gradient(135deg, #ffffff 0%, rgba(var(--ar),var(--ag),var(--ab),0.75) 40%, rgb(var(--ar),var(--ag),var(--ab)) 100%);
|
|
5592
|
+
-webkit-background-clip: text;
|
|
5593
|
+
-webkit-text-fill-color: transparent;
|
|
5594
|
+
background-clip: text;
|
|
5595
|
+
}
|
|
5596
|
+
|
|
5597
|
+
.hud-panel p {
|
|
5598
|
+
color: rgba(255, 255, 255, 0.35);
|
|
5599
|
+
font-size: 13px;
|
|
5600
|
+
margin-bottom: 40px;
|
|
5601
|
+
letter-spacing: 0.5px;
|
|
5602
|
+
font-weight: 300;
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5605
|
+
.btn-group {
|
|
5606
|
+
display: flex;
|
|
5607
|
+
gap: 14px;
|
|
5608
|
+
justify-content: center;
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5611
|
+
button,
|
|
5612
|
+
.file-upload {
|
|
5613
|
+
background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.12) 0%, rgba(var(--ar),var(--ag),var(--ab),0.06) 100%);
|
|
5614
|
+
border: 1px solid rgba(var(--ar),var(--ag),var(--ab),0.25);
|
|
5615
|
+
color: rgba(255, 255, 255, 0.85);
|
|
5616
|
+
padding: 15px 30px;
|
|
5617
|
+
font-size: 13px;
|
|
5618
|
+
font-weight: 500;
|
|
5619
|
+
cursor: pointer;
|
|
5620
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
5621
|
+
letter-spacing: 0.3px;
|
|
5622
|
+
border-radius: 14px;
|
|
5623
|
+
font-family: 'Inter', sans-serif;
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5626
|
+
button:hover,
|
|
5627
|
+
.file-upload:hover {
|
|
5628
|
+
background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.25) 0%, rgba(var(--ar),var(--ag),var(--ab),0.18) 100%);
|
|
5629
|
+
border-color: rgba(var(--ar),var(--ag),var(--ab),0.45);
|
|
5630
|
+
transform: translateY(-2px);
|
|
5631
|
+
box-shadow: 0 12px 35px rgba(139, 92, 246, 0.2);
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5634
|
+
input[type="file"] {
|
|
5635
|
+
display: none;
|
|
5636
|
+
}
|
|
5637
|
+
|
|
5638
|
+
#status-bar {
|
|
5639
|
+
position: absolute;
|
|
5640
|
+
bottom: 80px;
|
|
5641
|
+
left: 28px;
|
|
5642
|
+
color: rgba(255, 255, 255, 0.3);
|
|
5643
|
+
font-size: 10px;
|
|
5644
|
+
letter-spacing: 0.5px;
|
|
5645
|
+
z-index: 200;
|
|
5646
|
+
pointer-events: none;
|
|
5647
|
+
}
|
|
5648
|
+
|
|
5649
|
+
#status-bar span {
|
|
5650
|
+
color: rgba(196, 181, 253, 0.7);
|
|
5651
|
+
font-weight: 500;
|
|
5652
|
+
}
|
|
5653
|
+
|
|
5654
|
+
#interaction-hint {
|
|
5655
|
+
position: absolute;
|
|
5656
|
+
top: 28px;
|
|
5657
|
+
width: 100%;
|
|
5658
|
+
text-align: center;
|
|
5659
|
+
color: rgba(255, 255, 255, 0.5);
|
|
5660
|
+
font-size: 11px;
|
|
5661
|
+
letter-spacing: 2px;
|
|
5662
|
+
pointer-events: none;
|
|
5663
|
+
z-index: 9;
|
|
5664
|
+
text-transform: lowercase;
|
|
5665
|
+
}
|
|
5666
|
+
|
|
5667
|
+
#zoom-indicator {
|
|
5668
|
+
position: absolute;
|
|
5669
|
+
bottom: 80px;
|
|
5670
|
+
right: 28px;
|
|
5671
|
+
color: rgba(255, 255, 255, 0.3);
|
|
5672
|
+
font-size: 10px;
|
|
5673
|
+
z-index: 20;
|
|
5674
|
+
pointer-events: none;
|
|
5675
|
+
}
|
|
5676
|
+
|
|
5677
|
+
#zoom-indicator span {
|
|
5678
|
+
color: rgba(196, 181, 253, 0.7);
|
|
5679
|
+
}
|
|
5680
|
+
|
|
5681
|
+
#audio-controls {
|
|
5682
|
+
position: fixed;
|
|
5683
|
+
bottom: 24px;
|
|
5684
|
+
left: 50%;
|
|
5685
|
+
transform: translateX(-50%);
|
|
5686
|
+
display: flex;
|
|
5687
|
+
gap: 10px;
|
|
5688
|
+
align-items: center;
|
|
5689
|
+
padding: 12px 20px;
|
|
5690
|
+
background: linear-gradient(180deg, rgba(35, 28, 50, 0.88) 0%, rgba(22, 20, 38, 0.92) 100%);
|
|
5691
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
5692
|
+
border-radius: 18px;
|
|
5693
|
+
z-index: 200;
|
|
5694
|
+
opacity: 0;
|
|
5695
|
+
pointer-events: none;
|
|
5696
|
+
transition: all 0.4s ease;
|
|
5697
|
+
backdrop-filter: blur(25px);
|
|
5698
|
+
}
|
|
5699
|
+
|
|
5700
|
+
#audio-controls.visible {
|
|
5701
|
+
opacity: 1;
|
|
5702
|
+
pointer-events: auto;
|
|
5703
|
+
}
|
|
5704
|
+
|
|
5705
|
+
#audio-controls button,
|
|
5706
|
+
#audio-controls .file-upload {
|
|
5707
|
+
padding: 9px 14px;
|
|
5708
|
+
font-size: 11px;
|
|
5709
|
+
background: rgba(255, 255, 255, 0.03);
|
|
5710
|
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
5711
|
+
color: rgba(255, 255, 255, 0.65);
|
|
5712
|
+
border-radius: 10px;
|
|
5713
|
+
}
|
|
5714
|
+
|
|
5715
|
+
#play-pause-btn {
|
|
5716
|
+
min-width: 78px;
|
|
5717
|
+
background: linear-gradient(135deg, rgba(var(--ar),var(--ag),var(--ab),0.18) 0%, rgba(var(--ar),var(--ag),var(--ab),0.10) 100%) !important;
|
|
5718
|
+
border-color: rgba(var(--ar),var(--ag),var(--ab),0.28) !important;
|
|
5719
|
+
color: #e9d5ff !important;
|
|
5720
|
+
}
|
|
5721
|
+
|
|
5722
|
+
#play-pause-btn.playing {
|
|
5723
|
+
background: linear-gradient(135deg, rgba(251, 113, 133, 0.18) 0%, rgba(244, 114, 182, 0.12) 100%) !important;
|
|
5724
|
+
border-color: rgba(251, 113, 133, 0.35) !important;
|
|
5725
|
+
}
|
|
5726
|
+
|
|
5727
|
+
#progress-container {
|
|
5728
|
+
width: 85px;
|
|
5729
|
+
height: 4px;
|
|
5730
|
+
background: rgba(255, 255, 255, 0.06);
|
|
5731
|
+
border-radius: 4px;
|
|
5732
|
+
cursor: pointer;
|
|
5733
|
+
}
|
|
5734
|
+
|
|
5735
|
+
#progress-bar {
|
|
5736
|
+
height: 100%;
|
|
5737
|
+
background: linear-gradient(90deg, rgb(var(--ar),var(--ag),var(--ab)), rgba(var(--ar),var(--ag),var(--ab),0.6), rgba(var(--ar),var(--ag),var(--ab),0.3));
|
|
5738
|
+
border-radius: 4px;
|
|
5739
|
+
width: 0%;
|
|
5740
|
+
}
|
|
5741
|
+
|
|
5742
|
+
#time-display {
|
|
5743
|
+
color: rgba(255, 255, 255, 0.35);
|
|
5744
|
+
font-size: 10px;
|
|
5745
|
+
min-width: 62px;
|
|
5746
|
+
text-align: center;
|
|
5747
|
+
}
|
|
5748
|
+
|
|
5749
|
+
#track-name {
|
|
5750
|
+
color: rgba(255, 255, 255, 0.3);
|
|
5751
|
+
font-size: 10px;
|
|
5752
|
+
max-width: 85px;
|
|
5753
|
+
overflow: hidden;
|
|
5754
|
+
text-overflow: ellipsis;
|
|
5755
|
+
white-space: nowrap;
|
|
5756
|
+
}
|
|
5757
|
+
|
|
5758
5758
|
/* --- Center Panel Refinement --- */
|
|
5759
|
-
.hud-panel {
|
|
5760
|
-
background: rgba(15, 10, 25, 0.85)
|
|
5761
|
-
border: 1px solid rgba(255, 255, 255, 0.08)
|
|
5762
|
-
/* Darkened container
|
|
5763
|
-
padding: 24px
|
|
5764
|
-
/* Reduced padding
|
|
5765
|
-
border-radius: 24px
|
|
5766
|
-
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6)
|
|
5767
|
-
backdrop-filter: blur(40px)
|
|
5768
|
-
min-width: 420px
|
|
5769
|
-
/* Wider for buttons
|
|
5770
|
-
}
|
|
5771
|
-
|
|
5772
|
-
.hud-panel h1
|
|
5773
|
-
.hud-panel p {
|
|
5774
|
-
display: none
|
|
5775
|
-
/* Hide title as per screenshot showing only buttons
|
|
5776
|
-
}
|
|
5777
|
-
|
|
5778
|
-
.btn-group {
|
|
5779
|
-
display: flex
|
|
5780
|
-
gap: 16px
|
|
5781
|
-
justify-content: center
|
|
5782
|
-
width: 100
|
|
5783
|
-
}
|
|
5784
|
-
|
|
5785
|
-
.btn-group button {
|
|
5786
|
-
flex: 1
|
|
5787
|
-
background: transparent
|
|
5788
|
-
border: 1px solid rgba(var(--ar),var(--ag),var(--ab),0.3)
|
|
5789
|
-
color: rgb(var(--ar),var(--ag),var(--ab))
|
|
5790
|
-
padding: 14px 0
|
|
5791
|
-
border-radius: 12px
|
|
5792
|
-
font-size: 12px
|
|
5793
|
-
letter-spacing: 1px
|
|
5794
|
-
text-transform: uppercase
|
|
5795
|
-
font-weight: 600
|
|
5796
|
-
transition: all 0.3s ease
|
|
5797
|
-
}
|
|
5798
|
-
|
|
5799
|
-
.btn-group button:hover {
|
|
5800
|
-
background: rgba(var(--ar),var(--ag),var(--ab),0.1)
|
|
5801
|
-
border-color: rgb(var(--ar),var(--ag),var(--ab))
|
|
5802
|
-
box-shadow: 0 0 20px rgba(var(--ar),var(--ag),var(--ab),0.2)
|
|
5803
|
-
}
|
|
5759
|
+
.hud-panel {
|
|
5760
|
+
background: rgba(15, 10, 25, 0.85);
|
|
5761
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
5762
|
+
/* Darkened container */
|
|
5763
|
+
padding: 24px;
|
|
5764
|
+
/* Reduced padding */
|
|
5765
|
+
border-radius: 24px;
|
|
5766
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
|
|
5767
|
+
backdrop-filter: blur(40px);
|
|
5768
|
+
min-width: 420px;
|
|
5769
|
+
/* Wider for buttons */
|
|
5770
|
+
}
|
|
5771
|
+
|
|
5772
|
+
.hud-panel h1,
|
|
5773
|
+
.hud-panel p {
|
|
5774
|
+
display: none;
|
|
5775
|
+
/* Hide title as per screenshot showing only buttons */
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
.btn-group {
|
|
5779
|
+
display: flex;
|
|
5780
|
+
gap: 16px;
|
|
5781
|
+
justify-content: center;
|
|
5782
|
+
width: 100%;
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
.btn-group button {
|
|
5786
|
+
flex: 1;
|
|
5787
|
+
background: transparent;
|
|
5788
|
+
border: 1px solid rgba(var(--ar),var(--ag),var(--ab),0.3);
|
|
5789
|
+
color: rgb(var(--ar),var(--ag),var(--ab));
|
|
5790
|
+
padding: 14px 0;
|
|
5791
|
+
border-radius: 12px;
|
|
5792
|
+
font-size: 12px;
|
|
5793
|
+
letter-spacing: 1px;
|
|
5794
|
+
text-transform: uppercase;
|
|
5795
|
+
font-weight: 600;
|
|
5796
|
+
transition: all 0.3s ease;
|
|
5797
|
+
}
|
|
5798
|
+
|
|
5799
|
+
.btn-group button:hover {
|
|
5800
|
+
background: rgba(var(--ar),var(--ag),var(--ab),0.1);
|
|
5801
|
+
border-color: rgb(var(--ar),var(--ag),var(--ab));
|
|
5802
|
+
box-shadow: 0 0 20px rgba(var(--ar),var(--ag),var(--ab),0.2);
|
|
5803
|
+
}
|
|
5804
5804
|
#interaction-hint, #audio-controls, #status-bar, #zoom-indicator { display: none !important; }
|
|
5805
5805
|
</style>
|
|
5806
|
-
</head
|
|
5807
|
-
|
|
5808
|
-
<body
|
|
5806
|
+
</head>
|
|
5807
|
+
|
|
5808
|
+
<body>
|
|
5809
5809
|
<div id="scanlines" style="display:none"></div>
|
|
5810
|
-
<div id="interaction-hint">space \xB7 play/pause \xB7 scroll \xB7 zoom \xB7 drag \xB7 rotate \xB7 F \xB7 focus mode</div
|
|
5810
|
+
<div id="interaction-hint">space \xB7 play/pause \xB7 scroll \xB7 zoom \xB7 drag \xB7 rotate \xB7 F \xB7 focus mode</div>
|
|
5811
5811
|
<div id="overlay" style="display:none">
|
|
5812
|
-
<div class="hud-panel"
|
|
5813
|
-
<h1>Quantum Fields</h1
|
|
5814
|
-
<p>Audio-reactive particle simulation</p
|
|
5815
|
-
<div class="btn-group"
|
|
5816
|
-
<button id="enter-btn">Enter</button
|
|
5817
|
-
</div
|
|
5818
|
-
</div
|
|
5819
|
-
</div
|
|
5820
|
-
<div id="audio-controls"
|
|
5821
|
-
<button id="play-pause-btn" class="playing">\u23F8 pause</button
|
|
5822
|
-
<div id="progress-container"
|
|
5823
|
-
<div id="progress-bar"></div
|
|
5824
|
-
</div
|
|
5825
|
-
<span id="time-display">0:00 / 0:00</span
|
|
5826
|
-
<span id="track-name">no track</span
|
|
5827
|
-
<label class="file-upload" title="Load audio">\u{1F4C1}<input type="file" id="file-input-2" accept="audio/*"></label
|
|
5828
|
-
<button id="mic-btn-2" title="Microphone">\u{1F399}</button
|
|
5829
|
-
<button id="screenshot-btn" title="Screenshot">\u{1F4F7}</button
|
|
5830
|
-
<button id="fullscreen-btn" title="Fullscreen">\u26F6</button
|
|
5831
|
-
</div
|
|
5832
|
-
<div id="status-bar"><span>ready</span> \xB7 awaiting input</div
|
|
5833
|
-
<div id="zoom-indicator">zoom <span id="zoom-level">100%</span></div
|
|
5834
|
-
|
|
5835
|
-
<!-- Global Error Handler (Must be before module imports)
|
|
5836
|
-
<script
|
|
5837
|
-
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|
5838
|
-
const status = document.getElementById('status-bar')
|
|
5839
|
-
if (status) {
|
|
5840
|
-
status.innerHTML = \`ERROR: <span style="color:#ff6b6b">\${msg}</span
|
|
5841
|
-
status.style.opacity = 1
|
|
5842
|
-
}
|
|
5843
|
-
console.error('Global Error:', msg, url, lineNo, error)
|
|
5844
|
-
return false
|
|
5845
|
-
}
|
|
5846
|
-
// Catch unhandled promise rejections
|
|
5847
|
-
window.onunhandledrejection = function (event) {
|
|
5848
|
-
const status = document.getElementById('status-bar')
|
|
5849
|
-
if (status) {
|
|
5850
|
-
status.innerHTML = \`ERROR: <span style="color:#ff6b6b">\${event.reason}</span
|
|
5851
|
-
status.style.opacity = 1
|
|
5852
|
-
}
|
|
5853
|
-
console.error('Unhandled Rejection:', event.reason)
|
|
5854
|
-
}
|
|
5855
|
-
<\/script
|
|
5856
|
-
|
|
5857
|
-
<script type="importmap"
|
|
5858
|
-
{
|
|
5859
|
-
"imports": {
|
|
5860
|
-
"three": "https://unpkg.com/three@0.182.0/build/three.module.js"
|
|
5812
|
+
<div class="hud-panel">
|
|
5813
|
+
<h1>Quantum Fields</h1>
|
|
5814
|
+
<p>Audio-reactive particle simulation</p>
|
|
5815
|
+
<div class="btn-group">
|
|
5816
|
+
<button id="enter-btn">Enter</button>
|
|
5817
|
+
</div>
|
|
5818
|
+
</div>
|
|
5819
|
+
</div>
|
|
5820
|
+
<div id="audio-controls">
|
|
5821
|
+
<button id="play-pause-btn" class="playing">\u23F8 pause</button>
|
|
5822
|
+
<div id="progress-container">
|
|
5823
|
+
<div id="progress-bar"></div>
|
|
5824
|
+
</div>
|
|
5825
|
+
<span id="time-display">0:00 / 0:00</span>
|
|
5826
|
+
<span id="track-name">no track</span>
|
|
5827
|
+
<label class="file-upload" title="Load audio">\u{1F4C1}<input type="file" id="file-input-2" accept="audio/*"></label>
|
|
5828
|
+
<button id="mic-btn-2" title="Microphone">\u{1F399}</button>
|
|
5829
|
+
<button id="screenshot-btn" title="Screenshot">\u{1F4F7}</button>
|
|
5830
|
+
<button id="fullscreen-btn" title="Fullscreen">\u26F6</button>
|
|
5831
|
+
</div>
|
|
5832
|
+
<div id="status-bar"><span>ready</span> \xB7 awaiting input</div>
|
|
5833
|
+
<div id="zoom-indicator">zoom <span id="zoom-level">100%</span></div>
|
|
5834
|
+
|
|
5835
|
+
<!-- Global Error Handler (Must be before module imports) -->
|
|
5836
|
+
<script>
|
|
5837
|
+
window.onerror = function (msg, url, lineNo, columnNo, error) {
|
|
5838
|
+
const status = document.getElementById('status-bar');
|
|
5839
|
+
if (status) {
|
|
5840
|
+
status.innerHTML = \`ERROR: <span style="color:#ff6b6b">\${msg}</span>\`;
|
|
5841
|
+
status.style.opacity = 1;
|
|
5842
|
+
}
|
|
5843
|
+
console.error('Global Error:', msg, url, lineNo, error);
|
|
5844
|
+
return false;
|
|
5845
|
+
};
|
|
5846
|
+
// Catch unhandled promise rejections
|
|
5847
|
+
window.onunhandledrejection = function (event) {
|
|
5848
|
+
const status = document.getElementById('status-bar');
|
|
5849
|
+
if (status) {
|
|
5850
|
+
status.innerHTML = \`ERROR: <span style="color:#ff6b6b">\${event.reason}</span>\`;
|
|
5851
|
+
status.style.opacity = 1;
|
|
5852
|
+
}
|
|
5853
|
+
console.error('Unhandled Rejection:', event.reason);
|
|
5854
|
+
};
|
|
5855
|
+
<\/script>
|
|
5856
|
+
|
|
5857
|
+
<script type="importmap">
|
|
5858
|
+
{
|
|
5859
|
+
"imports": {
|
|
5860
|
+
"three": "https://unpkg.com/three@0.182.0/build/three.module.js",
|
|
5861
5861
|
"three/addons/": "https://unpkg.com/three@0.182.0/examples/jsm/"
|
|
5862
|
-
}
|
|
5863
|
-
}
|
|
5864
|
-
<\/script
|
|
5865
|
-
|
|
5866
|
-
<script type="module"
|
|
5867
|
-
import * as THREE from 'three'
|
|
5868
|
-
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
|
5869
|
-
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
|
|
5870
|
-
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'
|
|
5871
|
-
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
|
|
5872
|
-
import { AfterimagePass } from 'three/addons/postprocessing/AfterimagePass.js'
|
|
5873
|
-
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'
|
|
5874
|
-
// BokehPass removed - using custom DOF shader
|
|
5875
|
-
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js'
|
|
5876
|
-
|
|
5862
|
+
}
|
|
5863
|
+
}
|
|
5864
|
+
<\/script>
|
|
5865
|
+
|
|
5866
|
+
<script type="module">
|
|
5867
|
+
import * as THREE from 'three';
|
|
5868
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
5869
|
+
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
|
5870
|
+
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
|
5871
|
+
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
|
|
5872
|
+
import { AfterimagePass } from 'three/addons/postprocessing/AfterimagePass.js';
|
|
5873
|
+
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
|
|
5874
|
+
// BokehPass removed - using custom DOF shader
|
|
5875
|
+
import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
|
|
5876
|
+
|
|
5877
5877
|
function fitPixelRatio() {
|
|
5878
5878
|
return Math.min(window.devicePixelRatio || 1, 1.5,
|
|
5879
5879
|
Math.sqrt(2_100_000 / Math.max(1, window.innerWidth * window.innerHeight)));
|
|
@@ -5883,260 +5883,260 @@ var reference_default = `<!DOCTYPE html>
|
|
|
5883
5883
|
maxParticles: 250000,
|
|
5884
5884
|
maxDensity: 0.14,
|
|
5885
5885
|
secondaryParticles: 50000,
|
|
5886
|
-
initialDensity: 0.043
|
|
5887
|
-
fieldRadius: 90
|
|
5888
|
-
minZoom: 1
|
|
5889
|
-
maxZoom: 250
|
|
5886
|
+
initialDensity: 0.043,
|
|
5887
|
+
fieldRadius: 90,
|
|
5888
|
+
minZoom: 1,
|
|
5889
|
+
maxZoom: 250,
|
|
5890
5890
|
defaultZoom: 200
|
|
5891
5891
|
};
|
|
5892
5892
|
let particleBudget = Number(window.frameElement?.getAttribute('data-particle-budget') ?? Infinity);
|
|
5893
5893
|
particleBudget = Number.isFinite(particleBudget) ? Math.max(0, Math.floor(particleBudget)) : Infinity;
|
|
5894
|
-
|
|
5895
|
-
const STATE = {
|
|
5894
|
+
|
|
5895
|
+
const STATE = {
|
|
5896
5896
|
field: 'Gluon (Strong)',
|
|
5897
|
-
density: CONFIG.initialDensity
|
|
5898
|
-
sensitivity: 1.1
|
|
5899
|
-
trails: 0.66144
|
|
5900
|
-
bloom: 0.3
|
|
5901
|
-
timeScale: 1
|
|
5897
|
+
density: CONFIG.initialDensity,
|
|
5898
|
+
sensitivity: 1.1,
|
|
5899
|
+
trails: 0.66144,
|
|
5900
|
+
bloom: 0.3,
|
|
5901
|
+
timeScale: 1,
|
|
5902
5902
|
pixelRatio: fitPixelRatio(),
|
|
5903
|
-
chromaticAberration: 0.004
|
|
5904
|
-
filmGrain: 0.001
|
|
5905
|
-
cameraShake: 0.65
|
|
5906
|
-
vortexStrength: 0.73
|
|
5907
|
-
pulseIntensity: 0.15
|
|
5908
|
-
anamorphicStretch: 0.1
|
|
5909
|
-
bassGateThreshold: 0.12
|
|
5910
|
-
bassGateAttack: 0.3
|
|
5911
|
-
bassGateRelease: 0.136
|
|
5912
|
-
bassGateRatio: 2.142
|
|
5913
|
-
bassGateEnabled: false
|
|
5914
|
-
// NEW EFFECTS
|
|
5915
|
-
godRaysEnabled: false
|
|
5916
|
-
godRaysIntensity: 0.078
|
|
5917
|
-
godRaysDecay: 0.92
|
|
5918
|
-
dofEnabled: false
|
|
5919
|
-
dofFocus: 0.04
|
|
5920
|
-
dofFocalLength: 0.40
|
|
5921
|
-
dofBokehStrength: 0.36
|
|
5922
|
-
lensFlareEnabled: true
|
|
5923
|
-
lensFlareIntensity: 0.1
|
|
5924
|
-
particleTrailsEnabled: true
|
|
5925
|
-
particleTrailOpacity: 1
|
|
5926
|
-
nebulaEnabled: false
|
|
5927
|
-
nebulaIntensity: 0.3
|
|
5928
|
-
connectionsEnabled: true
|
|
5929
|
-
connectionThreshold: 20.0
|
|
5930
|
-
connectionOpacity: 0.75
|
|
5931
|
-
// === ENHANCEMENT PACK v2.0
|
|
5932
|
-
// 1. Motion Blur
|
|
5933
|
-
motionBlurEnabled: true
|
|
5934
|
-
motionBlurStrength: 0.1
|
|
5935
|
-
// 2. Adaptive Quality
|
|
5903
|
+
chromaticAberration: 0.004,
|
|
5904
|
+
filmGrain: 0.001,
|
|
5905
|
+
cameraShake: 0.65,
|
|
5906
|
+
vortexStrength: 0.73,
|
|
5907
|
+
pulseIntensity: 0.15,
|
|
5908
|
+
anamorphicStretch: 0.1,
|
|
5909
|
+
bassGateThreshold: 0.12,
|
|
5910
|
+
bassGateAttack: 0.3,
|
|
5911
|
+
bassGateRelease: 0.136,
|
|
5912
|
+
bassGateRatio: 2.142,
|
|
5913
|
+
bassGateEnabled: false,
|
|
5914
|
+
// NEW EFFECTS
|
|
5915
|
+
godRaysEnabled: false,
|
|
5916
|
+
godRaysIntensity: 0.078,
|
|
5917
|
+
godRaysDecay: 0.92,
|
|
5918
|
+
dofEnabled: false,
|
|
5919
|
+
dofFocus: 0.04,
|
|
5920
|
+
dofFocalLength: 0.40,
|
|
5921
|
+
dofBokehStrength: 0.36,
|
|
5922
|
+
lensFlareEnabled: true,
|
|
5923
|
+
lensFlareIntensity: 0.1,
|
|
5924
|
+
particleTrailsEnabled: true,
|
|
5925
|
+
particleTrailOpacity: 1,
|
|
5926
|
+
nebulaEnabled: false,
|
|
5927
|
+
nebulaIntensity: 0.3,
|
|
5928
|
+
connectionsEnabled: true,
|
|
5929
|
+
connectionThreshold: 20.0,
|
|
5930
|
+
connectionOpacity: 0.75,
|
|
5931
|
+
// === ENHANCEMENT PACK v2.0 ===
|
|
5932
|
+
// 1. Motion Blur
|
|
5933
|
+
motionBlurEnabled: true,
|
|
5934
|
+
motionBlurStrength: 0.1,
|
|
5935
|
+
// 2. Adaptive Quality
|
|
5936
5936
|
adaptiveQualityEnabled: true,
|
|
5937
5937
|
targetFPS: 45,
|
|
5938
5938
|
qualityLevel: 1,
|
|
5939
|
-
// 3. Onset Detection
|
|
5940
|
-
onsetSensitivity: 1
|
|
5941
|
-
onsetDecay: 0.92
|
|
5942
|
-
// 4. Audio-Reactive Camera
|
|
5943
|
-
audioCameraEnabled: true
|
|
5944
|
-
audioCameraSmoothing: 0.2
|
|
5945
|
-
audioCameraIntensity: 0.3
|
|
5946
|
-
// 5. Waveform Terrain
|
|
5947
|
-
waveformTerrainScale: 1.0
|
|
5948
|
-
waveformTerrainHeight: 20.0
|
|
5949
|
-
}
|
|
5950
|
-
|
|
5951
|
-
// --- AUDIO SYSTEM
|
|
5952
|
-
const AUDIO = {
|
|
5953
|
-
ctx: null, analyser: null, source: null, gainNode: null, data: null
|
|
5954
|
-
audioBuffer: null, isPlaying: false, isPaused: false
|
|
5955
|
-
startTime: 0, pauseTime: 0, currentTrackName: 'No Track'
|
|
5956
|
-
mode: null, micStream: null
|
|
5957
|
-
bands: { subBass: 0, bass: 0, lowMid: 0, mid: 0, highMid: 0, high: 0, ultraHigh: 0 }
|
|
5958
|
-
gatedBands: { subBass: 0, bass: 0, lowMid: 0, mid: 0, highMid: 0, high: 0, ultraHigh: 0 }
|
|
5959
|
-
beatThreshold: 0.15, beatDecay: 0.93, beatEnergy: 0
|
|
5960
|
-
lastBeatTime: 0, beatDetected: false, peakHistory: []
|
|
5939
|
+
// 3. Onset Detection
|
|
5940
|
+
onsetSensitivity: 1,
|
|
5941
|
+
onsetDecay: 0.92,
|
|
5942
|
+
// 4. Audio-Reactive Camera
|
|
5943
|
+
audioCameraEnabled: true,
|
|
5944
|
+
audioCameraSmoothing: 0.2,
|
|
5945
|
+
audioCameraIntensity: 0.3,
|
|
5946
|
+
// 5. Waveform Terrain
|
|
5947
|
+
waveformTerrainScale: 1.0,
|
|
5948
|
+
waveformTerrainHeight: 20.0
|
|
5949
|
+
};
|
|
5950
|
+
|
|
5951
|
+
// --- AUDIO SYSTEM ---
|
|
5952
|
+
const AUDIO = {
|
|
5953
|
+
ctx: null, analyser: null, source: null, gainNode: null, data: null,
|
|
5954
|
+
audioBuffer: null, isPlaying: false, isPaused: false,
|
|
5955
|
+
startTime: 0, pauseTime: 0, currentTrackName: 'No Track',
|
|
5956
|
+
mode: null, micStream: null,
|
|
5957
|
+
bands: { subBass: 0, bass: 0, lowMid: 0, mid: 0, highMid: 0, high: 0, ultraHigh: 0 },
|
|
5958
|
+
gatedBands: { subBass: 0, bass: 0, lowMid: 0, mid: 0, highMid: 0, high: 0, ultraHigh: 0 },
|
|
5959
|
+
beatThreshold: 0.15, beatDecay: 0.93, beatEnergy: 0,
|
|
5960
|
+
lastBeatTime: 0, beatDetected: false, peakHistory: [],
|
|
5961
5961
|
spectralCentroid: 0, spectralFlux: 0, prevSpectrum: null, active: false, idleCleared: false,
|
|
5962
|
-
// === ENHANCEMENT: Onset Detection
|
|
5963
|
-
onsetEnergy: 0, onsetDetected: false, onsetHistory: [], onsetThreshold: 0
|
|
5964
|
-
// === ENHANCEMENT: Waveform Data
|
|
5965
|
-
waveformData: new Float32Array(128), waveformSmoothed: new Float32Array(128)
|
|
5966
|
-
gateState: {
|
|
5967
|
-
isOpen: false, envelope: 0, holdTime: 0, noiseFloor: 0.05
|
|
5968
|
-
calibrationSamples: [], isCalibrating: true, calibrationFrames: 30
|
|
5969
|
-
}
|
|
5970
|
-
|
|
5971
|
-
init() {
|
|
5972
|
-
if (this.ctx) return
|
|
5973
|
-
this.ctx = new (window.AudioContext || window.webkitAudioContext)()
|
|
5974
|
-
this.analyser = this.ctx.createAnalyser()
|
|
5975
|
-
this.analyser.fftSize = 4096
|
|
5976
|
-
this.analyser.smoothingTimeConstant = 0.65
|
|
5977
|
-
this.data = new Uint8Array(this.analyser.frequencyBinCount)
|
|
5978
|
-
this.prevSpectrum = new Float32Array(this.analyser.frequencyBinCount)
|
|
5979
|
-
this.active = true
|
|
5980
|
-
}
|
|
5981
|
-
|
|
5982
|
-
loadTrack(arrayBuffer, fileName) {
|
|
5983
|
-
return new Promise((resolve, reject) => {
|
|
5984
|
-
this.ctx.decodeAudioData(arrayBuffer, (buffer) => {
|
|
5985
|
-
this.stopCurrentSource()
|
|
5986
|
-
this.audioBuffer = buffer
|
|
5987
|
-
this.currentTrackName = fileName || 'Unknown'
|
|
5988
|
-
this.mode = 'file'
|
|
5989
|
-
this.pauseTime = 0
|
|
5990
|
-
this.resetCalibration()
|
|
5991
|
-
resolve(buffer)
|
|
5992
|
-
}, reject)
|
|
5993
|
-
})
|
|
5994
|
-
}
|
|
5995
|
-
|
|
5996
|
-
play() {
|
|
5997
|
-
if (!this.audioBuffer || this.mode !== 'file' || this.isPlaying) return
|
|
5998
|
-
this.source = this.ctx.createBufferSource()
|
|
5999
|
-
this.source.buffer = this.audioBuffer
|
|
6000
|
-
this.source.loop = true
|
|
6001
|
-
this.source.connect(this.analyser)
|
|
6002
|
-
this.analyser.connect(this.ctx.destination)
|
|
6003
|
-
this.source.start(0, this.pauseTime)
|
|
6004
|
-
this.startTime = this.ctx.currentTime - this.pauseTime
|
|
6005
|
-
this.isPlaying = true
|
|
6006
|
-
this.isPaused = false
|
|
6007
|
-
}
|
|
6008
|
-
|
|
6009
|
-
pause() {
|
|
6010
|
-
if (!this.isPlaying || this.mode !== 'file') return
|
|
6011
|
-
this.pauseTime = this.getCurrentTime()
|
|
6012
|
-
this.isPaused = true
|
|
6013
|
-
this.isPlaying = false
|
|
6014
|
-
if (this.source) { try { this.source.stop(); } catch (e) { } this.source.disconnect(); this.source = null; }
|
|
6015
|
-
}
|
|
6016
|
-
|
|
6017
|
-
togglePlayPause() {
|
|
6018
|
-
if (this.mode === 'mic') return false
|
|
6019
|
-
if (this.isPlaying) { this.pause(); return false; }
|
|
6020
|
-
else { this.play(); return true; }
|
|
6021
|
-
}
|
|
6022
|
-
|
|
6023
|
-
getCurrentTime() {
|
|
6024
|
-
if (!this.isPlaying) return this.pauseTime
|
|
6025
|
-
if (!this.audioBuffer) return 0
|
|
6026
|
-
return (this.ctx.currentTime - this.startTime) % this.audioBuffer.duration
|
|
6027
|
-
}
|
|
6028
|
-
|
|
6029
|
-
getDuration() { return this.audioBuffer ? this.audioBuffer.duration : 0; }
|
|
6030
|
-
|
|
6031
|
-
seek(time) {
|
|
6032
|
-
if (this.mode !== 'file' || !this.audioBuffer) return
|
|
6033
|
-
const wasPlaying = this.isPlaying
|
|
6034
|
-
if (wasPlaying) this.pause()
|
|
6035
|
-
this.pauseTime = Math.max(0, Math.min(time, this.audioBuffer.duration))
|
|
6036
|
-
if (wasPlaying) this.play()
|
|
6037
|
-
}
|
|
6038
|
-
|
|
6039
|
-
stopCurrentSource() {
|
|
6040
|
-
if (this.source) { try { this.source.stop(); this.source.disconnect(); } catch (e) { } this.source = null; }
|
|
6041
|
-
if (this.gainNode) { try { this.gainNode.disconnect(); } catch (e) { } this.gainNode = null; }
|
|
6042
|
-
if (this.micStream) { this.micStream.getTracks().forEach(t => t.stop()); this.micStream = null; }
|
|
6043
|
-
this.isPlaying = false; this.isPaused = false; this.pauseTime = 0
|
|
6044
|
-
}
|
|
6045
|
-
|
|
6046
|
-
async startMic(preferredLabel = null) {
|
|
6047
|
-
console.log('startMic called with preferredLabel:', preferredLabel)
|
|
6048
|
-
const status = document.getElementById('status-bar')
|
|
6049
|
-
if (status) status.innerHTML = 'SYSTEM: <span>REQUESTING ACCESS...</span>'
|
|
6050
|
-
|
|
6051
|
-
try {
|
|
6052
|
-
// Ensure context is running
|
|
6053
|
-
if (this.ctx.state === 'suspended') {
|
|
6054
|
-
console.log('Resuming AudioContext...')
|
|
6055
|
-
await this.ctx.resume()
|
|
6056
|
-
}
|
|
6057
|
-
|
|
6058
|
-
// 1. Get initial stream (activates permissions so labels are visible)
|
|
6059
|
-
console.log('Requesting initial getUserMedia...')
|
|
6060
|
-
let stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
|
6061
|
-
console.log('Initial stream obtained:', stream.id)
|
|
6062
|
-
|
|
6063
|
-
// 2. Search for preferred device (loopback)
|
|
6064
|
-
let target = null
|
|
6065
|
-
if (preferredLabel) {
|
|
6066
|
-
const devices = await navigator.mediaDevices.enumerateDevices()
|
|
6067
|
-
console.log("Audio Devices Found:", devices.length, devices.map(d => \`\${d.label} (\${d.kind})\`))
|
|
6068
|
-
|
|
6069
|
-
target = devices.find(d => d.kind === 'audioinput'
|
|
6070
|
-
d.label.toLowerCase().includes(preferredLabel.toLowerCase()))
|
|
6071
|
-
}
|
|
6072
|
-
|
|
6073
|
-
if (target) {
|
|
6074
|
-
console.log(\`Switching audio input to: \${target.label} [\${target.deviceId}]\`)
|
|
6075
|
-
stream.getTracks().forEach(t => t.stop()); // Stop generic stream
|
|
6076
|
-
|
|
6077
|
-
// Request specific device - being explicit with constraints
|
|
6078
|
-
stream = await navigator.mediaDevices.getUserMedia({
|
|
6079
|
-
audio: {
|
|
6080
|
-
deviceId: { exact: target.deviceId }
|
|
6081
|
-
echoCancellation: false
|
|
6082
|
-
noiseSuppression: false
|
|
6083
|
-
autoGainControl: false
|
|
6084
|
-
channelCount: 2
|
|
6085
|
-
}
|
|
6086
|
-
})
|
|
6087
|
-
this.currentTrackName = target.label
|
|
6088
|
-
} else {
|
|
6089
|
-
if (preferredLabel) console.warn(\`Preferred device '\${preferredLabel}' not found. Using default.\`)
|
|
6090
|
-
// Keep the initial stream
|
|
6091
|
-
this.currentTrackName = stream.getAudioTracks()[0]?.label || 'Live Microphone'
|
|
6092
|
-
}
|
|
6093
|
-
|
|
6094
|
-
this.stopCurrentSource()
|
|
6095
|
-
this.micStream = stream
|
|
6096
|
-
this.source = this.ctx.createMediaStreamSource(stream)
|
|
6097
|
-
this.gainNode = this.ctx.createGain()
|
|
6098
|
-
this.gainNode.gain.value = 3.0; // Boost loopback/mic signal before FFT \u2014 tune via sensitivity slider
|
|
6099
|
-
this.source.connect(this.gainNode)
|
|
6100
|
-
this.gainNode.connect(this.analyser)
|
|
6101
|
-
this.mode = 'mic'
|
|
6102
|
-
this.isPlaying = true
|
|
6103
|
-
this.resetCalibration()
|
|
6104
|
-
|
|
6105
|
-
if (status) status.innerHTML = 'SYSTEM: <span>LISTENING (' + (preferredLabel ? 'Loopback' : 'Mic') + ')</span>'
|
|
6106
|
-
updateUI()
|
|
6107
|
-
|
|
6108
|
-
return stream
|
|
6109
|
-
} catch (e) {
|
|
6110
|
-
console.error("AUDIO.startMic failed:", e)
|
|
6111
|
-
throw e
|
|
6112
|
-
}
|
|
6113
|
-
}
|
|
6114
|
-
|
|
6115
|
-
resetCalibration() { this.gateState.isCalibrating = true; this.gateState.calibrationSamples = []; }
|
|
6116
|
-
|
|
6117
|
-
applyBassGate(rawValue) {
|
|
6118
|
-
if (!STATE.bassGateEnabled) return rawValue
|
|
6119
|
-
const { bassGateThreshold: threshold, bassGateAttack: attack, bassGateRelease: release, bassGateRatio: ratio } = STATE
|
|
6120
|
-
if (this.gateState.isCalibrating) {
|
|
6121
|
-
this.gateState.calibrationSamples.push(rawValue)
|
|
6122
|
-
if (this.gateState.calibrationSamples.length >= this.gateState.calibrationFrames) {
|
|
6123
|
-
const sorted = [...this.gateState.calibrationSamples].sort((a, b) => a - b)
|
|
6124
|
-
this.gateState.noiseFloor = sorted.slice(0, Math.floor(sorted.length * 0.2)).reduce((a, b) => a + b, 0) / Math.floor(sorted.length * 0.2)
|
|
6125
|
-
this.gateState.isCalibrating = false
|
|
6126
|
-
}
|
|
6127
|
-
}
|
|
6128
|
-
const effectiveThreshold = Math.max(threshold, this.gateState.noiseFloor * 1.5)
|
|
6129
|
-
if (rawValue > effectiveThreshold) { this.gateState.isOpen = true; this.gateState.holdTime = 10; }
|
|
6130
|
-
else if (rawValue < effectiveThreshold * 0.7 && this.gateState.holdTime <= 0) { this.gateState.isOpen = false; }
|
|
6131
|
-
if (this.gateState.holdTime > 0) this.gateState.holdTime
|
|
6132
|
-
this.gateState.envelope += ((this.gateState.isOpen ? 1.0 : 0.0) - this.gateState.envelope) * (this.gateState.isOpen ? attack : release)
|
|
6133
|
-
let output = rawValue
|
|
6134
|
-
if (this.gateState.isOpen && rawValue > effectiveThreshold) {
|
|
6135
|
-
output = effectiveThreshold + (rawValue - effectiveThreshold) / ratio
|
|
6136
|
-
}
|
|
6137
|
-
return output * this.gateState.envelope
|
|
6138
|
-
}
|
|
6139
|
-
|
|
5962
|
+
// === ENHANCEMENT: Onset Detection ===
|
|
5963
|
+
onsetEnergy: 0, onsetDetected: false, onsetHistory: [], onsetThreshold: 0,
|
|
5964
|
+
// === ENHANCEMENT: Waveform Data ===
|
|
5965
|
+
waveformData: new Float32Array(128), waveformSmoothed: new Float32Array(128),
|
|
5966
|
+
gateState: {
|
|
5967
|
+
isOpen: false, envelope: 0, holdTime: 0, noiseFloor: 0.05,
|
|
5968
|
+
calibrationSamples: [], isCalibrating: true, calibrationFrames: 30
|
|
5969
|
+
},
|
|
5970
|
+
|
|
5971
|
+
init() {
|
|
5972
|
+
if (this.ctx) return;
|
|
5973
|
+
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
|
|
5974
|
+
this.analyser = this.ctx.createAnalyser();
|
|
5975
|
+
this.analyser.fftSize = 4096;
|
|
5976
|
+
this.analyser.smoothingTimeConstant = 0.65;
|
|
5977
|
+
this.data = new Uint8Array(this.analyser.frequencyBinCount);
|
|
5978
|
+
this.prevSpectrum = new Float32Array(this.analyser.frequencyBinCount);
|
|
5979
|
+
this.active = true;
|
|
5980
|
+
},
|
|
5981
|
+
|
|
5982
|
+
loadTrack(arrayBuffer, fileName) {
|
|
5983
|
+
return new Promise((resolve, reject) => {
|
|
5984
|
+
this.ctx.decodeAudioData(arrayBuffer, (buffer) => {
|
|
5985
|
+
this.stopCurrentSource();
|
|
5986
|
+
this.audioBuffer = buffer;
|
|
5987
|
+
this.currentTrackName = fileName || 'Unknown';
|
|
5988
|
+
this.mode = 'file';
|
|
5989
|
+
this.pauseTime = 0;
|
|
5990
|
+
this.resetCalibration();
|
|
5991
|
+
resolve(buffer);
|
|
5992
|
+
}, reject);
|
|
5993
|
+
});
|
|
5994
|
+
},
|
|
5995
|
+
|
|
5996
|
+
play() {
|
|
5997
|
+
if (!this.audioBuffer || this.mode !== 'file' || this.isPlaying) return;
|
|
5998
|
+
this.source = this.ctx.createBufferSource();
|
|
5999
|
+
this.source.buffer = this.audioBuffer;
|
|
6000
|
+
this.source.loop = true;
|
|
6001
|
+
this.source.connect(this.analyser);
|
|
6002
|
+
this.analyser.connect(this.ctx.destination);
|
|
6003
|
+
this.source.start(0, this.pauseTime);
|
|
6004
|
+
this.startTime = this.ctx.currentTime - this.pauseTime;
|
|
6005
|
+
this.isPlaying = true;
|
|
6006
|
+
this.isPaused = false;
|
|
6007
|
+
},
|
|
6008
|
+
|
|
6009
|
+
pause() {
|
|
6010
|
+
if (!this.isPlaying || this.mode !== 'file') return;
|
|
6011
|
+
this.pauseTime = this.getCurrentTime();
|
|
6012
|
+
this.isPaused = true;
|
|
6013
|
+
this.isPlaying = false;
|
|
6014
|
+
if (this.source) { try { this.source.stop(); } catch (e) { } this.source.disconnect(); this.source = null; }
|
|
6015
|
+
},
|
|
6016
|
+
|
|
6017
|
+
togglePlayPause() {
|
|
6018
|
+
if (this.mode === 'mic') return false;
|
|
6019
|
+
if (this.isPlaying) { this.pause(); return false; }
|
|
6020
|
+
else { this.play(); return true; }
|
|
6021
|
+
},
|
|
6022
|
+
|
|
6023
|
+
getCurrentTime() {
|
|
6024
|
+
if (!this.isPlaying) return this.pauseTime;
|
|
6025
|
+
if (!this.audioBuffer) return 0;
|
|
6026
|
+
return (this.ctx.currentTime - this.startTime) % this.audioBuffer.duration;
|
|
6027
|
+
},
|
|
6028
|
+
|
|
6029
|
+
getDuration() { return this.audioBuffer ? this.audioBuffer.duration : 0; },
|
|
6030
|
+
|
|
6031
|
+
seek(time) {
|
|
6032
|
+
if (this.mode !== 'file' || !this.audioBuffer) return;
|
|
6033
|
+
const wasPlaying = this.isPlaying;
|
|
6034
|
+
if (wasPlaying) this.pause();
|
|
6035
|
+
this.pauseTime = Math.max(0, Math.min(time, this.audioBuffer.duration));
|
|
6036
|
+
if (wasPlaying) this.play();
|
|
6037
|
+
},
|
|
6038
|
+
|
|
6039
|
+
stopCurrentSource() {
|
|
6040
|
+
if (this.source) { try { this.source.stop(); this.source.disconnect(); } catch (e) { } this.source = null; }
|
|
6041
|
+
if (this.gainNode) { try { this.gainNode.disconnect(); } catch (e) { } this.gainNode = null; }
|
|
6042
|
+
if (this.micStream) { this.micStream.getTracks().forEach(t => t.stop()); this.micStream = null; }
|
|
6043
|
+
this.isPlaying = false; this.isPaused = false; this.pauseTime = 0;
|
|
6044
|
+
},
|
|
6045
|
+
|
|
6046
|
+
async startMic(preferredLabel = null) {
|
|
6047
|
+
console.log('startMic called with preferredLabel:', preferredLabel);
|
|
6048
|
+
const status = document.getElementById('status-bar');
|
|
6049
|
+
if (status) status.innerHTML = 'SYSTEM: <span>REQUESTING ACCESS...</span>';
|
|
6050
|
+
|
|
6051
|
+
try {
|
|
6052
|
+
// Ensure context is running
|
|
6053
|
+
if (this.ctx.state === 'suspended') {
|
|
6054
|
+
console.log('Resuming AudioContext...');
|
|
6055
|
+
await this.ctx.resume();
|
|
6056
|
+
}
|
|
6057
|
+
|
|
6058
|
+
// 1. Get initial stream (activates permissions so labels are visible)
|
|
6059
|
+
console.log('Requesting initial getUserMedia...');
|
|
6060
|
+
let stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
6061
|
+
console.log('Initial stream obtained:', stream.id);
|
|
6062
|
+
|
|
6063
|
+
// 2. Search for preferred device (loopback)
|
|
6064
|
+
let target = null;
|
|
6065
|
+
if (preferredLabel) {
|
|
6066
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
6067
|
+
console.log("Audio Devices Found:", devices.length, devices.map(d => \`\${d.label} (\${d.kind})\`));
|
|
6068
|
+
|
|
6069
|
+
target = devices.find(d => d.kind === 'audioinput' &&
|
|
6070
|
+
d.label.toLowerCase().includes(preferredLabel.toLowerCase()));
|
|
6071
|
+
}
|
|
6072
|
+
|
|
6073
|
+
if (target) {
|
|
6074
|
+
console.log(\`Switching audio input to: \${target.label} [\${target.deviceId}]\`);
|
|
6075
|
+
stream.getTracks().forEach(t => t.stop()); // Stop generic stream
|
|
6076
|
+
|
|
6077
|
+
// Request specific device - being explicit with constraints
|
|
6078
|
+
stream = await navigator.mediaDevices.getUserMedia({
|
|
6079
|
+
audio: {
|
|
6080
|
+
deviceId: { exact: target.deviceId },
|
|
6081
|
+
echoCancellation: false,
|
|
6082
|
+
noiseSuppression: false,
|
|
6083
|
+
autoGainControl: false,
|
|
6084
|
+
channelCount: 2
|
|
6085
|
+
}
|
|
6086
|
+
});
|
|
6087
|
+
this.currentTrackName = target.label;
|
|
6088
|
+
} else {
|
|
6089
|
+
if (preferredLabel) console.warn(\`Preferred device '\${preferredLabel}' not found. Using default.\`);
|
|
6090
|
+
// Keep the initial stream
|
|
6091
|
+
this.currentTrackName = stream.getAudioTracks()[0]?.label || 'Live Microphone';
|
|
6092
|
+
}
|
|
6093
|
+
|
|
6094
|
+
this.stopCurrentSource();
|
|
6095
|
+
this.micStream = stream;
|
|
6096
|
+
this.source = this.ctx.createMediaStreamSource(stream);
|
|
6097
|
+
this.gainNode = this.ctx.createGain();
|
|
6098
|
+
this.gainNode.gain.value = 3.0; // Boost loopback/mic signal before FFT \u2014 tune via sensitivity slider
|
|
6099
|
+
this.source.connect(this.gainNode);
|
|
6100
|
+
this.gainNode.connect(this.analyser);
|
|
6101
|
+
this.mode = 'mic';
|
|
6102
|
+
this.isPlaying = true;
|
|
6103
|
+
this.resetCalibration();
|
|
6104
|
+
|
|
6105
|
+
if (status) status.innerHTML = 'SYSTEM: <span>LISTENING (' + (preferredLabel ? 'Loopback' : 'Mic') + ')</span>';
|
|
6106
|
+
updateUI();
|
|
6107
|
+
|
|
6108
|
+
return stream;
|
|
6109
|
+
} catch (e) {
|
|
6110
|
+
console.error("AUDIO.startMic failed:", e);
|
|
6111
|
+
throw e;
|
|
6112
|
+
}
|
|
6113
|
+
},
|
|
6114
|
+
|
|
6115
|
+
resetCalibration() { this.gateState.isCalibrating = true; this.gateState.calibrationSamples = []; },
|
|
6116
|
+
|
|
6117
|
+
applyBassGate(rawValue) {
|
|
6118
|
+
if (!STATE.bassGateEnabled) return rawValue;
|
|
6119
|
+
const { bassGateThreshold: threshold, bassGateAttack: attack, bassGateRelease: release, bassGateRatio: ratio } = STATE;
|
|
6120
|
+
if (this.gateState.isCalibrating) {
|
|
6121
|
+
this.gateState.calibrationSamples.push(rawValue);
|
|
6122
|
+
if (this.gateState.calibrationSamples.length >= this.gateState.calibrationFrames) {
|
|
6123
|
+
const sorted = [...this.gateState.calibrationSamples].sort((a, b) => a - b);
|
|
6124
|
+
this.gateState.noiseFloor = sorted.slice(0, Math.floor(sorted.length * 0.2)).reduce((a, b) => a + b, 0) / Math.floor(sorted.length * 0.2);
|
|
6125
|
+
this.gateState.isCalibrating = false;
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
const effectiveThreshold = Math.max(threshold, this.gateState.noiseFloor * 1.5);
|
|
6129
|
+
if (rawValue > effectiveThreshold) { this.gateState.isOpen = true; this.gateState.holdTime = 10; }
|
|
6130
|
+
else if (rawValue < effectiveThreshold * 0.7 && this.gateState.holdTime <= 0) { this.gateState.isOpen = false; }
|
|
6131
|
+
if (this.gateState.holdTime > 0) this.gateState.holdTime--;
|
|
6132
|
+
this.gateState.envelope += ((this.gateState.isOpen ? 1.0 : 0.0) - this.gateState.envelope) * (this.gateState.isOpen ? attack : release);
|
|
6133
|
+
let output = rawValue;
|
|
6134
|
+
if (this.gateState.isOpen && rawValue > effectiveThreshold) {
|
|
6135
|
+
output = effectiveThreshold + (rawValue - effectiveThreshold) / ratio;
|
|
6136
|
+
}
|
|
6137
|
+
return output * this.gateState.envelope;
|
|
6138
|
+
},
|
|
6139
|
+
|
|
6140
6140
|
update() {
|
|
6141
6141
|
if (!this.active) return;
|
|
6142
6142
|
if (!this.isPlaying || !this.source) {
|
|
@@ -6152,127 +6152,127 @@ var reference_default = `<!DOCTYPE html>
|
|
|
6152
6152
|
}
|
|
6153
6153
|
this.idleCleared = false;
|
|
6154
6154
|
this.analyser.getByteFrequencyData(this.data);
|
|
6155
|
-
const len = this.data.length, nyquist = this.ctx.sampleRate / 2
|
|
6156
|
-
const getFreqIndex = (freq) => Math.round(freq / nyquist * len)
|
|
6157
|
-
const ranges = { subBass: [50, 120], bass: [120, 200], lowMid: [200, 450], mid: [450, 2000], highMid: [2000, 3500], high: [3500, 12000], ultraHigh: [12000, 20000] }
|
|
6158
|
-
let totalEnergy = 0, weightedFreqSum = 0
|
|
6159
|
-
for (const [band, [low, high]] of Object.entries(ranges)) {
|
|
6160
|
-
const lowIdx = getFreqIndex(low), highIdx = getFreqIndex(high)
|
|
6161
|
-
let sum = 0
|
|
6162
|
-
for (let i = lowIdx; i < highIdx && i < len; i++) {
|
|
6163
|
-
const v = this.data[i] / 255.0
|
|
6164
|
-
sum += v; totalEnergy += v; weightedFreqSum += v * i
|
|
6165
|
-
}
|
|
6166
|
-
const rawBand = sum / (highIdx - lowIdx)
|
|
6167
|
-
this.bands[band] = rawBand
|
|
6168
|
-
this.gatedBands[band] = (band === 'subBass' || band === 'bass' || band === 'lowMid') ? this.applyBassGate(rawBand) : rawBand
|
|
6169
|
-
}
|
|
6170
|
-
this.spectralCentroid = totalEnergy > 0 ? weightedFreqSum / totalEnergy / len : 0
|
|
6171
|
-
let flux = 0
|
|
6172
|
-
for (let i = 0; i < len; i++) {
|
|
6173
|
-
const current = this.data[i] / 255.0, diff = current - this.prevSpectrum[i]
|
|
6174
|
-
flux += diff > 0 ? diff * diff : 0
|
|
6175
|
-
this.prevSpectrum[i] = current
|
|
6176
|
-
}
|
|
6177
|
-
this.spectralFlux = Math.sqrt(flux / len)
|
|
6178
|
-
const currentEnergy = this.gatedBands.bass * 0.6 + this.gatedBands.subBass * 0.4
|
|
6179
|
-
this.peakHistory.push(currentEnergy)
|
|
6180
|
-
if (this.peakHistory.length > 50) this.peakHistory.shift()
|
|
6181
|
-
const avgEnergy = this.peakHistory.reduce((a, b) => a + b, 0) / this.peakHistory.length
|
|
6182
|
-
const variance = this.peakHistory.reduce((a, b) => a + Math.pow(b - avgEnergy, 2), 0) / this.peakHistory.length
|
|
6183
|
-
const dynamicThreshold = avgEnergy + Math.sqrt(variance) * 1.8
|
|
6184
|
-
const now = performance.now()
|
|
6185
|
-
if (currentEnergy > dynamicThreshold && currentEnergy > this.beatThreshold && now - this.lastBeatTime > 120) {
|
|
6186
|
-
this.beatDetected = true; this.beatEnergy = 1.0; this.lastBeatTime = now
|
|
6187
|
-
} else { this.beatDetected = false; }
|
|
6188
|
-
this.beatEnergy *= this.beatDecay
|
|
6189
|
-
|
|
6190
|
-
// === ENHANCEMENT: Improved Onset Detection
|
|
6191
|
-
// Uses spectral flux derivative for sharper transient response
|
|
6192
|
-
const onsetSens = STATE.onsetSensitivity
|
|
6193
|
-
this.onsetHistory.push(this.spectralFlux)
|
|
6194
|
-
if (this.onsetHistory.length > 8) this.onsetHistory.shift()
|
|
6195
|
-
const recentAvg = this.onsetHistory.slice(-4).reduce((a, b) => a + b, 0) / 4
|
|
6196
|
-
const olderAvg = this.onsetHistory.slice(0, 4).reduce((a, b) => a + b, 0) / Math.max(1, this.onsetHistory.slice(0, 4).length)
|
|
6197
|
-
const onsetDelta = (recentAvg - olderAvg) * onsetSens
|
|
6198
|
-
this.onsetDetected = onsetDelta > 0.015 && now - this.lastBeatTime > 50
|
|
6199
|
-
this.onsetEnergy = this.onsetDetected ? 1.0 : this.onsetEnergy * STATE.onsetDecay
|
|
6200
|
-
|
|
6201
|
-
// === ENHANCEMENT: Waveform Data for Terrain Field
|
|
6202
|
-
const waveformBins = 128
|
|
6203
|
-
const binSize = Math.floor(len / waveformBins)
|
|
6204
|
-
for (let i = 0; i < waveformBins; i++) {
|
|
6205
|
-
let sum = 0
|
|
6206
|
-
for (let j = 0; j < binSize; j++) {
|
|
6207
|
-
sum += this.data[i * binSize + j] / 255.0
|
|
6208
|
-
}
|
|
6209
|
-
const target = sum / binSize
|
|
6210
|
-
this.waveformSmoothed[i] += (target - this.waveformSmoothed[i]) * 0.3
|
|
6211
|
-
this.waveformData[i] = this.waveformSmoothed[i]
|
|
6212
|
-
}
|
|
6213
|
-
}
|
|
6214
|
-
}
|
|
6215
|
-
|
|
6216
|
-
// --- SCENE SETUP
|
|
6217
|
-
const scene = new THREE.Scene()
|
|
6218
|
-
scene.fog = new THREE.FogExp2(0x000508, 0.005)
|
|
6219
|
-
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 800)
|
|
6220
|
-
camera.position.set(0, 12, CONFIG.defaultZoom)
|
|
6221
|
-
const cameraShakeOffset = new THREE.Vector3()
|
|
6222
|
-
const baseCameraPos = camera.position.clone()
|
|
6223
|
-
const zoomIndicator = document.getElementById('zoom-level')
|
|
6224
|
-
|
|
6155
|
+
const len = this.data.length, nyquist = this.ctx.sampleRate / 2;
|
|
6156
|
+
const getFreqIndex = (freq) => Math.round(freq / nyquist * len);
|
|
6157
|
+
const ranges = { subBass: [50, 120], bass: [120, 200], lowMid: [200, 450], mid: [450, 2000], highMid: [2000, 3500], high: [3500, 12000], ultraHigh: [12000, 20000] };
|
|
6158
|
+
let totalEnergy = 0, weightedFreqSum = 0;
|
|
6159
|
+
for (const [band, [low, high]] of Object.entries(ranges)) {
|
|
6160
|
+
const lowIdx = getFreqIndex(low), highIdx = getFreqIndex(high);
|
|
6161
|
+
let sum = 0;
|
|
6162
|
+
for (let i = lowIdx; i < highIdx && i < len; i++) {
|
|
6163
|
+
const v = this.data[i] / 255.0;
|
|
6164
|
+
sum += v; totalEnergy += v; weightedFreqSum += v * i;
|
|
6165
|
+
}
|
|
6166
|
+
const rawBand = sum / (highIdx - lowIdx);
|
|
6167
|
+
this.bands[band] = rawBand;
|
|
6168
|
+
this.gatedBands[band] = (band === 'subBass' || band === 'bass' || band === 'lowMid') ? this.applyBassGate(rawBand) : rawBand;
|
|
6169
|
+
}
|
|
6170
|
+
this.spectralCentroid = totalEnergy > 0 ? weightedFreqSum / totalEnergy / len : 0;
|
|
6171
|
+
let flux = 0;
|
|
6172
|
+
for (let i = 0; i < len; i++) {
|
|
6173
|
+
const current = this.data[i] / 255.0, diff = current - this.prevSpectrum[i];
|
|
6174
|
+
flux += diff > 0 ? diff * diff : 0;
|
|
6175
|
+
this.prevSpectrum[i] = current;
|
|
6176
|
+
}
|
|
6177
|
+
this.spectralFlux = Math.sqrt(flux / len);
|
|
6178
|
+
const currentEnergy = this.gatedBands.bass * 0.6 + this.gatedBands.subBass * 0.4;
|
|
6179
|
+
this.peakHistory.push(currentEnergy);
|
|
6180
|
+
if (this.peakHistory.length > 50) this.peakHistory.shift();
|
|
6181
|
+
const avgEnergy = this.peakHistory.reduce((a, b) => a + b, 0) / this.peakHistory.length;
|
|
6182
|
+
const variance = this.peakHistory.reduce((a, b) => a + Math.pow(b - avgEnergy, 2), 0) / this.peakHistory.length;
|
|
6183
|
+
const dynamicThreshold = avgEnergy + Math.sqrt(variance) * 1.8;
|
|
6184
|
+
const now = performance.now();
|
|
6185
|
+
if (currentEnergy > dynamicThreshold && currentEnergy > this.beatThreshold && now - this.lastBeatTime > 120) {
|
|
6186
|
+
this.beatDetected = true; this.beatEnergy = 1.0; this.lastBeatTime = now;
|
|
6187
|
+
} else { this.beatDetected = false; }
|
|
6188
|
+
this.beatEnergy *= this.beatDecay;
|
|
6189
|
+
|
|
6190
|
+
// === ENHANCEMENT: Improved Onset Detection ===
|
|
6191
|
+
// Uses spectral flux derivative for sharper transient response
|
|
6192
|
+
const onsetSens = STATE.onsetSensitivity;
|
|
6193
|
+
this.onsetHistory.push(this.spectralFlux);
|
|
6194
|
+
if (this.onsetHistory.length > 8) this.onsetHistory.shift();
|
|
6195
|
+
const recentAvg = this.onsetHistory.slice(-4).reduce((a, b) => a + b, 0) / 4;
|
|
6196
|
+
const olderAvg = this.onsetHistory.slice(0, 4).reduce((a, b) => a + b, 0) / Math.max(1, this.onsetHistory.slice(0, 4).length);
|
|
6197
|
+
const onsetDelta = (recentAvg - olderAvg) * onsetSens;
|
|
6198
|
+
this.onsetDetected = onsetDelta > 0.015 && now - this.lastBeatTime > 50;
|
|
6199
|
+
this.onsetEnergy = this.onsetDetected ? 1.0 : this.onsetEnergy * STATE.onsetDecay;
|
|
6200
|
+
|
|
6201
|
+
// === ENHANCEMENT: Waveform Data for Terrain Field ===
|
|
6202
|
+
const waveformBins = 128;
|
|
6203
|
+
const binSize = Math.floor(len / waveformBins);
|
|
6204
|
+
for (let i = 0; i < waveformBins; i++) {
|
|
6205
|
+
let sum = 0;
|
|
6206
|
+
for (let j = 0; j < binSize; j++) {
|
|
6207
|
+
sum += this.data[i * binSize + j] / 255.0;
|
|
6208
|
+
}
|
|
6209
|
+
const target = sum / binSize;
|
|
6210
|
+
this.waveformSmoothed[i] += (target - this.waveformSmoothed[i]) * 0.3;
|
|
6211
|
+
this.waveformData[i] = this.waveformSmoothed[i];
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
};
|
|
6215
|
+
|
|
6216
|
+
// --- SCENE SETUP ---
|
|
6217
|
+
const scene = new THREE.Scene();
|
|
6218
|
+
scene.fog = new THREE.FogExp2(0x000508, 0.005);
|
|
6219
|
+
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 800);
|
|
6220
|
+
camera.position.set(0, 12, CONFIG.defaultZoom);
|
|
6221
|
+
const cameraShakeOffset = new THREE.Vector3();
|
|
6222
|
+
const baseCameraPos = camera.position.clone();
|
|
6223
|
+
const zoomIndicator = document.getElementById('zoom-level');
|
|
6224
|
+
|
|
6225
6225
|
const renderer = new THREE.WebGLRenderer({ powerPreference: "high-performance", antialias: true, alpha: true });
|
|
6226
6226
|
renderer.setClearColor(0x000000, 0);
|
|
6227
|
-
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
6228
|
-
renderer.setPixelRatio(STATE.pixelRatio)
|
|
6229
|
-
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
|
6230
|
-
renderer.toneMappingExposure = 2.1
|
|
6231
|
-
document.body.appendChild(renderer.domElement)
|
|
6232
|
-
|
|
6233
|
-
const controls = new OrbitControls(camera, renderer.domElement)
|
|
6234
|
-
controls.enableDamping = true
|
|
6235
|
-
controls.dampingFactor = 0.06
|
|
6227
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
6228
|
+
renderer.setPixelRatio(STATE.pixelRatio);
|
|
6229
|
+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
6230
|
+
renderer.toneMappingExposure = 2.1;
|
|
6231
|
+
document.body.appendChild(renderer.domElement);
|
|
6232
|
+
|
|
6233
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
6234
|
+
controls.enableDamping = true;
|
|
6235
|
+
controls.dampingFactor = 0.06;
|
|
6236
6236
|
controls.autoRotate = false;
|
|
6237
|
-
controls.autoRotateSpeed = 0.3
|
|
6238
|
-
controls.minDistance = CONFIG.minZoom
|
|
6239
|
-
controls.maxDistance = CONFIG.maxZoom
|
|
6240
|
-
controls.maxPolarAngle = Math.PI * 0.85
|
|
6241
|
-
controls.minPolarAngle = Math.PI * 0.15
|
|
6242
|
-
|
|
6243
|
-
controls.addEventListener('change', () => {
|
|
6244
|
-
const dist = camera.position.length()
|
|
6245
|
-
const zoomPercent = Math.round((1 - (dist - CONFIG.minZoom) / (CONFIG.maxZoom - CONFIG.minZoom)) * 100)
|
|
6246
|
-
zoomIndicator.textContent = Math.max(0, Math.min(100, zoomPercent)) + '%'
|
|
6247
|
-
baseCameraPos.copy(camera.position)
|
|
6248
|
-
})
|
|
6249
|
-
|
|
6250
|
-
const raycaster = new THREE.Raycaster()
|
|
6251
|
-
const mouse = new THREE.Vector2(9999, 9999)
|
|
6252
|
-
const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0)
|
|
6253
|
-
const mouseWorldPos = new THREE.Vector3()
|
|
6254
|
-
let mouseVelocity = new THREE.Vector2()
|
|
6255
|
-
let prevMouse = new THREE.Vector2()
|
|
6256
|
-
|
|
6257
|
-
window.addEventListener('mousemove', (e) => {
|
|
6258
|
-
const newMouse = new THREE.Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1)
|
|
6259
|
-
mouseVelocity.subVectors(newMouse, prevMouse)
|
|
6260
|
-
prevMouse.copy(newMouse)
|
|
6261
|
-
mouse.copy(newMouse)
|
|
6262
|
-
raycaster.setFromCamera(mouse, camera)
|
|
6263
|
-
const target = new THREE.Vector3()
|
|
6264
|
-
raycaster.ray.intersectPlane(plane, target)
|
|
6265
|
-
if (target) mouseWorldPos.copy(target)
|
|
6266
|
-
})
|
|
6267
|
-
|
|
6268
|
-
window.addEventListener('keydown', (e) => {
|
|
6269
|
-
if (e.key === '+' || e.key === '=') { camera.position.multiplyScalar(0.9); controls.update(); }
|
|
6270
|
-
else if (e.key === '-' || e.key === '_') { camera.position.multiplyScalar(1.1); controls.update(); }
|
|
6271
|
-
else if (e.key === '0') { camera.position.normalize().multiplyScalar(CONFIG.defaultZoom); controls.update(); }
|
|
6272
|
-
else if (e.key === ' ') { e.preventDefault(); togglePlayPause(); }
|
|
6237
|
+
controls.autoRotateSpeed = 0.3;
|
|
6238
|
+
controls.minDistance = CONFIG.minZoom;
|
|
6239
|
+
controls.maxDistance = CONFIG.maxZoom;
|
|
6240
|
+
controls.maxPolarAngle = Math.PI * 0.85;
|
|
6241
|
+
controls.minPolarAngle = Math.PI * 0.15;
|
|
6242
|
+
|
|
6243
|
+
controls.addEventListener('change', () => {
|
|
6244
|
+
const dist = camera.position.length();
|
|
6245
|
+
const zoomPercent = Math.round((1 - (dist - CONFIG.minZoom) / (CONFIG.maxZoom - CONFIG.minZoom)) * 100);
|
|
6246
|
+
zoomIndicator.textContent = Math.max(0, Math.min(100, zoomPercent)) + '%';
|
|
6247
|
+
baseCameraPos.copy(camera.position);
|
|
6248
|
+
});
|
|
6249
|
+
|
|
6250
|
+
const raycaster = new THREE.Raycaster();
|
|
6251
|
+
const mouse = new THREE.Vector2(9999, 9999);
|
|
6252
|
+
const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
|
|
6253
|
+
const mouseWorldPos = new THREE.Vector3();
|
|
6254
|
+
let mouseVelocity = new THREE.Vector2();
|
|
6255
|
+
let prevMouse = new THREE.Vector2();
|
|
6256
|
+
|
|
6257
|
+
window.addEventListener('mousemove', (e) => {
|
|
6258
|
+
const newMouse = new THREE.Vector2((e.clientX / window.innerWidth) * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
|
|
6259
|
+
mouseVelocity.subVectors(newMouse, prevMouse);
|
|
6260
|
+
prevMouse.copy(newMouse);
|
|
6261
|
+
mouse.copy(newMouse);
|
|
6262
|
+
raycaster.setFromCamera(mouse, camera);
|
|
6263
|
+
const target = new THREE.Vector3();
|
|
6264
|
+
raycaster.ray.intersectPlane(plane, target);
|
|
6265
|
+
if (target) mouseWorldPos.copy(target);
|
|
6266
|
+
});
|
|
6267
|
+
|
|
6268
|
+
window.addEventListener('keydown', (e) => {
|
|
6269
|
+
if (e.key === '+' || e.key === '=') { camera.position.multiplyScalar(0.9); controls.update(); }
|
|
6270
|
+
else if (e.key === '-' || e.key === '_') { camera.position.multiplyScalar(1.1); controls.update(); }
|
|
6271
|
+
else if (e.key === '0') { camera.position.normalize().multiplyScalar(CONFIG.defaultZoom); controls.update(); }
|
|
6272
|
+
else if (e.key === ' ') { e.preventDefault(); togglePlayPause(); }
|
|
6273
6273
|
else if (e.key === 'f' || e.key === 'F') { STATE.dofEnabled = !STATE.dofEnabled; customDOFPass.enabled = STATE.dofEnabled; }
|
|
6274
|
-
})
|
|
6275
|
-
|
|
6274
|
+
});
|
|
6275
|
+
|
|
6276
6276
|
// Every field type shares one screen-fitted footprint; switching presets never reallocates particles.
|
|
6277
6277
|
const shapeFunction = \`
|
|
6278
6278
|
vec3 shapedPosition(vec3 seed) {
|
|
@@ -6286,100 +6286,100 @@ vec3 shapedPosition(vec3 seed) {
|
|
|
6286
6286
|
const vertexShader = \`
|
|
6287
6287
|
uniform float uTime, uPixelRatio, uSizeBase, uNoiseScale, uCurlStrength;
|
|
6288
6288
|
uniform float uViewportAspect, uViewportHalfHeight;
|
|
6289
|
-
uniform vec3 uColor1, uColor2, uColor3
|
|
6290
|
-
uniform float uSubBass, uBass, uLowMid, uMid, uHighMid, uHigh, uUltraHigh
|
|
6291
|
-
uniform float uBeatEnergy, uSpectralCentroid, uSpectralFlux
|
|
6292
|
-
uniform float uOnsetEnergy, uTerrainMode, uTerrainHeight
|
|
6293
|
-
uniform float uVortexStrength, uPulseIntensity, uZoomFactor
|
|
6294
|
-
uniform vec3 uMousePos
|
|
6295
|
-
uniform vec2 uMouseVelocity
|
|
6296
|
-
attribute vec3 aRandom
|
|
6297
|
-
attribute float aPhase, aLayer
|
|
6298
|
-
varying vec3 vColor
|
|
6289
|
+
uniform vec3 uColor1, uColor2, uColor3;
|
|
6290
|
+
uniform float uSubBass, uBass, uLowMid, uMid, uHighMid, uHigh, uUltraHigh;
|
|
6291
|
+
uniform float uBeatEnergy, uSpectralCentroid, uSpectralFlux;
|
|
6292
|
+
uniform float uOnsetEnergy, uTerrainMode, uTerrainHeight;
|
|
6293
|
+
uniform float uVortexStrength, uPulseIntensity, uZoomFactor;
|
|
6294
|
+
uniform vec3 uMousePos;
|
|
6295
|
+
uniform vec2 uMouseVelocity;
|
|
6296
|
+
attribute vec3 aRandom;
|
|
6297
|
+
attribute float aPhase, aLayer;
|
|
6298
|
+
varying vec3 vColor;
|
|
6299
6299
|
varying float vAlpha, vEnergy, vDepth, vRimLight, vGlint;
|
|
6300
|
-
|
|
6301
|
-
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6302
|
-
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6303
|
-
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
|
|
6304
|
-
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
6305
|
-
|
|
6306
|
-
float snoise(vec3 v) {
|
|
6307
|
-
const vec2 C = vec2(1.0/6.0, 1.0/3.0)
|
|
6308
|
-
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0)
|
|
6309
|
-
vec3 i = floor(v + dot(v, C.yyy))
|
|
6310
|
-
vec3 x0 = v - i + dot(i, C.xxx)
|
|
6311
|
-
vec3 g = step(x0.yzx, x0.xyz)
|
|
6312
|
-
vec3 l = 1.0 - g
|
|
6313
|
-
vec3 i1 = min(g.xyz, l.zxy)
|
|
6314
|
-
vec3 i2 = max(g.xyz, l.zxy)
|
|
6315
|
-
vec3 x1 = x0 - i1 + C.xxx
|
|
6316
|
-
vec3 x2 = x0 - i2 + C.yyy
|
|
6317
|
-
vec3 x3 = x0 - D.yyy
|
|
6318
|
-
i = mod289(i)
|
|
6319
|
-
vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0))
|
|
6320
|
-
float n_ = 0.142857142857
|
|
6321
|
-
vec3 ns = n_ * D.wyz - D.xzx
|
|
6322
|
-
vec4 j = p - 49.0 * floor(p * ns.z * ns.z)
|
|
6323
|
-
vec4 x_ = floor(j * ns.z)
|
|
6324
|
-
vec4 y_ = floor(j - 7.0 * x_)
|
|
6325
|
-
vec4 x = x_ * ns.x + ns.yyyy
|
|
6326
|
-
vec4 y = y_ * ns.x + ns.yyyy
|
|
6327
|
-
vec4 h = 1.0 - abs(x) - abs(y)
|
|
6328
|
-
vec4 b0 = vec4(x.xy, y.xy)
|
|
6329
|
-
vec4 b1 = vec4(x.zw, y.zw)
|
|
6330
|
-
vec4 s0 = floor(b0) * 2.0 + 1.0
|
|
6331
|
-
vec4 s1 = floor(b1) * 2.0 + 1.0
|
|
6332
|
-
vec4 sh = -step(h, vec4(0.0))
|
|
6333
|
-
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy
|
|
6334
|
-
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww
|
|
6335
|
-
vec3 p0 = vec3(a0.xy, h.x)
|
|
6336
|
-
vec3 p1 = vec3(a0.zw, h.y)
|
|
6337
|
-
vec3 p2 = vec3(a1.xy, h.z)
|
|
6338
|
-
vec3 p3 = vec3(a1.zw, h.w)
|
|
6339
|
-
vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)))
|
|
6340
|
-
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w
|
|
6341
|
-
vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0)
|
|
6342
|
-
m = m * m
|
|
6343
|
-
return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)))
|
|
6344
|
-
}
|
|
6345
|
-
|
|
6300
|
+
|
|
6301
|
+
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6302
|
+
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6303
|
+
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
|
|
6304
|
+
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
6305
|
+
|
|
6306
|
+
float snoise(vec3 v) {
|
|
6307
|
+
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
|
|
6308
|
+
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
|
6309
|
+
vec3 i = floor(v + dot(v, C.yyy));
|
|
6310
|
+
vec3 x0 = v - i + dot(i, C.xxx);
|
|
6311
|
+
vec3 g = step(x0.yzx, x0.xyz);
|
|
6312
|
+
vec3 l = 1.0 - g;
|
|
6313
|
+
vec3 i1 = min(g.xyz, l.zxy);
|
|
6314
|
+
vec3 i2 = max(g.xyz, l.zxy);
|
|
6315
|
+
vec3 x1 = x0 - i1 + C.xxx;
|
|
6316
|
+
vec3 x2 = x0 - i2 + C.yyy;
|
|
6317
|
+
vec3 x3 = x0 - D.yyy;
|
|
6318
|
+
i = mod289(i);
|
|
6319
|
+
vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));
|
|
6320
|
+
float n_ = 0.142857142857;
|
|
6321
|
+
vec3 ns = n_ * D.wyz - D.xzx;
|
|
6322
|
+
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
6323
|
+
vec4 x_ = floor(j * ns.z);
|
|
6324
|
+
vec4 y_ = floor(j - 7.0 * x_);
|
|
6325
|
+
vec4 x = x_ * ns.x + ns.yyyy;
|
|
6326
|
+
vec4 y = y_ * ns.x + ns.yyyy;
|
|
6327
|
+
vec4 h = 1.0 - abs(x) - abs(y);
|
|
6328
|
+
vec4 b0 = vec4(x.xy, y.xy);
|
|
6329
|
+
vec4 b1 = vec4(x.zw, y.zw);
|
|
6330
|
+
vec4 s0 = floor(b0) * 2.0 + 1.0;
|
|
6331
|
+
vec4 s1 = floor(b1) * 2.0 + 1.0;
|
|
6332
|
+
vec4 sh = -step(h, vec4(0.0));
|
|
6333
|
+
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
|
6334
|
+
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
|
6335
|
+
vec3 p0 = vec3(a0.xy, h.x);
|
|
6336
|
+
vec3 p1 = vec3(a0.zw, h.y);
|
|
6337
|
+
vec3 p2 = vec3(a1.xy, h.z);
|
|
6338
|
+
vec3 p3 = vec3(a1.zw, h.w);
|
|
6339
|
+
vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
|
|
6340
|
+
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
|
|
6341
|
+
vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
|
|
6342
|
+
m = m * m;
|
|
6343
|
+
return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
|
|
6344
|
+
}
|
|
6345
|
+
|
|
6346
6346
|
vec3 curl(float x, float y, float z) {
|
|
6347
|
-
float eps = 0.08
|
|
6348
|
-
return vec3(
|
|
6349
|
-
snoise(vec3(x, y+eps, z)) - snoise(vec3(x, y-eps, z))
|
|
6350
|
-
snoise(vec3(x, y, z+eps)) - snoise(vec3(x, y, z-eps))
|
|
6351
|
-
snoise(vec3(x+eps, y, z)) - snoise(vec3(x-eps, y, z))
|
|
6352
|
-
)
|
|
6347
|
+
float eps = 0.08;
|
|
6348
|
+
return vec3(
|
|
6349
|
+
snoise(vec3(x, y+eps, z)) - snoise(vec3(x, y-eps, z)),
|
|
6350
|
+
snoise(vec3(x, y, z+eps)) - snoise(vec3(x, y, z-eps)),
|
|
6351
|
+
snoise(vec3(x+eps, y, z)) - snoise(vec3(x-eps, y, z))
|
|
6352
|
+
);
|
|
6353
6353
|
}
|
|
6354
6354
|
|
|
6355
6355
|
\${shapeFunction}
|
|
6356
6356
|
|
|
6357
6357
|
void main() {
|
|
6358
|
-
float t = uTime * (1.0 + uMid * 0.5)
|
|
6359
|
-
vec3 noisePos = position * 0.035 + aRandom
|
|
6360
|
-
float layerMod = 1.0 + aLayer * 0.5
|
|
6361
|
-
|
|
6362
|
-
vec3 flow1 = curl(noisePos.x * uNoiseScale + t*0.1, noisePos.y * uNoiseScale, noisePos.z * uNoiseScale)
|
|
6363
|
-
vec3 flow2 = curl(noisePos.x * uNoiseScale * 2.2 + t*0.07, noisePos.y * uNoiseScale * 2.2 + t*0.08, noisePos.z * uNoiseScale * 2.2) * 0.4
|
|
6364
|
-
vec3 flow3 = curl(noisePos.x * uNoiseScale * 0.5 - t*0.05, noisePos.y * uNoiseScale * 0.5, noisePos.z * uNoiseScale * 0.5) * 0.6 * uLowMid
|
|
6365
|
-
vec3 flow = flow1 + flow2 * uHigh + flow3
|
|
6366
|
-
|
|
6358
|
+
float t = uTime * (1.0 + uMid * 0.5);
|
|
6359
|
+
vec3 noisePos = position * 0.035 + aRandom;
|
|
6360
|
+
float layerMod = 1.0 + aLayer * 0.5;
|
|
6361
|
+
|
|
6362
|
+
vec3 flow1 = curl(noisePos.x * uNoiseScale + t*0.1, noisePos.y * uNoiseScale, noisePos.z * uNoiseScale);
|
|
6363
|
+
vec3 flow2 = curl(noisePos.x * uNoiseScale * 2.2 + t*0.07, noisePos.y * uNoiseScale * 2.2 + t*0.08, noisePos.z * uNoiseScale * 2.2) * 0.4;
|
|
6364
|
+
vec3 flow3 = curl(noisePos.x * uNoiseScale * 0.5 - t*0.05, noisePos.y * uNoiseScale * 0.5, noisePos.z * uNoiseScale * 0.5) * 0.6 * uLowMid;
|
|
6365
|
+
vec3 flow = flow1 + flow2 * uHigh + flow3;
|
|
6366
|
+
|
|
6367
6367
|
vec3 newPos = shapedPosition(aRandom) + (flow * uCurlStrength * layerMod * (1.0 + uSpectralFlux * 2.5));
|
|
6368
|
-
|
|
6368
|
+
|
|
6369
6369
|
float vortexAngle = uVortexStrength * uBass * 0.45 * (0.75 + aRandom.z * 0.5);
|
|
6370
|
-
float cosA = cos(vortexAngle), sinA = sin(vortexAngle)
|
|
6371
|
-
vec2 rotated = vec2(newPos.x * cosA - newPos.z * sinA, newPos.x * sinA + newPos.z * cosA)
|
|
6372
|
-
newPos.xz = mix(newPos.xz, rotated, uBass * uVortexStrength)
|
|
6373
|
-
|
|
6374
|
-
float yVortex = sin(newPos.y * 0.1 + uTime) * uMid * uVortexStrength * 2.0
|
|
6375
|
-
newPos.x += cos(newPos.y * 0.15) * yVortex
|
|
6376
|
-
newPos.z += sin(newPos.y * 0.15) * yVortex
|
|
6377
|
-
|
|
6378
|
-
float distToMouse = distance(newPos, uMousePos)
|
|
6379
|
-
float repulsion = smoothstep(25.0, 0.0, distToMouse)
|
|
6380
|
-
vec3 repelDir = normalize(newPos - uMousePos + vec3(0.001))
|
|
6381
|
-
newPos += repelDir * repulsion * (12.0 + length(uMouseVelocity) * 360.0)
|
|
6382
|
-
|
|
6370
|
+
float cosA = cos(vortexAngle), sinA = sin(vortexAngle);
|
|
6371
|
+
vec2 rotated = vec2(newPos.x * cosA - newPos.z * sinA, newPos.x * sinA + newPos.z * cosA);
|
|
6372
|
+
newPos.xz = mix(newPos.xz, rotated, uBass * uVortexStrength);
|
|
6373
|
+
|
|
6374
|
+
float yVortex = sin(newPos.y * 0.1 + uTime) * uMid * uVortexStrength * 2.0;
|
|
6375
|
+
newPos.x += cos(newPos.y * 0.15) * yVortex;
|
|
6376
|
+
newPos.z += sin(newPos.y * 0.15) * yVortex;
|
|
6377
|
+
|
|
6378
|
+
float distToMouse = distance(newPos, uMousePos);
|
|
6379
|
+
float repulsion = smoothstep(25.0, 0.0, distToMouse);
|
|
6380
|
+
vec3 repelDir = normalize(newPos - uMousePos + vec3(0.001));
|
|
6381
|
+
newPos += repelDir * repulsion * (12.0 + length(uMouseVelocity) * 360.0);
|
|
6382
|
+
|
|
6383
6383
|
// Audio changes depth throughout the plane, without evacuating its centre.
|
|
6384
6384
|
float beatWave = sin(aRandom.x * 12.0 + aRandom.y * 7.0 - uTime * 6.0);
|
|
6385
6385
|
float bassDepth = (beatWave * uBeatEnergy * uPulseIntensity * 3.5
|
|
@@ -6389,7 +6389,7 @@ void main() {
|
|
|
6389
6389
|
newPos.z += bassDepth;
|
|
6390
6390
|
float fieldWave = sin(aRandom.x * 11.0 - uTime * 0.8 + sin(aRandom.y * 7.0 + uTime * 0.35));
|
|
6391
6391
|
newPos.z += fieldWave * (1.8 + uBass * 2.4 + uBeatEnergy * 1.6);
|
|
6392
|
-
|
|
6392
|
+
|
|
6393
6393
|
// Waveform relief moves toward the camera while retaining the shared rectangular footprint.
|
|
6394
6394
|
if (uTerrainMode > 0.5) {
|
|
6395
6395
|
float freqBin = aRandom.x;
|
|
@@ -6399,329 +6399,329 @@ void main() {
|
|
|
6399
6399
|
waveHeight *= (1.0 + uSpectralFlux * 3.0);
|
|
6400
6400
|
newPos.z += waveHeight * uTerrainHeight;
|
|
6401
6401
|
newPos.z += sin(aRandom.y * 6.28 * 4.0 + uTime) * uSubBass * 5.0;
|
|
6402
|
-
}
|
|
6403
|
-
|
|
6404
|
-
vec4 mvPosition = modelViewMatrix * vec4(newPos, 1.0)
|
|
6405
|
-
gl_Position = projectionMatrix * mvPosition
|
|
6406
|
-
|
|
6402
|
+
}
|
|
6403
|
+
|
|
6404
|
+
vec4 mvPosition = modelViewMatrix * vec4(newPos, 1.0);
|
|
6405
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
6406
|
+
|
|
6407
6407
|
float size = uSizeBase * (0.85 + uBass * 0.5 + uBeatEnergy * 0.5) * (1.0 + (uZoomFactor - 1.0) * 0.3);
|
|
6408
6408
|
size *= 1.0 + sin(uTime * 10.0 + aPhase * 6.28) * uHigh * 0.35;
|
|
6409
6409
|
vGlint = smoothstep(0.985, 0.998, aRandom.z) * (0.7 + 0.3 * sin(uTime * 3.0 + aPhase * 6.28)) * (0.75 + uHigh * 0.15 + uOnsetEnergy * 0.1);
|
|
6410
6410
|
size *= 0.85 + vGlint * 2.1;
|
|
6411
6411
|
gl_PointSize = clamp(max(size * uPixelRatio * (90.0 / -mvPosition.z) * layerMod, vGlint * 4.5 * uPixelRatio), 0.5, 120.0);
|
|
6412
|
-
|
|
6413
|
-
float colorMix = smoothstep(0.0, 2.8, length(flow) + uHighMid * 3.0)
|
|
6414
|
-
vec3 baseColor = mix(uColor1, uColor2, colorMix)
|
|
6415
|
-
baseColor = mix(baseColor, uColor3, uSpectralCentroid * uHigh * 2.5)
|
|
6416
|
-
|
|
6417
|
-
float energyLevel = uBeatEnergy * 0.35 + repulsion * uMid * 2.0 + uSpectralFlux * 0.8
|
|
6418
|
-
vColor = mix(baseColor, vec3(1.0), energyLevel * 0.4)
|
|
6419
|
-
vColor += vec3(0.08, 0.04, 0.12) * uUltraHigh * 2.5
|
|
6420
|
-
|
|
6421
|
-
vRimLight = pow(1.0 - max(0.0, dot(normalize(-mvPosition.xyz), normalize(newPos))), 3.0)
|
|
6422
|
-
vEnergy = energyLevel
|
|
6423
|
-
vDepth = clamp(-mvPosition.z / 120.0, 0.0, 1.0)
|
|
6424
|
-
|
|
6412
|
+
|
|
6413
|
+
float colorMix = smoothstep(0.0, 2.8, length(flow) + uHighMid * 3.0);
|
|
6414
|
+
vec3 baseColor = mix(uColor1, uColor2, colorMix);
|
|
6415
|
+
baseColor = mix(baseColor, uColor3, uSpectralCentroid * uHigh * 2.5);
|
|
6416
|
+
|
|
6417
|
+
float energyLevel = uBeatEnergy * 0.35 + repulsion * uMid * 2.0 + uSpectralFlux * 0.8;
|
|
6418
|
+
vColor = mix(baseColor, vec3(1.0), energyLevel * 0.4);
|
|
6419
|
+
vColor += vec3(0.08, 0.04, 0.12) * uUltraHigh * 2.5;
|
|
6420
|
+
|
|
6421
|
+
vRimLight = pow(1.0 - max(0.0, dot(normalize(-mvPosition.xyz), normalize(newPos))), 3.0);
|
|
6422
|
+
vEnergy = energyLevel;
|
|
6423
|
+
vDepth = clamp(-mvPosition.z / 120.0, 0.0, 1.0);
|
|
6424
|
+
|
|
6425
6425
|
float alpha = 1.0 - smoothstep(0.92, 1.05, max(abs(newPos.x) / (uViewportAspect * uViewportHalfHeight * 0.82), abs(newPos.y) / (uViewportHalfHeight * 0.82)));
|
|
6426
|
-
float sparkle = 1.0 + (aRandom.x > 0.8 ? sin(uTime * 20.0 + aRandom.y * 150.0) * uHigh * 2.5 : 0.0)
|
|
6427
|
-
vAlpha = alpha * sparkle * (0.7 + uMid * 0.35) * layerMod
|
|
6428
|
-
}
|
|
6429
|
-
|
|
6430
|
-
const fragmentShader =
|
|
6431
|
-
varying vec3 vColor
|
|
6426
|
+
float sparkle = 1.0 + (aRandom.x > 0.8 ? sin(uTime * 20.0 + aRandom.y * 150.0) * uHigh * 2.5 : 0.0);
|
|
6427
|
+
vAlpha = alpha * sparkle * (0.7 + uMid * 0.35) * layerMod;
|
|
6428
|
+
}\`;
|
|
6429
|
+
|
|
6430
|
+
const fragmentShader = \`
|
|
6431
|
+
varying vec3 vColor;
|
|
6432
6432
|
varying float vAlpha, vEnergy, vDepth, vRimLight, vGlint;
|
|
6433
|
-
|
|
6434
|
-
void main() {
|
|
6435
|
-
vec2 xy = gl_PointCoord.xy - vec2(0.5)
|
|
6436
|
-
float r = length(xy)
|
|
6437
|
-
if(r > 0.5) discard
|
|
6438
|
-
|
|
6439
|
-
float core = smoothstep(0.16 - vEnergy * 0.04, 0.12 - vEnergy * 0.04, r)
|
|
6433
|
+
|
|
6434
|
+
void main() {
|
|
6435
|
+
vec2 xy = gl_PointCoord.xy - vec2(0.5);
|
|
6436
|
+
float r = length(xy);
|
|
6437
|
+
if(r > 0.5) discard;
|
|
6438
|
+
|
|
6439
|
+
float core = smoothstep(0.16 - vEnergy * 0.04, 0.12 - vEnergy * 0.04, r);
|
|
6440
6440
|
float glow = exp(-r * r * (20.0 - vEnergy * 10.0));
|
|
6441
6441
|
float halo = exp(-r * r * 5.0) * 0.35 + exp(-r * r * 2.5) * 0.15;
|
|
6442
6442
|
float glint = vGlint * (1.0 - smoothstep(0.012, 0.045, min(abs(xy.x), abs(xy.y)))) * (1.0 - smoothstep(0.16, 0.48, r));
|
|
6443
6443
|
|
|
6444
6444
|
vec3 finalColor = mix(vColor, vec3(1.0), core * 0.58 + glint * 0.38);
|
|
6445
6445
|
finalColor += vColor * vRimLight * 0.1;
|
|
6446
|
-
finalColor = mix(finalColor, vec3(0.02, 0.03, 0.05), vDepth * 0.4)
|
|
6447
|
-
|
|
6446
|
+
finalColor = mix(finalColor, vec3(0.02, 0.03, 0.05), vDepth * 0.4);
|
|
6447
|
+
|
|
6448
6448
|
float finalAlpha = clamp(vAlpha * (glow * 0.85 + halo * 0.55 + pow(1.0 - r * 2.0, 2.5) * 0.55 + glint * 0.9), 0.0, 1.0);
|
|
6449
|
-
gl_FragColor = vec4(finalColor, finalAlpha)
|
|
6450
|
-
}
|
|
6451
|
-
|
|
6452
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 POST-PROCESSING SHADERS \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
6453
|
-
|
|
6454
|
-
// GOD RAYS SHADER (Radial Light Scattering)
|
|
6455
|
-
const GodRaysShader = {
|
|
6456
|
-
uniforms: {
|
|
6457
|
-
tDiffuse: { value: null }
|
|
6458
|
-
uCenter: { value: new THREE.Vector2(0.5, 0.5) }
|
|
6459
|
-
uIntensity: { value: 0.35 }
|
|
6460
|
-
uDecay: { value: 0.94 }
|
|
6461
|
-
uBeatEnergy: { value: 0.0 }
|
|
6462
|
-
}
|
|
6463
|
-
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
|
|
6464
|
-
fragmentShader:
|
|
6465
|
-
uniform sampler2D tDiffuse
|
|
6466
|
-
uniform vec2 uCenter
|
|
6467
|
-
uniform float uIntensity, uDecay, uBeatEnergy
|
|
6468
|
-
varying vec2 vUv
|
|
6469
|
-
|
|
6470
|
-
void main() {
|
|
6471
|
-
vec4 color = texture2D(tDiffuse, vUv)
|
|
6472
|
-
vec2 dir = vUv - uCenter
|
|
6473
|
-
float dist = length(dir)
|
|
6474
|
-
dir = normalize(dir)
|
|
6475
|
-
|
|
6476
|
-
vec3 rays = vec3(0.0)
|
|
6477
|
-
float decay = 1.0
|
|
6478
|
-
|
|
6479
|
-
for(int i = 0; i < 32; i++) {
|
|
6480
|
-
float t = float(i) / 32.0
|
|
6481
|
-
vec2 samplePos = vUv - dir * t * 0.15 * (1.0 + uBeatEnergy)
|
|
6482
|
-
vec4 s = texture2D(tDiffuse, samplePos)
|
|
6483
|
-
float lum = dot(s.rgb, vec3(0.3, 0.59, 0.11))
|
|
6484
|
-
rays += s.rgb * decay * step(0.35, lum)
|
|
6485
|
-
decay *= uDecay
|
|
6486
|
-
}
|
|
6487
|
-
rays /= 16.0
|
|
6488
|
-
rays *= (1.0 - smoothstep(0.3, 0.9, dist))
|
|
6489
|
-
|
|
6449
|
+
gl_FragColor = vec4(finalColor, finalAlpha);
|
|
6450
|
+
}\`;
|
|
6451
|
+
|
|
6452
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 POST-PROCESSING SHADERS \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
6453
|
+
|
|
6454
|
+
// GOD RAYS SHADER (Radial Light Scattering)
|
|
6455
|
+
const GodRaysShader = {
|
|
6456
|
+
uniforms: {
|
|
6457
|
+
tDiffuse: { value: null },
|
|
6458
|
+
uCenter: { value: new THREE.Vector2(0.5, 0.5) },
|
|
6459
|
+
uIntensity: { value: 0.35 },
|
|
6460
|
+
uDecay: { value: 0.94 },
|
|
6461
|
+
uBeatEnergy: { value: 0.0 }
|
|
6462
|
+
},
|
|
6463
|
+
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
6464
|
+
fragmentShader: \`
|
|
6465
|
+
uniform sampler2D tDiffuse;
|
|
6466
|
+
uniform vec2 uCenter;
|
|
6467
|
+
uniform float uIntensity, uDecay, uBeatEnergy;
|
|
6468
|
+
varying vec2 vUv;
|
|
6469
|
+
|
|
6470
|
+
void main() {
|
|
6471
|
+
vec4 color = texture2D(tDiffuse, vUv);
|
|
6472
|
+
vec2 dir = vUv - uCenter;
|
|
6473
|
+
float dist = length(dir);
|
|
6474
|
+
dir = normalize(dir);
|
|
6475
|
+
|
|
6476
|
+
vec3 rays = vec3(0.0);
|
|
6477
|
+
float decay = 1.0;
|
|
6478
|
+
|
|
6479
|
+
for(int i = 0; i < 32; i++) {
|
|
6480
|
+
float t = float(i) / 32.0;
|
|
6481
|
+
vec2 samplePos = vUv - dir * t * 0.15 * (1.0 + uBeatEnergy);
|
|
6482
|
+
vec4 s = texture2D(tDiffuse, samplePos);
|
|
6483
|
+
float lum = dot(s.rgb, vec3(0.3, 0.59, 0.11));
|
|
6484
|
+
rays += s.rgb * decay * step(0.35, lum);
|
|
6485
|
+
decay *= uDecay;
|
|
6486
|
+
}
|
|
6487
|
+
rays /= 16.0;
|
|
6488
|
+
rays *= (1.0 - smoothstep(0.3, 0.9, dist));
|
|
6489
|
+
|
|
6490
6490
|
gl_FragColor = vec4(color.rgb + rays * uIntensity * (1.0 + uBeatEnergy * 0.8), color.a);
|
|
6491
|
-
}
|
|
6492
|
-
}
|
|
6493
|
-
|
|
6494
|
-
// LENS FLARE POST-PROCESS SHADER (Cinematic)
|
|
6495
|
-
const LensFlareShader = {
|
|
6496
|
-
uniforms: {
|
|
6497
|
-
tDiffuse: { value: null }
|
|
6498
|
-
uIntensity: { value: 1.0 }
|
|
6499
|
-
uBeatEnergy: { value: 0.0 }
|
|
6500
|
-
uTime: { value: 0.0 }
|
|
6501
|
-
}
|
|
6502
|
-
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
|
|
6503
|
-
fragmentShader:
|
|
6504
|
-
uniform sampler2D tDiffuse
|
|
6505
|
-
uniform float uIntensity, uBeatEnergy, uTime
|
|
6506
|
-
varying vec2 vUv
|
|
6507
|
-
|
|
6508
|
-
float sdHex(vec2 p) {
|
|
6509
|
-
p = abs(p)
|
|
6510
|
-
return max(p.x * 0.866 + p.y * 0.5, p.y) - 1.0
|
|
6511
|
-
}
|
|
6512
|
-
|
|
6513
|
-
void main() {
|
|
6514
|
-
vec4 color = texture2D(tDiffuse, vUv)
|
|
6515
|
-
vec2 center = vec2(0.5)
|
|
6516
|
-
vec2 toCenter = center - vUv
|
|
6517
|
-
float dist = length(toCenter)
|
|
6518
|
-
vec2 dir = normalize(toCenter + 0.0001)
|
|
6519
|
-
|
|
6520
|
-
vec3 flare = vec3(0.0)
|
|
6521
|
-
|
|
6522
|
-
// Sample bright areas for flare source
|
|
6523
|
-
float brightness = dot(color.rgb, vec3(0.299, 0.587, 0.114))
|
|
6524
|
-
float threshold = smoothstep(0.6, 0.9, brightness)
|
|
6525
|
-
|
|
6526
|
-
// Anamorphic horizontal streak
|
|
6527
|
-
float streak = 0.0
|
|
6528
|
-
for(float i = -8.0; i <= 8.0; i += 1.0) {
|
|
6529
|
-
vec2 streakUV = vUv + vec2(i * 0.012, 0.0)
|
|
6530
|
-
vec4 streakSample = texture2D(tDiffuse, streakUV)
|
|
6531
|
-
float streakBright = dot(streakSample.rgb, vec3(0.299, 0.587, 0.114))
|
|
6532
|
-
float falloff = 1.0 - abs(i) / 10.0
|
|
6533
|
-
streak += smoothstep(0.7, 1.0, streakBright) * falloff * falloff
|
|
6534
|
-
}
|
|
6535
|
-
flare += vec3(0.6, 0.7, 1.0) * streak * 0.08
|
|
6536
|
-
|
|
6537
|
-
// Subtle ghosts (reflections through lens elements)
|
|
6538
|
-
for(int i = 1; i <= 3; i++) {
|
|
6539
|
-
float scale = 1.0 - float(i) * 0.25
|
|
6540
|
-
vec2 ghostUV = center + (center - vUv) * scale * 0.8
|
|
6541
|
-
if(ghostUV.x > 0.0 && ghostUV.x < 1.0 && ghostUV.y > 0.0 && ghostUV.y < 1.0) {
|
|
6542
|
-
vec4 ghostSample = texture2D(tDiffuse, ghostUV)
|
|
6543
|
-
float ghostBright = dot(ghostSample.rgb, vec3(0.299, 0.587, 0.114))
|
|
6544
|
-
float ghostAlpha = smoothstep(0.5, 0.8, ghostBright) * (0.25 / float(i))
|
|
6545
|
-
vec3 ghostTint = vec3(0.7, 0.8, 1.0) * (1.0 - float(i) * 0.2)
|
|
6546
|
-
flare += ghostSample.rgb * ghostTint * ghostAlpha
|
|
6547
|
-
}
|
|
6548
|
-
}
|
|
6549
|
-
|
|
6550
|
-
// Soft circular halo around bright center
|
|
6551
|
-
float halo = smoothstep(0.25, 0.15, abs(dist - 0.2)) * threshold * 0.25
|
|
6552
|
-
flare += vec3(0.5, 0.6, 0.9) * halo
|
|
6553
|
-
|
|
6554
|
-
// Subtle vignette-edge glow
|
|
6555
|
-
float edgeGlow = smoothstep(0.5, 0.7, dist) * (1.0 - smoothstep(0.7, 0.85, dist))
|
|
6556
|
-
flare += vec3(0.3, 0.4, 0.6) * edgeGlow * brightness * 0.15
|
|
6557
|
-
|
|
6491
|
+
}\`
|
|
6492
|
+
};
|
|
6493
|
+
|
|
6494
|
+
// LENS FLARE POST-PROCESS SHADER (Cinematic)
|
|
6495
|
+
const LensFlareShader = {
|
|
6496
|
+
uniforms: {
|
|
6497
|
+
tDiffuse: { value: null },
|
|
6498
|
+
uIntensity: { value: 1.0 },
|
|
6499
|
+
uBeatEnergy: { value: 0.0 },
|
|
6500
|
+
uTime: { value: 0.0 }
|
|
6501
|
+
},
|
|
6502
|
+
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
6503
|
+
fragmentShader: \`
|
|
6504
|
+
uniform sampler2D tDiffuse;
|
|
6505
|
+
uniform float uIntensity, uBeatEnergy, uTime;
|
|
6506
|
+
varying vec2 vUv;
|
|
6507
|
+
|
|
6508
|
+
float sdHex(vec2 p) {
|
|
6509
|
+
p = abs(p);
|
|
6510
|
+
return max(p.x * 0.866 + p.y * 0.5, p.y) - 1.0;
|
|
6511
|
+
}
|
|
6512
|
+
|
|
6513
|
+
void main() {
|
|
6514
|
+
vec4 color = texture2D(tDiffuse, vUv);
|
|
6515
|
+
vec2 center = vec2(0.5);
|
|
6516
|
+
vec2 toCenter = center - vUv;
|
|
6517
|
+
float dist = length(toCenter);
|
|
6518
|
+
vec2 dir = normalize(toCenter + 0.0001);
|
|
6519
|
+
|
|
6520
|
+
vec3 flare = vec3(0.0);
|
|
6521
|
+
|
|
6522
|
+
// Sample bright areas for flare source
|
|
6523
|
+
float brightness = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
6524
|
+
float threshold = smoothstep(0.6, 0.9, brightness);
|
|
6525
|
+
|
|
6526
|
+
// Anamorphic horizontal streak
|
|
6527
|
+
float streak = 0.0;
|
|
6528
|
+
for(float i = -8.0; i <= 8.0; i += 1.0) {
|
|
6529
|
+
vec2 streakUV = vUv + vec2(i * 0.012, 0.0);
|
|
6530
|
+
vec4 streakSample = texture2D(tDiffuse, streakUV);
|
|
6531
|
+
float streakBright = dot(streakSample.rgb, vec3(0.299, 0.587, 0.114));
|
|
6532
|
+
float falloff = 1.0 - abs(i) / 10.0;
|
|
6533
|
+
streak += smoothstep(0.7, 1.0, streakBright) * falloff * falloff;
|
|
6534
|
+
}
|
|
6535
|
+
flare += vec3(0.6, 0.7, 1.0) * streak * 0.08;
|
|
6536
|
+
|
|
6537
|
+
// Subtle ghosts (reflections through lens elements)
|
|
6538
|
+
for(int i = 1; i <= 3; i++) {
|
|
6539
|
+
float scale = 1.0 - float(i) * 0.25;
|
|
6540
|
+
vec2 ghostUV = center + (center - vUv) * scale * 0.8;
|
|
6541
|
+
if(ghostUV.x > 0.0 && ghostUV.x < 1.0 && ghostUV.y > 0.0 && ghostUV.y < 1.0) {
|
|
6542
|
+
vec4 ghostSample = texture2D(tDiffuse, ghostUV);
|
|
6543
|
+
float ghostBright = dot(ghostSample.rgb, vec3(0.299, 0.587, 0.114));
|
|
6544
|
+
float ghostAlpha = smoothstep(0.5, 0.8, ghostBright) * (0.25 / float(i));
|
|
6545
|
+
vec3 ghostTint = vec3(0.7, 0.8, 1.0) * (1.0 - float(i) * 0.2);
|
|
6546
|
+
flare += ghostSample.rgb * ghostTint * ghostAlpha;
|
|
6547
|
+
}
|
|
6548
|
+
}
|
|
6549
|
+
|
|
6550
|
+
// Soft circular halo around bright center
|
|
6551
|
+
float halo = smoothstep(0.25, 0.15, abs(dist - 0.2)) * threshold * 0.25;
|
|
6552
|
+
flare += vec3(0.5, 0.6, 0.9) * halo;
|
|
6553
|
+
|
|
6554
|
+
// Subtle vignette-edge glow
|
|
6555
|
+
float edgeGlow = smoothstep(0.5, 0.7, dist) * (1.0 - smoothstep(0.7, 0.85, dist));
|
|
6556
|
+
flare += vec3(0.3, 0.4, 0.6) * edgeGlow * brightness * 0.15;
|
|
6557
|
+
|
|
6558
6558
|
gl_FragColor = vec4(color.rgb + flare * uIntensity * (1.0 + uBeatEnergy * 0.3), color.a);
|
|
6559
|
-
}
|
|
6560
|
-
}
|
|
6561
|
-
|
|
6562
|
-
// ANAMORPHIC FLARE SHADER
|
|
6563
|
-
const AnamorphicShader = {
|
|
6564
|
-
uniforms: {
|
|
6565
|
-
tDiffuse: { value: null }
|
|
6566
|
-
uStretch: { value: 0.1 }
|
|
6567
|
-
uThreshold: { value: 0.7 }
|
|
6568
|
-
uIntensity: { value: 0.4 }
|
|
6569
|
-
}
|
|
6570
|
-
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
|
|
6571
|
-
fragmentShader:
|
|
6572
|
-
uniform sampler2D tDiffuse
|
|
6573
|
-
uniform float uStretch, uThreshold, uIntensity
|
|
6574
|
-
varying vec2 vUv
|
|
6575
|
-
void main() {
|
|
6576
|
-
vec4 color = texture2D(tDiffuse, vUv)
|
|
6577
|
-
vec3 streak = vec3(0.0)
|
|
6578
|
-
for(float i = -15.0; i <= 15.0; i++) {
|
|
6579
|
-
vec4 s = texture2D(tDiffuse, vUv + vec2(i * uStretch * 0.01, 0.0))
|
|
6580
|
-
float bright = dot(s.rgb, vec3(0.299, 0.587, 0.114))
|
|
6581
|
-
if(bright > uThreshold) {
|
|
6582
|
-
float w = 1.0 - abs(i) / 15.0
|
|
6583
|
-
streak += s.rgb * w * w
|
|
6584
|
-
}
|
|
6585
|
-
}
|
|
6586
|
-
gl_FragColor = vec4(color.rgb + streak / 7.5 * vec3(0.6, 0.8, 1.0) * uIntensity, color.a)
|
|
6587
|
-
}
|
|
6588
|
-
}
|
|
6589
|
-
|
|
6590
|
-
// CHROMATIC ABERRATION SHADER
|
|
6591
|
-
const ChromaticShader = {
|
|
6592
|
-
uniforms: {
|
|
6593
|
-
tDiffuse: { value: null }
|
|
6594
|
-
uIntensity: { value: 0.004 }
|
|
6595
|
-
uBeatEnergy: { value: 0.0 }
|
|
6596
|
-
}
|
|
6597
|
-
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
|
|
6598
|
-
fragmentShader:
|
|
6599
|
-
uniform sampler2D tDiffuse
|
|
6600
|
-
uniform float uIntensity, uBeatEnergy
|
|
6601
|
-
varying vec2 vUv
|
|
6602
|
-
void main() {
|
|
6603
|
-
vec2 dir = vUv - vec2(0.5)
|
|
6604
|
-
float dist = length(dir)
|
|
6605
|
-
vec2 offset = dir * dist * uIntensity * (1.0 + uBeatEnergy * 1.5)
|
|
6606
|
-
gl_FragColor = vec4(
|
|
6607
|
-
texture2D(tDiffuse, vUv + offset * 1.2).r
|
|
6608
|
-
texture2D(tDiffuse, vUv).g
|
|
6609
|
-
texture2D(tDiffuse, vUv - offset * 1.2).b
|
|
6559
|
+
}\`
|
|
6560
|
+
};
|
|
6561
|
+
|
|
6562
|
+
// ANAMORPHIC FLARE SHADER
|
|
6563
|
+
const AnamorphicShader = {
|
|
6564
|
+
uniforms: {
|
|
6565
|
+
tDiffuse: { value: null },
|
|
6566
|
+
uStretch: { value: 0.1 },
|
|
6567
|
+
uThreshold: { value: 0.7 },
|
|
6568
|
+
uIntensity: { value: 0.4 }
|
|
6569
|
+
},
|
|
6570
|
+
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
6571
|
+
fragmentShader: \`
|
|
6572
|
+
uniform sampler2D tDiffuse;
|
|
6573
|
+
uniform float uStretch, uThreshold, uIntensity;
|
|
6574
|
+
varying vec2 vUv;
|
|
6575
|
+
void main() {
|
|
6576
|
+
vec4 color = texture2D(tDiffuse, vUv);
|
|
6577
|
+
vec3 streak = vec3(0.0);
|
|
6578
|
+
for(float i = -15.0; i <= 15.0; i++) {
|
|
6579
|
+
vec4 s = texture2D(tDiffuse, vUv + vec2(i * uStretch * 0.01, 0.0));
|
|
6580
|
+
float bright = dot(s.rgb, vec3(0.299, 0.587, 0.114));
|
|
6581
|
+
if(bright > uThreshold) {
|
|
6582
|
+
float w = 1.0 - abs(i) / 15.0;
|
|
6583
|
+
streak += s.rgb * w * w;
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
gl_FragColor = vec4(color.rgb + streak / 7.5 * vec3(0.6, 0.8, 1.0) * uIntensity, color.a);
|
|
6587
|
+
}\`
|
|
6588
|
+
};
|
|
6589
|
+
|
|
6590
|
+
// CHROMATIC ABERRATION SHADER
|
|
6591
|
+
const ChromaticShader = {
|
|
6592
|
+
uniforms: {
|
|
6593
|
+
tDiffuse: { value: null },
|
|
6594
|
+
uIntensity: { value: 0.004 },
|
|
6595
|
+
uBeatEnergy: { value: 0.0 }
|
|
6596
|
+
},
|
|
6597
|
+
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
6598
|
+
fragmentShader: \`
|
|
6599
|
+
uniform sampler2D tDiffuse;
|
|
6600
|
+
uniform float uIntensity, uBeatEnergy;
|
|
6601
|
+
varying vec2 vUv;
|
|
6602
|
+
void main() {
|
|
6603
|
+
vec2 dir = vUv - vec2(0.5);
|
|
6604
|
+
float dist = length(dir);
|
|
6605
|
+
vec2 offset = dir * dist * uIntensity * (1.0 + uBeatEnergy * 1.5);
|
|
6606
|
+
gl_FragColor = vec4(
|
|
6607
|
+
texture2D(tDiffuse, vUv + offset * 1.2).r,
|
|
6608
|
+
texture2D(tDiffuse, vUv).g,
|
|
6609
|
+
texture2D(tDiffuse, vUv - offset * 1.2).b,
|
|
6610
6610
|
texture2D(tDiffuse, vUv).a
|
|
6611
|
-
)
|
|
6612
|
-
}
|
|
6613
|
-
}
|
|
6614
|
-
|
|
6615
|
-
// FILM GRAIN SHADER
|
|
6616
|
-
const GrainShader = {
|
|
6617
|
-
uniforms: {
|
|
6618
|
-
tDiffuse: { value: null }
|
|
6619
|
-
uTime: { value: 0.0 }
|
|
6620
|
-
uIntensity: { value: 0.0 }
|
|
6621
|
-
}
|
|
6622
|
-
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
|
|
6623
|
-
fragmentShader:
|
|
6624
|
-
uniform sampler2D tDiffuse
|
|
6625
|
-
uniform float uTime, uIntensity
|
|
6626
|
-
varying vec2 vUv
|
|
6627
|
-
float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453); }
|
|
6628
|
-
void main() {
|
|
6629
|
-
vec4 color = texture2D(tDiffuse, vUv)
|
|
6630
|
-
float grain = (rand(vUv + fract(uTime)) * 2.0 - 1.0) * uIntensity
|
|
6631
|
-
gl_FragColor = vec4(color.rgb + grain * (1.0 - dot(color.rgb, vec3(0.299, 0.587, 0.114)) * 0.5), color.a)
|
|
6632
|
-
}
|
|
6633
|
-
}
|
|
6634
|
-
|
|
6635
|
-
// CUSTOM DEPTH OF FIELD SHADER (Radial + Edge blur)
|
|
6636
|
-
const CustomDOFShader = {
|
|
6637
|
-
uniforms: {
|
|
6638
|
-
tDiffuse: { value: null }
|
|
6639
|
-
uFocus: { value: 0.05 }
|
|
6640
|
-
uFocalLength: { value: 0.25 }
|
|
6641
|
-
uBokehStrength: { value: 0.5 }
|
|
6642
|
-
uResolution: { value: new THREE.Vector2() }
|
|
6643
|
-
uBeatEnergy: { value: 0.0 }
|
|
6644
|
-
}
|
|
6645
|
-
vertexShader:
|
|
6646
|
-
varying vec2 vUv
|
|
6647
|
-
void main() {
|
|
6648
|
-
vUv = uv
|
|
6649
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0)
|
|
6650
|
-
}
|
|
6651
|
-
|
|
6652
|
-
fragmentShader:
|
|
6653
|
-
uniform sampler2D tDiffuse
|
|
6654
|
-
uniform float uFocus
|
|
6655
|
-
uniform float uFocalLength
|
|
6656
|
-
uniform float uBokehStrength
|
|
6657
|
-
uniform vec2 uResolution
|
|
6658
|
-
uniform float uBeatEnergy
|
|
6659
|
-
varying vec2 vUv
|
|
6660
|
-
|
|
6661
|
-
void main() {
|
|
6662
|
-
vec2 center = vec2(0.5)
|
|
6663
|
-
float dist = length(vUv - center)
|
|
6664
|
-
|
|
6665
|
-
// Focus ring - sharp in a ring around center, blurry inside and outside
|
|
6666
|
-
float focusRing = abs(dist - uFocus)
|
|
6667
|
-
float blur = smoothstep(0.0, uFocalLength, focusRing) * uBokehStrength
|
|
6668
|
-
|
|
6669
|
-
vec3 color = vec3(0.0)
|
|
6670
|
-
float total = 0.0
|
|
6671
|
-
vec2 texel = 1.0 / uResolution
|
|
6672
|
-
|
|
6673
|
-
if(blur < 0.01) {
|
|
6674
|
-
color = texture2D(tDiffuse, vUv).rgb
|
|
6675
|
-
total = 1.0
|
|
6676
|
-
} else {
|
|
6677
|
-
// Golden angle spiral sampling
|
|
6678
|
-
const float GOLDEN_ANGLE = 2.39996323
|
|
6679
|
-
const int SAMPLES = 24
|
|
6680
|
-
float radius = blur * 20.0 * (1.0 + uBeatEnergy * 0.1)
|
|
6681
|
-
|
|
6682
|
-
for(int i = 0; i < SAMPLES; i++) {
|
|
6683
|
-
float angle = float(i) * GOLDEN_ANGLE
|
|
6684
|
-
float r = sqrt(float(i) / float(SAMPLES)) * radius
|
|
6685
|
-
vec2 offset = vec2(cos(angle), sin(angle)) * r * texel
|
|
6686
|
-
vec3 s = texture2D(tDiffuse, vUv + offset).rgb
|
|
6687
|
-
|
|
6688
|
-
// Subtle highlight boost
|
|
6689
|
-
float lum = dot(s, vec3(0.299, 0.587, 0.114))
|
|
6690
|
-
s *= 1.0 + smoothstep(0.7, 1.0, lum) * blur * 0.3
|
|
6691
|
-
|
|
6692
|
-
color += s
|
|
6693
|
-
total += 1.0
|
|
6694
|
-
}
|
|
6695
|
-
}
|
|
6696
|
-
|
|
6611
|
+
);
|
|
6612
|
+
}\`
|
|
6613
|
+
};
|
|
6614
|
+
|
|
6615
|
+
// FILM GRAIN SHADER
|
|
6616
|
+
const GrainShader = {
|
|
6617
|
+
uniforms: {
|
|
6618
|
+
tDiffuse: { value: null },
|
|
6619
|
+
uTime: { value: 0.0 },
|
|
6620
|
+
uIntensity: { value: 0.0 }
|
|
6621
|
+
},
|
|
6622
|
+
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
6623
|
+
fragmentShader: \`
|
|
6624
|
+
uniform sampler2D tDiffuse;
|
|
6625
|
+
uniform float uTime, uIntensity;
|
|
6626
|
+
varying vec2 vUv;
|
|
6627
|
+
float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898,78.233))) * 43758.5453); }
|
|
6628
|
+
void main() {
|
|
6629
|
+
vec4 color = texture2D(tDiffuse, vUv);
|
|
6630
|
+
float grain = (rand(vUv + fract(uTime)) * 2.0 - 1.0) * uIntensity;
|
|
6631
|
+
gl_FragColor = vec4(color.rgb + grain * (1.0 - dot(color.rgb, vec3(0.299, 0.587, 0.114)) * 0.5), color.a);
|
|
6632
|
+
}\`
|
|
6633
|
+
};
|
|
6634
|
+
|
|
6635
|
+
// CUSTOM DEPTH OF FIELD SHADER (Radial + Edge blur)
|
|
6636
|
+
const CustomDOFShader = {
|
|
6637
|
+
uniforms: {
|
|
6638
|
+
tDiffuse: { value: null },
|
|
6639
|
+
uFocus: { value: 0.05 },
|
|
6640
|
+
uFocalLength: { value: 0.25 },
|
|
6641
|
+
uBokehStrength: { value: 0.5 },
|
|
6642
|
+
uResolution: { value: new THREE.Vector2() },
|
|
6643
|
+
uBeatEnergy: { value: 0.0 }
|
|
6644
|
+
},
|
|
6645
|
+
vertexShader: \`
|
|
6646
|
+
varying vec2 vUv;
|
|
6647
|
+
void main() {
|
|
6648
|
+
vUv = uv;
|
|
6649
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
6650
|
+
}
|
|
6651
|
+
\`,
|
|
6652
|
+
fragmentShader: \`
|
|
6653
|
+
uniform sampler2D tDiffuse;
|
|
6654
|
+
uniform float uFocus;
|
|
6655
|
+
uniform float uFocalLength;
|
|
6656
|
+
uniform float uBokehStrength;
|
|
6657
|
+
uniform vec2 uResolution;
|
|
6658
|
+
uniform float uBeatEnergy;
|
|
6659
|
+
varying vec2 vUv;
|
|
6660
|
+
|
|
6661
|
+
void main() {
|
|
6662
|
+
vec2 center = vec2(0.5);
|
|
6663
|
+
float dist = length(vUv - center);
|
|
6664
|
+
|
|
6665
|
+
// Focus ring - sharp in a ring around center, blurry inside and outside
|
|
6666
|
+
float focusRing = abs(dist - uFocus);
|
|
6667
|
+
float blur = smoothstep(0.0, uFocalLength, focusRing) * uBokehStrength;
|
|
6668
|
+
|
|
6669
|
+
vec3 color = vec3(0.0);
|
|
6670
|
+
float total = 0.0;
|
|
6671
|
+
vec2 texel = 1.0 / uResolution;
|
|
6672
|
+
|
|
6673
|
+
if(blur < 0.01) {
|
|
6674
|
+
color = texture2D(tDiffuse, vUv).rgb;
|
|
6675
|
+
total = 1.0;
|
|
6676
|
+
} else {
|
|
6677
|
+
// Golden angle spiral sampling
|
|
6678
|
+
const float GOLDEN_ANGLE = 2.39996323;
|
|
6679
|
+
const int SAMPLES = 24;
|
|
6680
|
+
float radius = blur * 20.0 * (1.0 + uBeatEnergy * 0.1);
|
|
6681
|
+
|
|
6682
|
+
for(int i = 0; i < SAMPLES; i++) {
|
|
6683
|
+
float angle = float(i) * GOLDEN_ANGLE;
|
|
6684
|
+
float r = sqrt(float(i) / float(SAMPLES)) * radius;
|
|
6685
|
+
vec2 offset = vec2(cos(angle), sin(angle)) * r * texel;
|
|
6686
|
+
vec3 s = texture2D(tDiffuse, vUv + offset).rgb;
|
|
6687
|
+
|
|
6688
|
+
// Subtle highlight boost
|
|
6689
|
+
float lum = dot(s, vec3(0.299, 0.587, 0.114));
|
|
6690
|
+
s *= 1.0 + smoothstep(0.7, 1.0, lum) * blur * 0.3;
|
|
6691
|
+
|
|
6692
|
+
color += s;
|
|
6693
|
+
total += 1.0;
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
|
|
6697
6697
|
gl_FragColor = vec4(color / total, texture2D(tDiffuse, vUv).a);
|
|
6698
|
-
}
|
|
6699
|
-
|
|
6700
|
-
}
|
|
6701
|
-
|
|
6702
|
-
// VIGNETTE + GLOW SHADER
|
|
6703
|
-
const VignetteShader = {
|
|
6704
|
-
uniforms: {
|
|
6705
|
-
tDiffuse: { value: null }
|
|
6706
|
-
uBeatEnergy: { value: 0.0 }
|
|
6707
|
-
}
|
|
6708
|
-
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
|
|
6709
|
-
fragmentShader:
|
|
6710
|
-
uniform sampler2D tDiffuse
|
|
6711
|
-
uniform float uBeatEnergy
|
|
6712
|
-
varying vec2 vUv
|
|
6713
|
-
void main() {
|
|
6714
|
-
vec4 color = texture2D(tDiffuse, vUv)
|
|
6715
|
-
vec2 uv = vUv * (1.0 - vUv.yx)
|
|
6716
|
-
float vig = pow(uv.x * uv.y * 15.0, 0.38 * (1.0 - uBeatEnergy * 0.15))
|
|
6717
|
-
float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114))
|
|
6718
|
-
float glow = smoothstep(0.5, 0.9, lum) * 0.1 * (1.0 + uBeatEnergy * 0.5)
|
|
6719
|
-
gl_FragColor = vec4(color.rgb * vig * (1.0 + glow), color.a)
|
|
6720
|
-
}
|
|
6721
|
-
}
|
|
6722
|
-
|
|
6723
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 GEOMETRY \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
6724
|
-
const geometry = new THREE.BufferGeometry()
|
|
6698
|
+
}
|
|
6699
|
+
\`
|
|
6700
|
+
};
|
|
6701
|
+
|
|
6702
|
+
// VIGNETTE + GLOW SHADER
|
|
6703
|
+
const VignetteShader = {
|
|
6704
|
+
uniforms: {
|
|
6705
|
+
tDiffuse: { value: null },
|
|
6706
|
+
uBeatEnergy: { value: 0.0 }
|
|
6707
|
+
},
|
|
6708
|
+
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
6709
|
+
fragmentShader: \`
|
|
6710
|
+
uniform sampler2D tDiffuse;
|
|
6711
|
+
uniform float uBeatEnergy;
|
|
6712
|
+
varying vec2 vUv;
|
|
6713
|
+
void main() {
|
|
6714
|
+
vec4 color = texture2D(tDiffuse, vUv);
|
|
6715
|
+
vec2 uv = vUv * (1.0 - vUv.yx);
|
|
6716
|
+
float vig = pow(uv.x * uv.y * 15.0, 0.38 * (1.0 - uBeatEnergy * 0.15));
|
|
6717
|
+
float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
6718
|
+
float glow = smoothstep(0.5, 0.9, lum) * 0.1 * (1.0 + uBeatEnergy * 0.5);
|
|
6719
|
+
gl_FragColor = vec4(color.rgb * vig * (1.0 + glow), color.a);
|
|
6720
|
+
}\`
|
|
6721
|
+
};
|
|
6722
|
+
|
|
6723
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 GEOMETRY \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
6724
|
+
const geometry = new THREE.BufferGeometry();
|
|
6725
6725
|
const mainCapacity = Math.ceil(CONFIG.maxParticles * CONFIG.maxDensity);
|
|
6726
6726
|
const pos = new Float32Array(mainCapacity * 3);
|
|
6727
6727
|
const rnd = new Float32Array(mainCapacity * 3);
|
|
@@ -6730,11 +6730,11 @@ void main() {\r
|
|
|
6730
6730
|
const vec = new THREE.Vector3();
|
|
6731
6731
|
|
|
6732
6732
|
for (let i = 0; i < mainCapacity; i++) {
|
|
6733
|
-
vec.setFromSphericalCoords(
|
|
6734
|
-
CONFIG.fieldRadius * Math.pow(Math.random(), 0.33)
|
|
6735
|
-
Math.acos(2 * Math.random() - 1)
|
|
6736
|
-
Math.random() * Math.PI * 2
|
|
6737
|
-
)
|
|
6733
|
+
vec.setFromSphericalCoords(
|
|
6734
|
+
CONFIG.fieldRadius * Math.pow(Math.random(), 0.33),
|
|
6735
|
+
Math.acos(2 * Math.random() - 1),
|
|
6736
|
+
Math.random() * Math.PI * 2
|
|
6737
|
+
);
|
|
6738
6738
|
pos[i * 3] = vec.x;
|
|
6739
6739
|
pos[i * 3 + 1] = vec.y;
|
|
6740
6740
|
pos[i * 3 + 2] = vec.z;
|
|
@@ -6742,57 +6742,57 @@ void main() {\r
|
|
|
6742
6742
|
rnd[i * 3 + 1] = Math.random();
|
|
6743
6743
|
rnd[i * 3 + 2] = Math.random();
|
|
6744
6744
|
phase[i] = Math.random() * Math.PI * 2;
|
|
6745
|
-
}
|
|
6746
|
-
|
|
6747
|
-
// Secondary particles now use separate instanced system below
|
|
6748
|
-
|
|
6745
|
+
}
|
|
6746
|
+
|
|
6747
|
+
// Secondary particles now use separate instanced system below
|
|
6748
|
+
|
|
6749
6749
|
geometry.setAttribute('position', new THREE.BufferAttribute(pos, 3));
|
|
6750
6750
|
geometry.setAttribute('aRandom', new THREE.BufferAttribute(rnd, 3));
|
|
6751
6751
|
geometry.setAttribute('aPhase', new THREE.BufferAttribute(phase, 1));
|
|
6752
6752
|
geometry.setAttribute('aLayer', new THREE.BufferAttribute(layer, 1));
|
|
6753
|
-
geometry.setDrawRange(0, Math.floor(CONFIG.maxParticles * STATE.density))
|
|
6754
|
-
|
|
6755
|
-
const FIELD_TYPES = {
|
|
6753
|
+
geometry.setDrawRange(0, Math.floor(CONFIG.maxParticles * STATE.density));
|
|
6754
|
+
|
|
6755
|
+
const FIELD_TYPES = {
|
|
6756
6756
|
'Photon (EM)': { c1: '#00f2ff', c2: '#0051ff', c3: '#ffffff', s: 1.0, curl: 1.0, sz: 3.0, sc1: '#66d9ff', sc2: '#99eeff' },
|
|
6757
6757
|
'Gluon (Strong)': { c1: '#ff0055', c2: '#7000ff', c3: '#ff8800', s: 2.5, curl: 2.4, sz: 3.4, sc1: '#cc66ff', sc2: '#ff6699' },
|
|
6758
6758
|
'Higgs (Mass)': { c1: '#ffae00', c2: '#ff4800', c3: '#ffff00', s: 0.6, curl: 0.3, sz: 4.0, sc1: '#ffcc66', sc2: '#ffdd99' },
|
|
6759
6759
|
'Gravity': { c1: '#ffffff', c2: '#4466ff', c3: '#8844ff', s: 0.75, curl: 0.18, sz: 2.8, sc1: '#aabbff', sc2: '#ccaaff' },
|
|
6760
6760
|
'Dark Energy': { c1: '#1a0033', c2: '#6600ff', c3: '#ff00ff', s: 1.9, curl: 1.6, sz: 3.1, sc1: '#9933ff', sc2: '#cc66ff' },
|
|
6761
6761
|
'Neutrino': { c1: '#00ff88', c2: '#00ffcc', c3: '#88ffff', s: 3.0, curl: 3.0, sz: 2.6, sc1: '#66ffbb', sc2: '#99ffdd' },
|
|
6762
|
-
// === ENHANCEMENT: Waveform Terrain Field
|
|
6762
|
+
// === ENHANCEMENT: Waveform Terrain Field ===
|
|
6763
6763
|
'Waveform Terrain': { c1: '#ff3366', c2: '#33ff99', c3: '#6633ff', s: 0.4, curl: 0.15, sz: 2.4, sc1: '#ff6699', sc2: '#66ffcc', terrain: true }
|
|
6764
|
-
}
|
|
6765
|
-
|
|
6766
|
-
// === ENHANCEMENT: Adaptive Quality System
|
|
6767
|
-
const QUALITY_SYSTEM = {
|
|
6768
|
-
frameCount: 0
|
|
6769
|
-
frameTimes: []
|
|
6770
|
-
lastAdjustTime: 0
|
|
6771
|
-
currentFPS: 60
|
|
6772
|
-
|
|
6773
|
-
update(deltaTime) {
|
|
6774
|
-
this.frameCount
|
|
6775
|
-
this.frameTimes.push(deltaTime)
|
|
6776
|
-
if (this.frameTimes.length > 30) this.frameTimes.shift()
|
|
6777
|
-
|
|
6778
|
-
if (this.frameCount % 30 === 0) {
|
|
6779
|
-
const avgDelta = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length
|
|
6780
|
-
this.currentFPS = 1000 / avgDelta
|
|
6781
|
-
|
|
6764
|
+
};
|
|
6765
|
+
|
|
6766
|
+
// === ENHANCEMENT: Adaptive Quality System ===
|
|
6767
|
+
const QUALITY_SYSTEM = {
|
|
6768
|
+
frameCount: 0,
|
|
6769
|
+
frameTimes: [],
|
|
6770
|
+
lastAdjustTime: 0,
|
|
6771
|
+
currentFPS: 60,
|
|
6772
|
+
|
|
6773
|
+
update(deltaTime) {
|
|
6774
|
+
this.frameCount++;
|
|
6775
|
+
this.frameTimes.push(deltaTime);
|
|
6776
|
+
if (this.frameTimes.length > 30) this.frameTimes.shift();
|
|
6777
|
+
|
|
6778
|
+
if (this.frameCount % 30 === 0) {
|
|
6779
|
+
const avgDelta = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length;
|
|
6780
|
+
this.currentFPS = 1000 / avgDelta;
|
|
6781
|
+
|
|
6782
6782
|
if (STATE.adaptiveQualityEnabled && performance.now() - this.lastAdjustTime > 1000) {
|
|
6783
6783
|
if (this.currentFPS < STATE.targetFPS - 10 && STATE.qualityLevel > 0.3) {
|
|
6784
6784
|
STATE.qualityLevel = Math.max(0.3, STATE.qualityLevel - (this.currentFPS < STATE.targetFPS * 0.6 ? 0.2 : 0.1));
|
|
6785
|
-
this.applyQuality()
|
|
6786
|
-
this.lastAdjustTime = performance.now()
|
|
6787
|
-
} else if (this.currentFPS > STATE.targetFPS + 5 && STATE.qualityLevel < 1.0) {
|
|
6788
|
-
STATE.qualityLevel = Math.min(1.0, STATE.qualityLevel + 0.05)
|
|
6789
|
-
this.applyQuality()
|
|
6790
|
-
this.lastAdjustTime = performance.now()
|
|
6791
|
-
}
|
|
6792
|
-
}
|
|
6793
|
-
}
|
|
6794
|
-
}
|
|
6795
|
-
|
|
6785
|
+
this.applyQuality();
|
|
6786
|
+
this.lastAdjustTime = performance.now();
|
|
6787
|
+
} else if (this.currentFPS > STATE.targetFPS + 5 && STATE.qualityLevel < 1.0) {
|
|
6788
|
+
STATE.qualityLevel = Math.min(1.0, STATE.qualityLevel + 0.05);
|
|
6789
|
+
this.applyQuality();
|
|
6790
|
+
this.lastAdjustTime = performance.now();
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
},
|
|
6795
|
+
|
|
6796
6796
|
applyQuality() {
|
|
6797
6797
|
const q = STATE.qualityLevel;
|
|
6798
6798
|
const pixelRatio = Math.min(STATE.pixelRatio, Math.max(0.35, STATE.pixelRatio * q));
|
|
@@ -6809,301 +6809,301 @@ void main() {\r
|
|
|
6809
6809
|
material.uniforms.uPixelRatio.value = pixelRatio;
|
|
6810
6810
|
secondaryMaterial.uniforms.uPixelRatio.value = pixelRatio;
|
|
6811
6811
|
}
|
|
6812
|
-
}
|
|
6813
|
-
|
|
6814
|
-
// === ENHANCEMENT: Audio-Reactive Camera System
|
|
6815
|
-
const AUDIO_CAMERA = {
|
|
6816
|
-
targetOffset: new THREE.Vector3()
|
|
6817
|
-
currentOffset: new THREE.Vector3()
|
|
6818
|
-
targetFOV: 55
|
|
6819
|
-
baseFOV: 55
|
|
6820
|
-
breathPhase: 0
|
|
6821
|
-
|
|
6822
|
-
update(deltaTime) {
|
|
6823
|
-
if (!STATE.audioCameraEnabled || !AUDIO.active) return
|
|
6824
|
-
|
|
6825
|
-
const bass = AUDIO.gatedBands.bass
|
|
6826
|
-
const mid = AUDIO.gatedBands.mid
|
|
6827
|
-
const high = AUDIO.gatedBands.high
|
|
6828
|
-
const intensity = STATE.audioCameraIntensity
|
|
6829
|
-
const smooth = STATE.audioCameraSmoothing
|
|
6830
|
-
|
|
6831
|
-
// Bass-driven zoom pulse
|
|
6832
|
-
this.targetFOV = this.baseFOV - bass * 8 * intensity + AUDIO.beatEnergy * 5 * intensity
|
|
6833
|
-
camera.fov += (this.targetFOV - camera.fov) * smooth
|
|
6834
|
-
camera.updateProjectionMatrix()
|
|
6835
|
-
|
|
6836
|
-
// Subtle sway based on mid frequencies
|
|
6837
|
-
this.breathPhase += deltaTime * 0.001 * (1 + mid * 2)
|
|
6838
|
-
this.targetOffset.x = Math.sin(this.breathPhase * 0.7) * mid * 3 * intensity
|
|
6839
|
-
this.targetOffset.y = Math.cos(this.breathPhase * 0.5) * high * 2 * intensity
|
|
6840
|
-
this.targetOffset.z = Math.sin(this.breathPhase * 0.3) * bass * 4 * intensity
|
|
6841
|
-
|
|
6842
|
-
// Smooth interpolation
|
|
6843
|
-
this.currentOffset.lerp(this.targetOffset, smooth)
|
|
6844
|
-
|
|
6845
|
-
// Apply onset punch
|
|
6846
|
-
if (AUDIO.onsetDetected) {
|
|
6847
|
-
this.currentOffset.z -= AUDIO.onsetEnergy * 2 * intensity
|
|
6848
|
-
}
|
|
6849
|
-
}
|
|
6850
|
-
}
|
|
6851
|
-
|
|
6852
|
-
const def = FIELD_TYPES[STATE.field]
|
|
6853
|
-
const material = new THREE.ShaderMaterial({
|
|
6854
|
-
vertexShader, fragmentShader
|
|
6855
|
-
uniforms: {
|
|
6856
|
-
uTime: { value: 0 }
|
|
6857
|
-
uPixelRatio: { value: STATE.pixelRatio }
|
|
6858
|
-
uSizeBase: { value: def.sz }
|
|
6859
|
-
uNoiseScale: { value: def.s }
|
|
6860
|
-
uCurlStrength: { value: def.curl }
|
|
6812
|
+
};
|
|
6813
|
+
|
|
6814
|
+
// === ENHANCEMENT: Audio-Reactive Camera System ===
|
|
6815
|
+
const AUDIO_CAMERA = {
|
|
6816
|
+
targetOffset: new THREE.Vector3(),
|
|
6817
|
+
currentOffset: new THREE.Vector3(),
|
|
6818
|
+
targetFOV: 55,
|
|
6819
|
+
baseFOV: 55,
|
|
6820
|
+
breathPhase: 0,
|
|
6821
|
+
|
|
6822
|
+
update(deltaTime) {
|
|
6823
|
+
if (!STATE.audioCameraEnabled || !AUDIO.active) return;
|
|
6824
|
+
|
|
6825
|
+
const bass = AUDIO.gatedBands.bass;
|
|
6826
|
+
const mid = AUDIO.gatedBands.mid;
|
|
6827
|
+
const high = AUDIO.gatedBands.high;
|
|
6828
|
+
const intensity = STATE.audioCameraIntensity;
|
|
6829
|
+
const smooth = STATE.audioCameraSmoothing;
|
|
6830
|
+
|
|
6831
|
+
// Bass-driven zoom pulse
|
|
6832
|
+
this.targetFOV = this.baseFOV - bass * 8 * intensity + AUDIO.beatEnergy * 5 * intensity;
|
|
6833
|
+
camera.fov += (this.targetFOV - camera.fov) * smooth;
|
|
6834
|
+
camera.updateProjectionMatrix();
|
|
6835
|
+
|
|
6836
|
+
// Subtle sway based on mid frequencies
|
|
6837
|
+
this.breathPhase += deltaTime * 0.001 * (1 + mid * 2);
|
|
6838
|
+
this.targetOffset.x = Math.sin(this.breathPhase * 0.7) * mid * 3 * intensity;
|
|
6839
|
+
this.targetOffset.y = Math.cos(this.breathPhase * 0.5) * high * 2 * intensity;
|
|
6840
|
+
this.targetOffset.z = Math.sin(this.breathPhase * 0.3) * bass * 4 * intensity;
|
|
6841
|
+
|
|
6842
|
+
// Smooth interpolation
|
|
6843
|
+
this.currentOffset.lerp(this.targetOffset, smooth);
|
|
6844
|
+
|
|
6845
|
+
// Apply onset punch
|
|
6846
|
+
if (AUDIO.onsetDetected) {
|
|
6847
|
+
this.currentOffset.z -= AUDIO.onsetEnergy * 2 * intensity;
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
};
|
|
6851
|
+
|
|
6852
|
+
const def = FIELD_TYPES[STATE.field];
|
|
6853
|
+
const material = new THREE.ShaderMaterial({
|
|
6854
|
+
vertexShader, fragmentShader,
|
|
6855
|
+
uniforms: {
|
|
6856
|
+
uTime: { value: 0 },
|
|
6857
|
+
uPixelRatio: { value: STATE.pixelRatio },
|
|
6858
|
+
uSizeBase: { value: def.sz },
|
|
6859
|
+
uNoiseScale: { value: def.s },
|
|
6860
|
+
uCurlStrength: { value: def.curl },
|
|
6861
6861
|
uViewportAspect: { value: window.innerWidth / window.innerHeight },
|
|
6862
6862
|
uViewportHalfHeight: { value: Math.tan(camera.fov * Math.PI / 360) * CONFIG.defaultZoom },
|
|
6863
|
-
uColor1: { value: new THREE.Color(def.c1) }
|
|
6864
|
-
uColor2: { value: new THREE.Color(def.c2) }
|
|
6865
|
-
uColor3: { value: new THREE.Color(def.c3) }
|
|
6866
|
-
uSubBass: { value: 0 }, uBass: { value: 0 }, uLowMid: { value: 0 }
|
|
6867
|
-
uMid: { value: 0 }, uHighMid: { value: 0 }, uHigh: { value: 0 }, uUltraHigh: { value: 0 }
|
|
6868
|
-
uBeatEnergy: { value: 0 }, uSpectralCentroid: { value: 0 }, uSpectralFlux: { value: 0 }
|
|
6869
|
-
uOnsetEnergy: { value: 0 }, uTerrainMode: { value: 0 }, uTerrainHeight: { value: 15.0 }
|
|
6870
|
-
uVortexStrength: { value: STATE.vortexStrength }
|
|
6871
|
-
uPulseIntensity: { value: STATE.pulseIntensity }
|
|
6872
|
-
uZoomFactor: { value: 1.0 }
|
|
6873
|
-
uMousePos: { value: new THREE.Vector3(999, 999, 999) }
|
|
6874
|
-
uMouseVelocity: { value: new THREE.Vector2(0, 0) }
|
|
6875
|
-
}
|
|
6876
|
-
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending
|
|
6877
|
-
})
|
|
6878
|
-
|
|
6879
|
-
const points = new THREE.Points(geometry, material)
|
|
6880
|
-
scene.add(points)
|
|
6881
|
-
|
|
6882
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 INSTANCED SECONDARY PARTICLES \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
6863
|
+
uColor1: { value: new THREE.Color(def.c1) },
|
|
6864
|
+
uColor2: { value: new THREE.Color(def.c2) },
|
|
6865
|
+
uColor3: { value: new THREE.Color(def.c3) },
|
|
6866
|
+
uSubBass: { value: 0 }, uBass: { value: 0 }, uLowMid: { value: 0 },
|
|
6867
|
+
uMid: { value: 0 }, uHighMid: { value: 0 }, uHigh: { value: 0 }, uUltraHigh: { value: 0 },
|
|
6868
|
+
uBeatEnergy: { value: 0 }, uSpectralCentroid: { value: 0 }, uSpectralFlux: { value: 0 },
|
|
6869
|
+
uOnsetEnergy: { value: 0 }, uTerrainMode: { value: 0 }, uTerrainHeight: { value: 15.0 },
|
|
6870
|
+
uVortexStrength: { value: STATE.vortexStrength },
|
|
6871
|
+
uPulseIntensity: { value: STATE.pulseIntensity },
|
|
6872
|
+
uZoomFactor: { value: 1.0 },
|
|
6873
|
+
uMousePos: { value: new THREE.Vector3(999, 999, 999) },
|
|
6874
|
+
uMouseVelocity: { value: new THREE.Vector2(0, 0) }
|
|
6875
|
+
},
|
|
6876
|
+
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending
|
|
6877
|
+
});
|
|
6878
|
+
|
|
6879
|
+
const points = new THREE.Points(geometry, material);
|
|
6880
|
+
scene.add(points);
|
|
6881
|
+
|
|
6882
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 INSTANCED SECONDARY PARTICLES \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
6883
6883
|
const secondaryVertexShader = \`
|
|
6884
6884
|
uniform float uTime, uPixelRatio;
|
|
6885
6885
|
uniform float uViewportAspect, uViewportHalfHeight;
|
|
6886
|
-
uniform float uSubBass, uBass, uLowMid, uMid, uHighMid, uHigh, uUltraHigh
|
|
6887
|
-
uniform float uBeatEnergy, uSpectralCentroid, uSpectralFlux, uOnsetEnergy
|
|
6888
|
-
uniform vec3 uColor1, uColor2
|
|
6886
|
+
uniform float uSubBass, uBass, uLowMid, uMid, uHighMid, uHigh, uUltraHigh;
|
|
6887
|
+
uniform float uBeatEnergy, uSpectralCentroid, uSpectralFlux, uOnsetEnergy;
|
|
6888
|
+
uniform vec3 uColor1, uColor2;
|
|
6889
6889
|
uniform float uVortexStrength;
|
|
6890
|
-
|
|
6891
|
-
attribute vec3 instancePosition
|
|
6892
|
-
attribute vec3 instanceRandom
|
|
6893
|
-
attribute float instancePhase
|
|
6894
|
-
|
|
6895
|
-
varying vec3 vColor
|
|
6896
|
-
varying float vAlpha
|
|
6897
|
-
varying float vEnergy
|
|
6898
|
-
varying float vDepth
|
|
6899
|
-
|
|
6900
|
-
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6901
|
-
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6902
|
-
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
|
|
6903
|
-
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
6904
|
-
|
|
6905
|
-
float snoise(vec3 v) {
|
|
6906
|
-
const vec2 C = vec2(1.0/6.0, 1.0/3.0)
|
|
6907
|
-
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0)
|
|
6908
|
-
vec3 i = floor(v + dot(v, C.yyy))
|
|
6909
|
-
vec3 x0 = v - i + dot(i, C.xxx)
|
|
6910
|
-
vec3 g = step(x0.yzx, x0.xyz)
|
|
6911
|
-
vec3 l = 1.0 - g
|
|
6912
|
-
vec3 i1 = min(g.xyz, l.zxy)
|
|
6913
|
-
vec3 i2 = max(g.xyz, l.zxy)
|
|
6914
|
-
vec3 x1 = x0 - i1 + C.xxx
|
|
6915
|
-
vec3 x2 = x0 - i2 + C.yyy
|
|
6916
|
-
vec3 x3 = x0 - D.yyy
|
|
6917
|
-
i = mod289(i)
|
|
6918
|
-
vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0))
|
|
6919
|
-
float n_ = 0.142857142857
|
|
6920
|
-
vec3 ns = n_ * D.wyz - D.xzx
|
|
6921
|
-
vec4 j = p - 49.0 * floor(p * ns.z * ns.z)
|
|
6922
|
-
vec4 x_ = floor(j * ns.z)
|
|
6923
|
-
vec4 y_ = floor(j - 7.0 * x_)
|
|
6924
|
-
vec4 x = x_ * ns.x + ns.yyyy
|
|
6925
|
-
vec4 y = y_ * ns.x + ns.yyyy
|
|
6926
|
-
vec4 h = 1.0 - abs(x) - abs(y)
|
|
6927
|
-
vec4 b0 = vec4(x.xy, y.xy)
|
|
6928
|
-
vec4 b1 = vec4(x.zw, y.zw)
|
|
6929
|
-
vec4 s0 = floor(b0) * 2.0 + 1.0
|
|
6930
|
-
vec4 s1 = floor(b1) * 2.0 + 1.0
|
|
6931
|
-
vec4 sh = -step(h, vec4(0.0))
|
|
6932
|
-
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy
|
|
6933
|
-
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww
|
|
6934
|
-
vec3 p0 = vec3(a0.xy, h.x)
|
|
6935
|
-
vec3 p1 = vec3(a0.zw, h.y)
|
|
6936
|
-
vec3 p2 = vec3(a1.xy, h.z)
|
|
6937
|
-
vec3 p3 = vec3(a1.zw, h.w)
|
|
6938
|
-
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)))
|
|
6939
|
-
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w
|
|
6940
|
-
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0)
|
|
6941
|
-
m = m * m
|
|
6942
|
-
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)))
|
|
6943
|
-
}
|
|
6944
|
-
|
|
6890
|
+
|
|
6891
|
+
attribute vec3 instancePosition;
|
|
6892
|
+
attribute vec3 instanceRandom;
|
|
6893
|
+
attribute float instancePhase;
|
|
6894
|
+
|
|
6895
|
+
varying vec3 vColor;
|
|
6896
|
+
varying float vAlpha;
|
|
6897
|
+
varying float vEnergy;
|
|
6898
|
+
varying float vDepth;
|
|
6899
|
+
|
|
6900
|
+
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6901
|
+
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
6902
|
+
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
|
|
6903
|
+
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
6904
|
+
|
|
6905
|
+
float snoise(vec3 v) {
|
|
6906
|
+
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
|
|
6907
|
+
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
|
6908
|
+
vec3 i = floor(v + dot(v, C.yyy));
|
|
6909
|
+
vec3 x0 = v - i + dot(i, C.xxx);
|
|
6910
|
+
vec3 g = step(x0.yzx, x0.xyz);
|
|
6911
|
+
vec3 l = 1.0 - g;
|
|
6912
|
+
vec3 i1 = min(g.xyz, l.zxy);
|
|
6913
|
+
vec3 i2 = max(g.xyz, l.zxy);
|
|
6914
|
+
vec3 x1 = x0 - i1 + C.xxx;
|
|
6915
|
+
vec3 x2 = x0 - i2 + C.yyy;
|
|
6916
|
+
vec3 x3 = x0 - D.yyy;
|
|
6917
|
+
i = mod289(i);
|
|
6918
|
+
vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));
|
|
6919
|
+
float n_ = 0.142857142857;
|
|
6920
|
+
vec3 ns = n_ * D.wyz - D.xzx;
|
|
6921
|
+
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
6922
|
+
vec4 x_ = floor(j * ns.z);
|
|
6923
|
+
vec4 y_ = floor(j - 7.0 * x_);
|
|
6924
|
+
vec4 x = x_ * ns.x + ns.yyyy;
|
|
6925
|
+
vec4 y = y_ * ns.x + ns.yyyy;
|
|
6926
|
+
vec4 h = 1.0 - abs(x) - abs(y);
|
|
6927
|
+
vec4 b0 = vec4(x.xy, y.xy);
|
|
6928
|
+
vec4 b1 = vec4(x.zw, y.zw);
|
|
6929
|
+
vec4 s0 = floor(b0) * 2.0 + 1.0;
|
|
6930
|
+
vec4 s1 = floor(b1) * 2.0 + 1.0;
|
|
6931
|
+
vec4 sh = -step(h, vec4(0.0));
|
|
6932
|
+
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
|
6933
|
+
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
|
6934
|
+
vec3 p0 = vec3(a0.xy, h.x);
|
|
6935
|
+
vec3 p1 = vec3(a0.zw, h.y);
|
|
6936
|
+
vec3 p2 = vec3(a1.xy, h.z);
|
|
6937
|
+
vec3 p3 = vec3(a1.zw, h.w);
|
|
6938
|
+
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
|
|
6939
|
+
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
|
|
6940
|
+
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
|
6941
|
+
m = m * m;
|
|
6942
|
+
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
|
|
6943
|
+
}
|
|
6944
|
+
|
|
6945
6945
|
\${shapeFunction}
|
|
6946
6946
|
|
|
6947
6947
|
void main() {
|
|
6948
6948
|
// Secondary particles run at a slower timescale \u2014 they are the atmosphere, not the core
|
|
6949
|
-
float t = uTime * 0.45
|
|
6950
|
-
|
|
6951
|
-
// Multi-scale noise flow \u2014 low freqs drive large drift, high freqs add fine turbulence
|
|
6952
|
-
vec3 noisePos = instancePosition * 0.035 + instanceRandom
|
|
6953
|
-
vec3 drift = vec3(
|
|
6954
|
-
snoise(noisePos + t * 0.06)
|
|
6955
|
-
snoise(noisePos * 0.9 - t * 0.05)
|
|
6956
|
-
snoise(noisePos * 1.1 + t * 0.04)
|
|
6957
|
-
) * (5.0 + uSubBass * 10.0 + uLowMid * 6.0)
|
|
6958
|
-
|
|
6959
|
-
vec3 turbulence = vec3(
|
|
6960
|
-
snoise(noisePos * 2.5 + t * 0.18)
|
|
6961
|
-
snoise(noisePos * 2.5 - t * 0.14)
|
|
6962
|
-
snoise(noisePos * 2.5 + t * 0.12)
|
|
6963
|
-
) * (uHigh * 5.0 + uSpectralFlux * 8.0)
|
|
6964
|
-
|
|
6965
|
-
vec3 offset = drift + turbulence
|
|
6966
|
-
|
|
6967
|
-
// Orbital drift \u2014 vortex-aware, modulated by bass
|
|
6968
|
-
float orbitSpeed = 0.12 + instanceRandom.x * 0.15 + uBass * 0.08
|
|
6969
|
-
float angle = uTime * orbitSpeed + instancePhase
|
|
6970
|
-
float orbitRadius = length(instancePosition.xz) * (1.0 + uSubBass * 0.25)
|
|
6971
|
-
// Vortex coupling \u2014 secondary shell rotates with the main field vortex
|
|
6972
|
-
float vortexInfluence = uVortexStrength * uBass * 0.4
|
|
6973
|
-
float vAngle = angle + vortexInfluence
|
|
6974
|
-
vec3 orbit = vec3(
|
|
6975
|
-
cos(vAngle) * orbitRadius - instancePosition.x
|
|
6976
|
-
sin(uTime * 0.5 + instancePhase) * (uMid * 4.0 + uLowMid * 2.0)
|
|
6977
|
-
sin(vAngle) * orbitRadius - instancePosition.z
|
|
6978
|
-
) * 0.07
|
|
6979
|
-
|
|
6949
|
+
float t = uTime * 0.45;
|
|
6950
|
+
|
|
6951
|
+
// Multi-scale noise flow \u2014 low freqs drive large drift, high freqs add fine turbulence
|
|
6952
|
+
vec3 noisePos = instancePosition * 0.035 + instanceRandom;
|
|
6953
|
+
vec3 drift = vec3(
|
|
6954
|
+
snoise(noisePos + t * 0.06),
|
|
6955
|
+
snoise(noisePos * 0.9 - t * 0.05),
|
|
6956
|
+
snoise(noisePos * 1.1 + t * 0.04)
|
|
6957
|
+
) * (5.0 + uSubBass * 10.0 + uLowMid * 6.0);
|
|
6958
|
+
|
|
6959
|
+
vec3 turbulence = vec3(
|
|
6960
|
+
snoise(noisePos * 2.5 + t * 0.18),
|
|
6961
|
+
snoise(noisePos * 2.5 - t * 0.14),
|
|
6962
|
+
snoise(noisePos * 2.5 + t * 0.12)
|
|
6963
|
+
) * (uHigh * 5.0 + uSpectralFlux * 8.0);
|
|
6964
|
+
|
|
6965
|
+
vec3 offset = drift + turbulence;
|
|
6966
|
+
|
|
6967
|
+
// Orbital drift \u2014 vortex-aware, modulated by bass
|
|
6968
|
+
float orbitSpeed = 0.12 + instanceRandom.x * 0.15 + uBass * 0.08;
|
|
6969
|
+
float angle = uTime * orbitSpeed + instancePhase;
|
|
6970
|
+
float orbitRadius = length(instancePosition.xz) * (1.0 + uSubBass * 0.25);
|
|
6971
|
+
// Vortex coupling \u2014 secondary shell rotates with the main field vortex
|
|
6972
|
+
float vortexInfluence = uVortexStrength * uBass * 0.4;
|
|
6973
|
+
float vAngle = angle + vortexInfluence;
|
|
6974
|
+
vec3 orbit = vec3(
|
|
6975
|
+
cos(vAngle) * orbitRadius - instancePosition.x,
|
|
6976
|
+
sin(uTime * 0.5 + instancePhase) * (uMid * 4.0 + uLowMid * 2.0),
|
|
6977
|
+
sin(vAngle) * orbitRadius - instancePosition.z
|
|
6978
|
+
) * 0.07;
|
|
6979
|
+
|
|
6980
6980
|
vec3 newPos = shapedPosition(instanceRandom) + offset + orbit;
|
|
6981
|
-
|
|
6981
|
+
|
|
6982
6982
|
// Transients and sub-bass move the atmosphere in depth, not away from the centre.
|
|
6983
6983
|
newPos.z += uOnsetEnergy * 14.0 * (0.6 + instanceRandom.z * 0.4);
|
|
6984
6984
|
newPos.z += sin(uTime * 1.2 + instancePhase) * uSubBass * 5.0;
|
|
6985
|
-
|
|
6986
|
-
vec4 mvPosition = modelViewMatrix * vec4(newPos, 1.0)
|
|
6987
|
-
gl_Position = projectionMatrix * mvPosition
|
|
6988
|
-
|
|
6989
|
-
// Size: driven by different bands than the main system
|
|
6990
|
-
// Sparkle on ultraHigh, swell on subBass, punch on beatEnergy
|
|
6991
|
-
float sparkle = 1.0 + (instanceRandom.x > 0.85 ? sin(uTime * 15.0 + instanceRandom.y * 200.0) * uUltraHigh * 3.0 : 0.0)
|
|
6992
|
-
float swell = 1.0 + uSubBass * 0.6 + uLowMid * 0.3
|
|
6993
|
-
float size = (1.1 + uBeatEnergy * 0.7) * swell * sparkle
|
|
6994
|
-
gl_PointSize = clamp(size * uPixelRatio * (55.0 / -mvPosition.z), 0.5, 55.0)
|
|
6995
|
-
|
|
6996
|
-
// Colour: spectral centroid shifts hue, highMid brightens toward white
|
|
6997
|
-
float centroidMix = clamp(uSpectralCentroid * 2.2 + uHighMid * 0.4, 0.0, 1.0)
|
|
6998
|
-
vColor = mix(uColor1, uColor2, centroidMix)
|
|
6999
|
-
// SubBass warmth \u2014 hint of deep colour saturation on heavy lows
|
|
7000
|
-
vColor = mix(vColor, vColor * 1.3, uSubBass * 0.4)
|
|
7001
|
-
// Beat flash \u2014 brief white-hot core
|
|
7002
|
-
vColor = mix(vColor, vec3(1.0), uBeatEnergy * 0.25)
|
|
7003
|
-
|
|
7004
|
-
// Alpha: outer shell fades at edge, mid-frequency presence sustains visibility
|
|
6985
|
+
|
|
6986
|
+
vec4 mvPosition = modelViewMatrix * vec4(newPos, 1.0);
|
|
6987
|
+
gl_Position = projectionMatrix * mvPosition;
|
|
6988
|
+
|
|
6989
|
+
// Size: driven by different bands than the main system
|
|
6990
|
+
// Sparkle on ultraHigh, swell on subBass, punch on beatEnergy
|
|
6991
|
+
float sparkle = 1.0 + (instanceRandom.x > 0.85 ? sin(uTime * 15.0 + instanceRandom.y * 200.0) * uUltraHigh * 3.0 : 0.0);
|
|
6992
|
+
float swell = 1.0 + uSubBass * 0.6 + uLowMid * 0.3;
|
|
6993
|
+
float size = (1.1 + uBeatEnergy * 0.7) * swell * sparkle;
|
|
6994
|
+
gl_PointSize = clamp(size * uPixelRatio * (55.0 / -mvPosition.z), 0.5, 55.0);
|
|
6995
|
+
|
|
6996
|
+
// Colour: spectral centroid shifts hue, highMid brightens toward white
|
|
6997
|
+
float centroidMix = clamp(uSpectralCentroid * 2.2 + uHighMid * 0.4, 0.0, 1.0);
|
|
6998
|
+
vColor = mix(uColor1, uColor2, centroidMix);
|
|
6999
|
+
// SubBass warmth \u2014 hint of deep colour saturation on heavy lows
|
|
7000
|
+
vColor = mix(vColor, vColor * 1.3, uSubBass * 0.4);
|
|
7001
|
+
// Beat flash \u2014 brief white-hot core
|
|
7002
|
+
vColor = mix(vColor, vec3(1.0), uBeatEnergy * 0.25);
|
|
7003
|
+
|
|
7004
|
+
// Alpha: outer shell fades at edge, mid-frequency presence sustains visibility
|
|
7005
7005
|
float edgeFade = 1.0 - smoothstep(0.92, 1.05, max(abs(newPos.x) / (uViewportAspect * uViewportHalfHeight * 0.82), abs(newPos.y) / (uViewportHalfHeight * 0.82)));
|
|
7006
|
-
vAlpha = edgeFade * (0.45 + uMid * 0.35 + uLowMid * 0.2) * sparkle
|
|
7007
|
-
|
|
7008
|
-
vEnergy = uBeatEnergy * 0.4 + uHighMid * 0.3 + uOnsetEnergy * 0.3
|
|
7009
|
-
vDepth = clamp(-mvPosition.z / 160.0, 0.0, 1.0)
|
|
7010
|
-
}
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
const secondaryFragmentShader =
|
|
7014
|
-
varying vec3 vColor
|
|
7015
|
-
varying float vAlpha
|
|
7016
|
-
varying float vEnergy
|
|
7017
|
-
varying float vDepth
|
|
7018
|
-
|
|
7019
|
-
void main() {
|
|
7020
|
-
vec2 xy = gl_PointCoord.xy - vec2(0.5)
|
|
7021
|
-
float r = length(xy)
|
|
7022
|
-
if (r > 0.5) discard
|
|
7023
|
-
|
|
7024
|
-
// Wider, softer glow than main particles \u2014 atmospheric haze
|
|
7025
|
-
float glow = exp(-r * r * 8.0)
|
|
7026
|
-
float softGlow = exp(-r * r * 3.0) * 0.4
|
|
7027
|
-
float core = smoothstep(0.18 - vEnergy * 0.06, 0.10, r)
|
|
7028
|
-
|
|
7029
|
-
vec3 finalColor = mix(vColor, vec3(1.0, 0.97, 0.92), core * 0.55)
|
|
7030
|
-
// Depth haze \u2014 far particles shift slightly cooler/dimmer
|
|
7031
|
-
finalColor = mix(finalColor, vec3(0.02, 0.03, 0.06), vDepth * 0.35)
|
|
7032
|
-
|
|
7033
|
-
float finalAlpha = clamp(vAlpha * (glow * 0.65 + softGlow + core * 0.35), 0.0, 1.0)
|
|
7034
|
-
gl_FragColor = vec4(finalColor, finalAlpha)
|
|
7035
|
-
}
|
|
7036
|
-
|
|
7037
|
-
|
|
7038
|
-
// Create instanced geometry for secondary particles
|
|
7039
|
-
const secondaryGeometry = new THREE.InstancedBufferGeometry()
|
|
7040
|
-
|
|
7041
|
-
// Base geometry (single point)
|
|
7042
|
-
const basePositions = new Float32Array([0, 0, 0])
|
|
7043
|
-
secondaryGeometry.setAttribute('position', new THREE.BufferAttribute(basePositions, 3))
|
|
7044
|
-
|
|
7045
|
-
// Instance attributes
|
|
7046
|
-
const instancePositions = new Float32Array(CONFIG.secondaryParticles * 3)
|
|
7047
|
-
const instanceRandoms = new Float32Array(CONFIG.secondaryParticles * 3)
|
|
7048
|
-
const instancePhases = new Float32Array(CONFIG.secondaryParticles)
|
|
7049
|
-
|
|
7050
|
-
for (let i = 0; i < CONFIG.secondaryParticles; i++) {
|
|
7051
|
-
// Distribute in outer shell
|
|
7052
|
-
const r = CONFIG.fieldRadius * (0.65 + Math.random() * 0.45)
|
|
7053
|
-
const theta = Math.acos(2 * Math.random() - 1)
|
|
7054
|
-
const phi = Math.random() * Math.PI * 2
|
|
7055
|
-
|
|
7056
|
-
instancePositions[i * 3] = r * Math.sin(theta) * Math.cos(phi)
|
|
7057
|
-
instancePositions[i * 3 + 1] = r * Math.sin(theta) * Math.sin(phi)
|
|
7058
|
-
instancePositions[i * 3 + 2] = r * Math.cos(theta)
|
|
7059
|
-
|
|
7060
|
-
instanceRandoms[i * 3] = Math.random()
|
|
7061
|
-
instanceRandoms[i * 3 + 1] = Math.random()
|
|
7062
|
-
instanceRandoms[i * 3 + 2] = Math.random()
|
|
7063
|
-
|
|
7064
|
-
instancePhases[i] = Math.random() * Math.PI * 2
|
|
7065
|
-
}
|
|
7066
|
-
|
|
7067
|
-
secondaryGeometry.setAttribute('instancePosition', new THREE.InstancedBufferAttribute(instancePositions, 3))
|
|
7068
|
-
secondaryGeometry.setAttribute('instanceRandom', new THREE.InstancedBufferAttribute(instanceRandoms, 3))
|
|
7069
|
-
secondaryGeometry.setAttribute('instancePhase', new THREE.InstancedBufferAttribute(instancePhases, 1))
|
|
7070
|
-
|
|
7071
|
-
const secondaryMaterial = new THREE.ShaderMaterial({
|
|
7072
|
-
vertexShader: secondaryVertexShader
|
|
7073
|
-
fragmentShader: secondaryFragmentShader
|
|
7074
|
-
uniforms: {
|
|
7075
|
-
uTime: { value: 0 }
|
|
7076
|
-
uPixelRatio: { value: STATE.pixelRatio }
|
|
7077
|
-
uSubBass: { value: 0 }
|
|
7078
|
-
uBass: { value: 0 }
|
|
7079
|
-
uLowMid: { value: 0 }
|
|
7080
|
-
uMid: { value: 0 }
|
|
7081
|
-
uHighMid: { value: 0 }
|
|
7082
|
-
uHigh: { value: 0 }
|
|
7083
|
-
uUltraHigh: { value: 0 }
|
|
7084
|
-
uBeatEnergy: { value: 0 }
|
|
7085
|
-
uSpectralCentroid: { value: 0 }
|
|
7086
|
-
uSpectralFlux: { value: 0 }
|
|
7087
|
-
uOnsetEnergy: { value: 0 }
|
|
7088
|
-
uVortexStrength: { value: STATE.vortexStrength }
|
|
7089
|
-
uColor1: { value: new THREE.Color(def.sc1) }
|
|
7090
|
-
uColor2: { value: new THREE.Color(def.sc2) }
|
|
7006
|
+
vAlpha = edgeFade * (0.45 + uMid * 0.35 + uLowMid * 0.2) * sparkle;
|
|
7007
|
+
|
|
7008
|
+
vEnergy = uBeatEnergy * 0.4 + uHighMid * 0.3 + uOnsetEnergy * 0.3;
|
|
7009
|
+
vDepth = clamp(-mvPosition.z / 160.0, 0.0, 1.0);
|
|
7010
|
+
}
|
|
7011
|
+
\`;
|
|
7012
|
+
|
|
7013
|
+
const secondaryFragmentShader = \`
|
|
7014
|
+
varying vec3 vColor;
|
|
7015
|
+
varying float vAlpha;
|
|
7016
|
+
varying float vEnergy;
|
|
7017
|
+
varying float vDepth;
|
|
7018
|
+
|
|
7019
|
+
void main() {
|
|
7020
|
+
vec2 xy = gl_PointCoord.xy - vec2(0.5);
|
|
7021
|
+
float r = length(xy);
|
|
7022
|
+
if (r > 0.5) discard;
|
|
7023
|
+
|
|
7024
|
+
// Wider, softer glow than main particles \u2014 atmospheric haze
|
|
7025
|
+
float glow = exp(-r * r * 8.0);
|
|
7026
|
+
float softGlow = exp(-r * r * 3.0) * 0.4;
|
|
7027
|
+
float core = smoothstep(0.18 - vEnergy * 0.06, 0.10, r);
|
|
7028
|
+
|
|
7029
|
+
vec3 finalColor = mix(vColor, vec3(1.0, 0.97, 0.92), core * 0.55);
|
|
7030
|
+
// Depth haze \u2014 far particles shift slightly cooler/dimmer
|
|
7031
|
+
finalColor = mix(finalColor, vec3(0.02, 0.03, 0.06), vDepth * 0.35);
|
|
7032
|
+
|
|
7033
|
+
float finalAlpha = clamp(vAlpha * (glow * 0.65 + softGlow + core * 0.35), 0.0, 1.0);
|
|
7034
|
+
gl_FragColor = vec4(finalColor, finalAlpha);
|
|
7035
|
+
}
|
|
7036
|
+
\`;
|
|
7037
|
+
|
|
7038
|
+
// Create instanced geometry for secondary particles
|
|
7039
|
+
const secondaryGeometry = new THREE.InstancedBufferGeometry();
|
|
7040
|
+
|
|
7041
|
+
// Base geometry (single point)
|
|
7042
|
+
const basePositions = new Float32Array([0, 0, 0]);
|
|
7043
|
+
secondaryGeometry.setAttribute('position', new THREE.BufferAttribute(basePositions, 3));
|
|
7044
|
+
|
|
7045
|
+
// Instance attributes
|
|
7046
|
+
const instancePositions = new Float32Array(CONFIG.secondaryParticles * 3);
|
|
7047
|
+
const instanceRandoms = new Float32Array(CONFIG.secondaryParticles * 3);
|
|
7048
|
+
const instancePhases = new Float32Array(CONFIG.secondaryParticles);
|
|
7049
|
+
|
|
7050
|
+
for (let i = 0; i < CONFIG.secondaryParticles; i++) {
|
|
7051
|
+
// Distribute in outer shell
|
|
7052
|
+
const r = CONFIG.fieldRadius * (0.65 + Math.random() * 0.45);
|
|
7053
|
+
const theta = Math.acos(2 * Math.random() - 1);
|
|
7054
|
+
const phi = Math.random() * Math.PI * 2;
|
|
7055
|
+
|
|
7056
|
+
instancePositions[i * 3] = r * Math.sin(theta) * Math.cos(phi);
|
|
7057
|
+
instancePositions[i * 3 + 1] = r * Math.sin(theta) * Math.sin(phi);
|
|
7058
|
+
instancePositions[i * 3 + 2] = r * Math.cos(theta);
|
|
7059
|
+
|
|
7060
|
+
instanceRandoms[i * 3] = Math.random();
|
|
7061
|
+
instanceRandoms[i * 3 + 1] = Math.random();
|
|
7062
|
+
instanceRandoms[i * 3 + 2] = Math.random();
|
|
7063
|
+
|
|
7064
|
+
instancePhases[i] = Math.random() * Math.PI * 2;
|
|
7065
|
+
}
|
|
7066
|
+
|
|
7067
|
+
secondaryGeometry.setAttribute('instancePosition', new THREE.InstancedBufferAttribute(instancePositions, 3));
|
|
7068
|
+
secondaryGeometry.setAttribute('instanceRandom', new THREE.InstancedBufferAttribute(instanceRandoms, 3));
|
|
7069
|
+
secondaryGeometry.setAttribute('instancePhase', new THREE.InstancedBufferAttribute(instancePhases, 1));
|
|
7070
|
+
|
|
7071
|
+
const secondaryMaterial = new THREE.ShaderMaterial({
|
|
7072
|
+
vertexShader: secondaryVertexShader,
|
|
7073
|
+
fragmentShader: secondaryFragmentShader,
|
|
7074
|
+
uniforms: {
|
|
7075
|
+
uTime: { value: 0 },
|
|
7076
|
+
uPixelRatio: { value: STATE.pixelRatio },
|
|
7077
|
+
uSubBass: { value: 0 },
|
|
7078
|
+
uBass: { value: 0 },
|
|
7079
|
+
uLowMid: { value: 0 },
|
|
7080
|
+
uMid: { value: 0 },
|
|
7081
|
+
uHighMid: { value: 0 },
|
|
7082
|
+
uHigh: { value: 0 },
|
|
7083
|
+
uUltraHigh: { value: 0 },
|
|
7084
|
+
uBeatEnergy: { value: 0 },
|
|
7085
|
+
uSpectralCentroid: { value: 0 },
|
|
7086
|
+
uSpectralFlux: { value: 0 },
|
|
7087
|
+
uOnsetEnergy: { value: 0 },
|
|
7088
|
+
uVortexStrength: { value: STATE.vortexStrength },
|
|
7089
|
+
uColor1: { value: new THREE.Color(def.sc1) },
|
|
7090
|
+
uColor2: { value: new THREE.Color(def.sc2) },
|
|
7091
7091
|
uViewportAspect: { value: window.innerWidth / window.innerHeight },
|
|
7092
7092
|
uViewportHalfHeight: { value: Math.tan(camera.fov * Math.PI / 360) * CONFIG.defaultZoom }
|
|
7093
|
-
}
|
|
7094
|
-
transparent: true
|
|
7095
|
-
depthWrite: false
|
|
7096
|
-
blending: THREE.AdditiveBlending
|
|
7097
|
-
})
|
|
7098
|
-
|
|
7099
|
-
const secondaryPoints = new THREE.Points(secondaryGeometry, secondaryMaterial)
|
|
7100
|
-
scene.add(secondaryPoints)
|
|
7101
|
-
|
|
7093
|
+
},
|
|
7094
|
+
transparent: true,
|
|
7095
|
+
depthWrite: false,
|
|
7096
|
+
blending: THREE.AdditiveBlending
|
|
7097
|
+
});
|
|
7098
|
+
|
|
7099
|
+
const secondaryPoints = new THREE.Points(secondaryGeometry, secondaryMaterial);
|
|
7100
|
+
scene.add(secondaryPoints);
|
|
7101
|
+
|
|
7102
7102
|
// Expose secondary-layer controls to the host component.
|
|
7103
|
-
STATE.secondaryEnabled = true
|
|
7104
|
-
STATE.secondaryOpacity = 1.0
|
|
7105
|
-
|
|
7106
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 PARTICLE TRAIL SYSTEM \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7103
|
+
STATE.secondaryEnabled = true;
|
|
7104
|
+
STATE.secondaryOpacity = 1.0;
|
|
7105
|
+
|
|
7106
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 PARTICLE TRAIL SYSTEM \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7107
7107
|
const particleSeeds = geometry.getAttribute('aRandom');
|
|
7108
7108
|
function fieldParticlePosition(index, target) {
|
|
7109
7109
|
const halfHeight = material.uniforms.uViewportHalfHeight.value * 0.78;
|
|
@@ -7116,7 +7116,7 @@ void main() {\r
|
|
|
7116
7116
|
}
|
|
7117
7117
|
|
|
7118
7118
|
class TrailSystem {
|
|
7119
|
-
constructor(count, length) {
|
|
7119
|
+
constructor(count, length) {
|
|
7120
7120
|
this.trails = [];
|
|
7121
7121
|
this.meshes = [];
|
|
7122
7122
|
this.nextPosition = new THREE.Vector3();
|
|
@@ -7131,41 +7131,41 @@ void main() {\r
|
|
|
7131
7131
|
const trail = {
|
|
7132
7132
|
positions: Array.from({ length }, () => start.clone()),
|
|
7133
7133
|
anchor,
|
|
7134
|
-
phase: Math.random() * Math.PI * 2
|
|
7135
|
-
speed: 0.3 + Math.random() * 0.7
|
|
7136
|
-
color: new THREE.Color().setHSL(Math.random(), 0.7, 0.6)
|
|
7137
|
-
}
|
|
7138
|
-
this.trails.push(trail)
|
|
7139
|
-
|
|
7140
|
-
const geom = new THREE.BufferGeometry()
|
|
7141
|
-
const positions = new Float32Array(length * 3)
|
|
7142
|
-
const alphas = new Float32Array(length)
|
|
7143
|
-
for (let j = 0; j < length; j++) alphas[j] = 1.0 - j / length
|
|
7144
|
-
|
|
7145
|
-
geom.setAttribute('position', new THREE.BufferAttribute(positions, 3))
|
|
7146
|
-
geom.setAttribute('alpha', new THREE.BufferAttribute(alphas, 1))
|
|
7147
|
-
|
|
7148
|
-
const mat = new THREE.ShaderMaterial({
|
|
7149
|
-
vertexShader: \`attribute float alpha; varying float vAlpha; void main() { vAlpha = alpha; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
|
|
7150
|
-
fragmentShader: \`uniform vec3 uColor; uniform float uOpacity, uBeat; varying float vAlpha; void main() { gl_FragColor = vec4(uColor * (1.0 + uBeat * 0.3), vAlpha * uOpacity * 0.6 * (1.0 + uBeat * 0.5)); }
|
|
7151
|
-
uniforms: { uColor: { value: trail.color }, uOpacity: { value: 0.4 }, uBeat: { value: 0 } }
|
|
7152
|
-
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending
|
|
7153
|
-
})
|
|
7154
|
-
|
|
7155
|
-
const line = new THREE.Line(geom, mat)
|
|
7156
|
-
this.meshes.push(line)
|
|
7157
|
-
scene.add(line)
|
|
7158
|
-
}
|
|
7159
|
-
}
|
|
7160
|
-
|
|
7134
|
+
phase: Math.random() * Math.PI * 2,
|
|
7135
|
+
speed: 0.3 + Math.random() * 0.7,
|
|
7136
|
+
color: new THREE.Color().setHSL(Math.random(), 0.7, 0.6)
|
|
7137
|
+
};
|
|
7138
|
+
this.trails.push(trail);
|
|
7139
|
+
|
|
7140
|
+
const geom = new THREE.BufferGeometry();
|
|
7141
|
+
const positions = new Float32Array(length * 3);
|
|
7142
|
+
const alphas = new Float32Array(length);
|
|
7143
|
+
for (let j = 0; j < length; j++) alphas[j] = 1.0 - j / length;
|
|
7144
|
+
|
|
7145
|
+
geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
7146
|
+
geom.setAttribute('alpha', new THREE.BufferAttribute(alphas, 1));
|
|
7147
|
+
|
|
7148
|
+
const mat = new THREE.ShaderMaterial({
|
|
7149
|
+
vertexShader: \`attribute float alpha; varying float vAlpha; void main() { vAlpha = alpha; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
7150
|
+
fragmentShader: \`uniform vec3 uColor; uniform float uOpacity, uBeat; varying float vAlpha; void main() { gl_FragColor = vec4(uColor * (1.0 + uBeat * 0.3), vAlpha * uOpacity * 0.6 * (1.0 + uBeat * 0.5)); }\`,
|
|
7151
|
+
uniforms: { uColor: { value: trail.color }, uOpacity: { value: 0.4 }, uBeat: { value: 0 } },
|
|
7152
|
+
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending
|
|
7153
|
+
});
|
|
7154
|
+
|
|
7155
|
+
const line = new THREE.Line(geom, mat);
|
|
7156
|
+
this.meshes.push(line);
|
|
7157
|
+
scene.add(line);
|
|
7158
|
+
}
|
|
7159
|
+
}
|
|
7160
|
+
|
|
7161
7161
|
update(time, audio, fieldColor) {
|
|
7162
7162
|
const halfHeight = material.uniforms.uViewportHalfHeight.value * 0.78;
|
|
7163
7163
|
const halfWidth = halfHeight * material.uniforms.uViewportAspect.value;
|
|
7164
7164
|
for (let i = 0; i < this.trails.length; i++) {
|
|
7165
|
-
const trail = this.trails[i], mesh = this.meshes[i]
|
|
7165
|
+
const trail = this.trails[i], mesh = this.meshes[i];
|
|
7166
7166
|
mesh.visible = STATE.particleTrailsEnabled && i < this.trails.length * STATE.qualityLevel;
|
|
7167
|
-
if (!mesh.visible) continue
|
|
7168
|
-
|
|
7167
|
+
if (!mesh.visible) continue;
|
|
7168
|
+
|
|
7169
7169
|
const angle = time * 0.5 * trail.speed + trail.phase;
|
|
7170
7170
|
const newPos = this.nextPosition.set(
|
|
7171
7171
|
trail.anchor.x * halfWidth + Math.cos(angle) * (4 + audio.bass * 8),
|
|
@@ -7174,86 +7174,86 @@ void main() {\r
|
|
|
7174
7174
|
);
|
|
7175
7175
|
newPos.x = THREE.MathUtils.clamp(newPos.x, -halfWidth * 0.94, halfWidth * 0.94);
|
|
7176
7176
|
newPos.y = THREE.MathUtils.clamp(newPos.y, -halfHeight * 0.94, halfHeight * 0.94);
|
|
7177
|
-
|
|
7178
|
-
for (let j = trail.positions.length - 1; j > 0; j--) trail.positions[j].copy(trail.positions[j - 1])
|
|
7179
|
-
trail.positions[0].copy(newPos)
|
|
7180
|
-
|
|
7181
|
-
const posAttr = mesh.geometry.getAttribute('position')
|
|
7182
|
-
for (let j = 0; j < trail.positions.length; j++) {
|
|
7183
|
-
posAttr.setXYZ(j, trail.positions[j].x, trail.positions[j].y, trail.positions[j].z)
|
|
7184
|
-
}
|
|
7185
|
-
posAttr.needsUpdate = true
|
|
7186
|
-
|
|
7187
|
-
mesh.material.uniforms.uBeat.value = audio.beat
|
|
7188
|
-
mesh.material.uniforms.uOpacity.value = STATE.particleTrailOpacity
|
|
7189
|
-
if (fieldColor) trail.color.lerp(fieldColor, 0.01)
|
|
7190
|
-
}
|
|
7191
|
-
}
|
|
7192
|
-
}
|
|
7193
|
-
|
|
7194
|
-
const trailSystem = new TrailSystem(50, 12)
|
|
7195
|
-
|
|
7196
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 PARTICLE CONNECTIONS (Force Network) \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7197
|
-
const connectionCount = 500; // Number of particles to check for connections
|
|
7198
|
-
const maxConnections = 800; // Maximum line segments
|
|
7199
|
-
const connectionGeometry = new THREE.BufferGeometry()
|
|
7200
|
-
const connectionPositions = new Float32Array(maxConnections * 6); // 2 points per line, 3 coords each
|
|
7201
|
-
const connectionAlphas = new Float32Array(maxConnections * 2); // Alpha per vertex
|
|
7202
|
-
connectionGeometry.setAttribute('position', new THREE.BufferAttribute(connectionPositions, 3))
|
|
7203
|
-
connectionGeometry.setAttribute('alpha', new THREE.BufferAttribute(connectionAlphas, 1))
|
|
7204
|
-
connectionGeometry.setDrawRange(0, 0)
|
|
7205
|
-
|
|
7206
|
-
const connectionMaterial = new THREE.ShaderMaterial({
|
|
7207
|
-
vertexShader:
|
|
7208
|
-
attribute float alpha
|
|
7209
|
-
varying float vAlpha
|
|
7177
|
+
|
|
7178
|
+
for (let j = trail.positions.length - 1; j > 0; j--) trail.positions[j].copy(trail.positions[j - 1]);
|
|
7179
|
+
trail.positions[0].copy(newPos);
|
|
7180
|
+
|
|
7181
|
+
const posAttr = mesh.geometry.getAttribute('position');
|
|
7182
|
+
for (let j = 0; j < trail.positions.length; j++) {
|
|
7183
|
+
posAttr.setXYZ(j, trail.positions[j].x, trail.positions[j].y, trail.positions[j].z);
|
|
7184
|
+
}
|
|
7185
|
+
posAttr.needsUpdate = true;
|
|
7186
|
+
|
|
7187
|
+
mesh.material.uniforms.uBeat.value = audio.beat;
|
|
7188
|
+
mesh.material.uniforms.uOpacity.value = STATE.particleTrailOpacity;
|
|
7189
|
+
if (fieldColor) trail.color.lerp(fieldColor, 0.01);
|
|
7190
|
+
}
|
|
7191
|
+
}
|
|
7192
|
+
}
|
|
7193
|
+
|
|
7194
|
+
const trailSystem = new TrailSystem(50, 12);
|
|
7195
|
+
|
|
7196
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 PARTICLE CONNECTIONS (Force Network) \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7197
|
+
const connectionCount = 500; // Number of particles to check for connections
|
|
7198
|
+
const maxConnections = 800; // Maximum line segments
|
|
7199
|
+
const connectionGeometry = new THREE.BufferGeometry();
|
|
7200
|
+
const connectionPositions = new Float32Array(maxConnections * 6); // 2 points per line, 3 coords each
|
|
7201
|
+
const connectionAlphas = new Float32Array(maxConnections * 2); // Alpha per vertex
|
|
7202
|
+
connectionGeometry.setAttribute('position', new THREE.BufferAttribute(connectionPositions, 3));
|
|
7203
|
+
connectionGeometry.setAttribute('alpha', new THREE.BufferAttribute(connectionAlphas, 1));
|
|
7204
|
+
connectionGeometry.setDrawRange(0, 0);
|
|
7205
|
+
|
|
7206
|
+
const connectionMaterial = new THREE.ShaderMaterial({
|
|
7207
|
+
vertexShader: \`
|
|
7208
|
+
attribute float alpha;
|
|
7209
|
+
varying float vAlpha;
|
|
7210
7210
|
void main() {
|
|
7211
7211
|
vAlpha = alpha;
|
|
7212
7212
|
vec4 mvPos = modelViewMatrix * vec4(position, 1.0);
|
|
7213
7213
|
gl_Position = projectionMatrix * mvPos;
|
|
7214
|
-
}
|
|
7215
|
-
|
|
7216
|
-
fragmentShader:
|
|
7217
|
-
uniform vec3 uColor
|
|
7218
|
-
uniform vec3 uColor2
|
|
7219
|
-
uniform float uOpacity
|
|
7220
|
-
uniform float uBeatEnergy
|
|
7221
|
-
uniform float uHighMid
|
|
7222
|
-
uniform float uSpectralCentroid
|
|
7223
|
-
varying float vAlpha
|
|
7214
|
+
}
|
|
7215
|
+
\`,
|
|
7216
|
+
fragmentShader: \`
|
|
7217
|
+
uniform vec3 uColor;
|
|
7218
|
+
uniform vec3 uColor2;
|
|
7219
|
+
uniform float uOpacity;
|
|
7220
|
+
uniform float uBeatEnergy;
|
|
7221
|
+
uniform float uHighMid;
|
|
7222
|
+
uniform float uSpectralCentroid;
|
|
7223
|
+
varying float vAlpha;
|
|
7224
7224
|
void main() {
|
|
7225
7225
|
// Blend hue with spectral centroid (brighter/higher = more c2)
|
|
7226
7226
|
vec3 color = mix(uColor, uColor2, clamp(uSpectralCentroid * 2.0, 0.0, 1.0));
|
|
7227
|
-
// Beat pulse flash
|
|
7228
|
-
float beatFlash = 1.0 + uBeatEnergy * 1.2
|
|
7229
|
-
color *= beatFlash
|
|
7230
|
-
// Per-line alpha reacts to highMid (attack/presence energy)
|
|
7231
|
-
float lineAlpha = vAlpha * uOpacity * (0.4 + uHighMid * 0.5 + uBeatEnergy * 0.4)
|
|
7232
|
-
gl_FragColor = vec4(color, clamp(lineAlpha, 0.0, 1.0))
|
|
7233
|
-
}
|
|
7234
|
-
|
|
7235
|
-
uniforms: {
|
|
7236
|
-
uColor: { value: new THREE.Color(def.c2) }
|
|
7237
|
-
uColor2: { value: new THREE.Color(def.c1) }
|
|
7238
|
-
uOpacity: { value: 1.0 }
|
|
7239
|
-
uBeatEnergy: { value: 0 }
|
|
7240
|
-
uHighMid: { value: 0 }
|
|
7241
|
-
uSpectralCentroid: { value: 0 }
|
|
7242
|
-
}
|
|
7243
|
-
transparent: true
|
|
7244
|
-
depthWrite: false
|
|
7245
|
-
blending: THREE.AdditiveBlending
|
|
7246
|
-
})
|
|
7247
|
-
|
|
7248
|
-
const connectionMesh = new THREE.LineSegments(connectionGeometry, connectionMaterial)
|
|
7249
|
-
scene.add(connectionMesh)
|
|
7250
|
-
|
|
7227
|
+
// Beat pulse flash
|
|
7228
|
+
float beatFlash = 1.0 + uBeatEnergy * 1.2;
|
|
7229
|
+
color *= beatFlash;
|
|
7230
|
+
// Per-line alpha reacts to highMid (attack/presence energy)
|
|
7231
|
+
float lineAlpha = vAlpha * uOpacity * (0.4 + uHighMid * 0.5 + uBeatEnergy * 0.4);
|
|
7232
|
+
gl_FragColor = vec4(color, clamp(lineAlpha, 0.0, 1.0));
|
|
7233
|
+
}
|
|
7234
|
+
\`,
|
|
7235
|
+
uniforms: {
|
|
7236
|
+
uColor: { value: new THREE.Color(def.c2) },
|
|
7237
|
+
uColor2: { value: new THREE.Color(def.c1) },
|
|
7238
|
+
uOpacity: { value: 1.0 },
|
|
7239
|
+
uBeatEnergy: { value: 0 },
|
|
7240
|
+
uHighMid: { value: 0 },
|
|
7241
|
+
uSpectralCentroid: { value: 0 }
|
|
7242
|
+
},
|
|
7243
|
+
transparent: true,
|
|
7244
|
+
depthWrite: false,
|
|
7245
|
+
blending: THREE.AdditiveBlending
|
|
7246
|
+
});
|
|
7247
|
+
|
|
7248
|
+
const connectionMesh = new THREE.LineSegments(connectionGeometry, connectionMaterial);
|
|
7249
|
+
scene.add(connectionMesh);
|
|
7250
|
+
|
|
7251
7251
|
// Particle indices to track for connections \u2014 refreshed periodically
|
|
7252
7252
|
const trackedParticles = [];
|
|
7253
7253
|
const positions = Array.from({ length: connectionCount }, () => new THREE.Vector3());
|
|
7254
|
-
let connectionRefreshCounter = 0
|
|
7254
|
+
let connectionRefreshCounter = 0;
|
|
7255
7255
|
const CONNECTION_REFRESH_INTERVAL = 40; // 20 Hz updates preserve the original ~2 s selection cycle
|
|
7256
|
-
|
|
7256
|
+
|
|
7257
7257
|
function refreshTrackedParticles() {
|
|
7258
7258
|
trackedParticles.length = 0;
|
|
7259
7259
|
const visibleParticles = Math.max(1, geometry.drawRange.count);
|
|
@@ -7261,7 +7261,7 @@ void main() {\r
|
|
|
7261
7261
|
const trackedCount = Math.max(2, Math.floor(connectionCount * Math.min(1, STATE.qualityLevel, visibleFraction)));
|
|
7262
7262
|
for (let i = 0; i < trackedCount; i++) {
|
|
7263
7263
|
trackedParticles.push(Math.floor(Math.random() * visibleParticles));
|
|
7264
|
-
}
|
|
7264
|
+
}
|
|
7265
7265
|
}
|
|
7266
7266
|
refreshTrackedParticles();
|
|
7267
7267
|
let lastConnectionState = null;
|
|
@@ -7274,395 +7274,395 @@ void main() {\r
|
|
|
7274
7274
|
// Refresh particle selection every N frames \u2014 network evolves over time
|
|
7275
7275
|
connectionRefreshCounter++;
|
|
7276
7276
|
if (forceRefresh || connectionRefreshCounter >= CONNECTION_REFRESH_INTERVAL) {
|
|
7277
|
-
refreshTrackedParticles()
|
|
7278
|
-
connectionRefreshCounter = 0
|
|
7279
|
-
}
|
|
7280
|
-
|
|
7277
|
+
refreshTrackedParticles();
|
|
7278
|
+
connectionRefreshCounter = 0;
|
|
7279
|
+
}
|
|
7280
|
+
|
|
7281
7281
|
// Mirror the shader's screen-fitted seed mapping for the line systems.
|
|
7282
7282
|
for (let i = 0; i < trackedParticles.length; i++) {
|
|
7283
7283
|
fieldParticlePosition(trackedParticles[i], positions[i]);
|
|
7284
|
-
}
|
|
7285
|
-
|
|
7286
|
-
// Threshold breathes with bass, tightens on high energy
|
|
7287
|
-
const effectiveThreshold = threshold * (1.0 + bassBoost * 0.6) * (1.0 - highMid * 0.15)
|
|
7288
|
-
const thresholdSq = effectiveThreshold * effectiveThreshold
|
|
7289
|
-
|
|
7290
|
-
// Dynamic connection cap \u2014 expands with mid+highMid energy
|
|
7291
|
-
const dynamicMax = Math.floor(maxConnections * (0.3 + (mid + highMid) * 0.7))
|
|
7292
|
-
|
|
7293
|
-
let connIdx = 0
|
|
7284
|
+
}
|
|
7285
|
+
|
|
7286
|
+
// Threshold breathes with bass, tightens on high energy
|
|
7287
|
+
const effectiveThreshold = threshold * (1.0 + bassBoost * 0.6) * (1.0 - highMid * 0.15);
|
|
7288
|
+
const thresholdSq = effectiveThreshold * effectiveThreshold;
|
|
7289
|
+
|
|
7290
|
+
// Dynamic connection cap \u2014 expands with mid+highMid energy
|
|
7291
|
+
const dynamicMax = Math.floor(maxConnections * (0.3 + (mid + highMid) * 0.7));
|
|
7292
|
+
|
|
7293
|
+
let connIdx = 0;
|
|
7294
7294
|
for (let i = 0; i < trackedParticles.length && connIdx < dynamicMax; i++) {
|
|
7295
7295
|
for (let j = i + 1; j < trackedParticles.length && connIdx < dynamicMax; j++) {
|
|
7296
|
-
const distSq = positions[i].distanceToSquared(positions[j])
|
|
7297
|
-
if (distSq < thresholdSq && distSq > 0.1) {
|
|
7298
|
-
const dist = Math.sqrt(distSq)
|
|
7296
|
+
const distSq = positions[i].distanceToSquared(positions[j]);
|
|
7297
|
+
if (distSq < thresholdSq && distSq > 0.1) {
|
|
7298
|
+
const dist = Math.sqrt(distSq);
|
|
7299
7299
|
const alpha = (1.0 - dist / effectiveThreshold) * 0.8;
|
|
7300
|
-
|
|
7301
|
-
connectionPositions[connIdx * 6 + 0] = positions[i].x
|
|
7302
|
-
connectionPositions[connIdx * 6 + 1] = positions[i].y
|
|
7303
|
-
connectionPositions[connIdx * 6 + 2] = positions[i].z
|
|
7304
|
-
connectionPositions[connIdx * 6 + 3] = positions[j].x
|
|
7305
|
-
connectionPositions[connIdx * 6 + 4] = positions[j].y
|
|
7306
|
-
connectionPositions[connIdx * 6 + 5] = positions[j].z
|
|
7307
|
-
|
|
7308
|
-
connectionAlphas[connIdx * 2 + 0] = alpha
|
|
7309
|
-
connectionAlphas[connIdx * 2 + 1] = alpha
|
|
7310
|
-
|
|
7311
|
-
connIdx
|
|
7312
|
-
}
|
|
7313
|
-
}
|
|
7314
|
-
}
|
|
7315
|
-
|
|
7316
|
-
connectionGeometry.attributes.position.needsUpdate = true
|
|
7317
|
-
connectionGeometry.attributes.alpha.needsUpdate = true
|
|
7318
|
-
connectionGeometry.setDrawRange(0, connIdx * 2)
|
|
7319
|
-
}
|
|
7320
|
-
|
|
7321
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 NETWORK CRAWLERS \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7322
|
-
const CRAWLER_COUNT = 24; // concurrent crawlers
|
|
7323
|
-
const CRAWLER_TAIL = 10; // trail positions per crawler
|
|
7324
|
-
const crawlerMaxSegs = CRAWLER_COUNT * (CRAWLER_TAIL - 1)
|
|
7325
|
-
|
|
7326
|
-
const crawlerGeo = new THREE.BufferGeometry()
|
|
7327
|
-
const crawlerPos = new Float32Array(crawlerMaxSegs * 6)
|
|
7328
|
-
const crawlerAlp = new Float32Array(crawlerMaxSegs * 2)
|
|
7329
|
-
crawlerGeo.setAttribute('position', new THREE.BufferAttribute(crawlerPos, 3))
|
|
7330
|
-
crawlerGeo.setAttribute('alpha', new THREE.BufferAttribute(crawlerAlp, 1))
|
|
7331
|
-
crawlerGeo.setDrawRange(0, 0)
|
|
7332
|
-
|
|
7333
|
-
const crawlerMaterial = new THREE.ShaderMaterial({
|
|
7334
|
-
vertexShader:
|
|
7335
|
-
attribute float alpha
|
|
7336
|
-
varying float vAlpha
|
|
7337
|
-
void main() {
|
|
7338
|
-
vAlpha = alpha
|
|
7339
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0)
|
|
7340
|
-
}
|
|
7341
|
-
|
|
7342
|
-
fragmentShader:
|
|
7343
|
-
uniform vec3 uColor
|
|
7344
|
-
uniform float uBeatEnergy
|
|
7345
|
-
varying float vAlpha
|
|
7346
|
-
void main() {
|
|
7347
|
-
vec3 c = uColor * (1.2 + uBeatEnergy * 1.5)
|
|
7348
|
-
gl_FragColor = vec4(c, vAlpha)
|
|
7349
|
-
}
|
|
7350
|
-
|
|
7351
|
-
uniforms: {
|
|
7352
|
-
uColor: { value: new THREE.Color(def.c1) }
|
|
7353
|
-
uBeatEnergy: { value: 0 }
|
|
7354
|
-
}
|
|
7355
|
-
transparent: true
|
|
7356
|
-
depthWrite: false
|
|
7357
|
-
blending: THREE.AdditiveBlending
|
|
7358
|
-
})
|
|
7359
|
-
|
|
7360
|
-
const crawlerMesh = new THREE.LineSegments(crawlerGeo, crawlerMaterial)
|
|
7361
|
-
scene.add(crawlerMesh)
|
|
7362
|
-
|
|
7363
|
-
// Initialise crawler agents
|
|
7364
|
-
const crawlers = []
|
|
7365
|
-
const CRAWLER_MIN_STEP = 1.8; // min world-space distance before adding a tail point
|
|
7366
|
-
for (let i = 0; i < CRAWLER_COUNT; i++) {
|
|
7367
|
-
crawlers.push({
|
|
7368
|
-
fromIdx: Math.floor(Math.random() * connectionCount)
|
|
7369
|
-
toIdx: Math.floor(Math.random() * connectionCount)
|
|
7370
|
-
t: Math.random()
|
|
7371
|
-
speed: 0.0015 + Math.random() * 0.002
|
|
7372
|
-
tail: []
|
|
7373
|
-
lastTailPos: null, // last world position a tail point was recorded
|
|
7374
|
-
hue: Math.random()
|
|
7375
|
-
})
|
|
7376
|
-
}
|
|
7377
|
-
|
|
7300
|
+
|
|
7301
|
+
connectionPositions[connIdx * 6 + 0] = positions[i].x;
|
|
7302
|
+
connectionPositions[connIdx * 6 + 1] = positions[i].y;
|
|
7303
|
+
connectionPositions[connIdx * 6 + 2] = positions[i].z;
|
|
7304
|
+
connectionPositions[connIdx * 6 + 3] = positions[j].x;
|
|
7305
|
+
connectionPositions[connIdx * 6 + 4] = positions[j].y;
|
|
7306
|
+
connectionPositions[connIdx * 6 + 5] = positions[j].z;
|
|
7307
|
+
|
|
7308
|
+
connectionAlphas[connIdx * 2 + 0] = alpha;
|
|
7309
|
+
connectionAlphas[connIdx * 2 + 1] = alpha;
|
|
7310
|
+
|
|
7311
|
+
connIdx++;
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
|
|
7316
|
+
connectionGeometry.attributes.position.needsUpdate = true;
|
|
7317
|
+
connectionGeometry.attributes.alpha.needsUpdate = true;
|
|
7318
|
+
connectionGeometry.setDrawRange(0, connIdx * 2);
|
|
7319
|
+
}
|
|
7320
|
+
|
|
7321
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 NETWORK CRAWLERS \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7322
|
+
const CRAWLER_COUNT = 24; // concurrent crawlers
|
|
7323
|
+
const CRAWLER_TAIL = 10; // trail positions per crawler
|
|
7324
|
+
const crawlerMaxSegs = CRAWLER_COUNT * (CRAWLER_TAIL - 1);
|
|
7325
|
+
|
|
7326
|
+
const crawlerGeo = new THREE.BufferGeometry();
|
|
7327
|
+
const crawlerPos = new Float32Array(crawlerMaxSegs * 6);
|
|
7328
|
+
const crawlerAlp = new Float32Array(crawlerMaxSegs * 2);
|
|
7329
|
+
crawlerGeo.setAttribute('position', new THREE.BufferAttribute(crawlerPos, 3));
|
|
7330
|
+
crawlerGeo.setAttribute('alpha', new THREE.BufferAttribute(crawlerAlp, 1));
|
|
7331
|
+
crawlerGeo.setDrawRange(0, 0);
|
|
7332
|
+
|
|
7333
|
+
const crawlerMaterial = new THREE.ShaderMaterial({
|
|
7334
|
+
vertexShader: \`
|
|
7335
|
+
attribute float alpha;
|
|
7336
|
+
varying float vAlpha;
|
|
7337
|
+
void main() {
|
|
7338
|
+
vAlpha = alpha;
|
|
7339
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
7340
|
+
}
|
|
7341
|
+
\`,
|
|
7342
|
+
fragmentShader: \`
|
|
7343
|
+
uniform vec3 uColor;
|
|
7344
|
+
uniform float uBeatEnergy;
|
|
7345
|
+
varying float vAlpha;
|
|
7346
|
+
void main() {
|
|
7347
|
+
vec3 c = uColor * (1.2 + uBeatEnergy * 1.5);
|
|
7348
|
+
gl_FragColor = vec4(c, vAlpha);
|
|
7349
|
+
}
|
|
7350
|
+
\`,
|
|
7351
|
+
uniforms: {
|
|
7352
|
+
uColor: { value: new THREE.Color(def.c1) },
|
|
7353
|
+
uBeatEnergy: { value: 0 }
|
|
7354
|
+
},
|
|
7355
|
+
transparent: true,
|
|
7356
|
+
depthWrite: false,
|
|
7357
|
+
blending: THREE.AdditiveBlending
|
|
7358
|
+
});
|
|
7359
|
+
|
|
7360
|
+
const crawlerMesh = new THREE.LineSegments(crawlerGeo, crawlerMaterial);
|
|
7361
|
+
scene.add(crawlerMesh);
|
|
7362
|
+
|
|
7363
|
+
// Initialise crawler agents
|
|
7364
|
+
const crawlers = [];
|
|
7365
|
+
const CRAWLER_MIN_STEP = 1.8; // min world-space distance before adding a tail point
|
|
7366
|
+
for (let i = 0; i < CRAWLER_COUNT; i++) {
|
|
7367
|
+
crawlers.push({
|
|
7368
|
+
fromIdx: Math.floor(Math.random() * connectionCount),
|
|
7369
|
+
toIdx: Math.floor(Math.random() * connectionCount),
|
|
7370
|
+
t: Math.random(),
|
|
7371
|
+
speed: 0.0015 + Math.random() * 0.002,
|
|
7372
|
+
tail: [],
|
|
7373
|
+
lastTailPos: null, // last world position a tail point was recorded
|
|
7374
|
+
hue: Math.random()
|
|
7375
|
+
});
|
|
7376
|
+
}
|
|
7377
|
+
|
|
7378
7378
|
function updateCrawlers(dt, highMid, beatEnergy, spectralCentroid) {
|
|
7379
7379
|
// Helper: world position of a tracked particle
|
|
7380
7380
|
function particlePos(idx) {
|
|
7381
7381
|
return positions[idx % trackedParticles.length];
|
|
7382
|
-
}
|
|
7383
|
-
|
|
7384
|
-
// Helper: find nearest neighbour within threshold
|
|
7385
|
-
function nearestNeighbour(fromIdx, excludeIdx) {
|
|
7386
|
-
const origin = particlePos(fromIdx)
|
|
7387
|
-
const threshold = STATE.connectionThreshold * (1.2 + highMid * 0.4)
|
|
7388
|
-
const threshSq = threshold * threshold
|
|
7389
|
-
let bestDist = Infinity, bestIdx = -1
|
|
7390
|
-
const check = Math.min(trackedParticles.length, 80); // sample subset for perf
|
|
7391
|
-
const start = Math.floor(Math.random() * (trackedParticles.length - check))
|
|
7392
|
-
for (let k = start; k < start + check; k++) {
|
|
7393
|
-
const ki = k % trackedParticles.length
|
|
7394
|
-
if (ki === fromIdx || ki === excludeIdx) continue
|
|
7395
|
-
const d = origin.distanceToSquared(particlePos(ki))
|
|
7396
|
-
if (d < threshSq && d < bestDist) { bestDist = d; bestIdx = ki; }
|
|
7397
|
-
}
|
|
7398
|
-
// fallback: pick random if no neighbour in range
|
|
7399
|
-
return bestIdx === -1 ? Math.floor(Math.random() * trackedParticles.length) : bestIdx
|
|
7400
|
-
}
|
|
7401
|
-
|
|
7402
|
-
let segIdx = 0
|
|
7403
|
-
|
|
7404
|
-
for (let i = 0; i < crawlers.length; i++) {
|
|
7405
|
-
const c = crawlers[i]
|
|
7406
|
-
|
|
7407
|
-
// Speed reacts to highMid (attack energy) and beat
|
|
7408
|
-
const spd = c.speed * (1.0 + highMid * 0.8 + beatEnergy * 0.5)
|
|
7409
|
-
c.t += spd
|
|
7410
|
-
|
|
7411
|
-
// Reached target \u2014 advance to next node
|
|
7412
|
-
if (c.t >= 1.0) {
|
|
7413
|
-
c.t -= 1.0
|
|
7414
|
-
c.fromIdx = c.toIdx
|
|
7415
|
-
c.toIdx = nearestNeighbour(c.fromIdx, c.fromIdx)
|
|
7416
|
-
// Occasionally teleport to keep crawlers spread across the field
|
|
7417
|
-
if (Math.random() < 0.04) {
|
|
7418
|
-
c.fromIdx = Math.floor(Math.random() * trackedParticles.length)
|
|
7419
|
-
c.toIdx = nearestNeighbour(c.fromIdx, c.fromIdx)
|
|
7420
|
-
c.tail = []
|
|
7421
|
-
c.lastTailPos = null
|
|
7422
|
-
}
|
|
7423
|
-
}
|
|
7424
|
-
|
|
7425
|
-
// Current head position
|
|
7426
|
-
const from = particlePos(c.fromIdx)
|
|
7427
|
-
const to = particlePos(c.toIdx)
|
|
7428
|
-
const head = new THREE.Vector3().lerpVectors(from, to, c.t)
|
|
7429
|
-
|
|
7430
|
-
// Only record a tail point when head has moved far enough in world space
|
|
7431
|
-
const distMoved = c.lastTailPos ? head.distanceTo(c.lastTailPos) : Infinity
|
|
7432
|
-
if (distMoved >= CRAWLER_MIN_STEP) {
|
|
7433
|
-
c.tail.push(head.clone())
|
|
7434
|
-
if (c.tail.length > CRAWLER_TAIL) c.tail.shift()
|
|
7435
|
-
c.lastTailPos = head.clone()
|
|
7436
|
-
}
|
|
7437
|
-
|
|
7438
|
-
// Write tail segments
|
|
7439
|
-
const tailLen = c.tail.length
|
|
7440
|
-
for (let s = 0; s < tailLen - 1 && segIdx < crawlerMaxSegs; s++) {
|
|
7441
|
-
const p0 = c.tail[s]
|
|
7442
|
-
const p1 = c.tail[s + 1]
|
|
7443
|
-
// Alpha: head is brightest, tail fades
|
|
7444
|
-
const baseAlpha = ((s + 1) / (tailLen - 1))
|
|
7445
|
-
const alpha = baseAlpha * (0.25 + spectralCentroid * 0.3 + beatEnergy * 0.4)
|
|
7446
|
-
|
|
7447
|
-
crawlerPos[segIdx * 6 + 0] = p0.x; crawlerPos[segIdx * 6 + 1] = p0.y; crawlerPos[segIdx * 6 + 2] = p0.z
|
|
7448
|
-
crawlerPos[segIdx * 6 + 3] = p1.x; crawlerPos[segIdx * 6 + 4] = p1.y; crawlerPos[segIdx * 6 + 5] = p1.z
|
|
7449
|
-
crawlerAlp[segIdx * 2 + 0] = alpha
|
|
7450
|
-
crawlerAlp[segIdx * 2 + 1] = alpha
|
|
7451
|
-
segIdx
|
|
7452
|
-
}
|
|
7453
|
-
}
|
|
7454
|
-
|
|
7455
|
-
crawlerGeo.attributes.position.needsUpdate = true
|
|
7456
|
-
crawlerGeo.attributes.alpha.needsUpdate = true
|
|
7457
|
-
crawlerGeo.setDrawRange(0, segIdx * 2)
|
|
7458
|
-
}
|
|
7459
|
-
|
|
7460
|
-
STATE.crawlersEnabled = true
|
|
7461
|
-
|
|
7462
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 NEBULA BACKGROUND \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7463
|
-
const nebulaVertexShader =
|
|
7464
|
-
varying vec3 vWorldPosition
|
|
7465
|
-
varying vec2 vUv
|
|
7466
|
-
void main() {
|
|
7467
|
-
vUv = uv
|
|
7468
|
-
vec4 worldPos = modelMatrix * vec4(position, 1.0)
|
|
7469
|
-
vWorldPosition = worldPos.xyz
|
|
7470
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0)
|
|
7471
|
-
}
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
const nebulaFragmentShader =
|
|
7475
|
-
uniform float uTime
|
|
7476
|
-
uniform vec3 uColor1
|
|
7477
|
-
uniform vec3 uColor2
|
|
7478
|
-
uniform vec3 uColor3
|
|
7479
|
-
uniform float uBass
|
|
7480
|
-
uniform float uMid
|
|
7481
|
-
uniform float uHigh
|
|
7482
|
-
uniform float uBeatEnergy
|
|
7483
|
-
uniform float uIntensity
|
|
7484
|
-
varying vec3 vWorldPosition
|
|
7485
|
-
varying vec2 vUv
|
|
7486
|
-
|
|
7487
|
-
// Simplex 3D noise
|
|
7488
|
-
vec4 permute(vec4 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
|
|
7489
|
-
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
7490
|
-
|
|
7491
|
-
float snoise(vec3 v) {
|
|
7492
|
-
const vec2 C = vec2(1.0/6.0, 1.0/3.0)
|
|
7493
|
-
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0)
|
|
7494
|
-
vec3 i = floor(v + dot(v, C.yyy))
|
|
7495
|
-
vec3 x0 = v - i + dot(i, C.xxx)
|
|
7496
|
-
vec3 g = step(x0.yzx, x0.xyz)
|
|
7497
|
-
vec3 l = 1.0 - g
|
|
7498
|
-
vec3 i1 = min(g.xyz, l.zxy)
|
|
7499
|
-
vec3 i2 = max(g.xyz, l.zxy)
|
|
7500
|
-
vec3 x1 = x0 - i1 + C.xxx
|
|
7501
|
-
vec3 x2 = x0 - i2 + C.yyy
|
|
7502
|
-
vec3 x3 = x0 - D.yyy
|
|
7503
|
-
i = mod(i, 289.0)
|
|
7504
|
-
vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0))
|
|
7505
|
-
float n_ = 1.0/7.0
|
|
7506
|
-
vec3 ns = n_ * D.wyz - D.xzx
|
|
7507
|
-
vec4 j = p - 49.0 * floor(p * ns.z * ns.z)
|
|
7508
|
-
vec4 x_ = floor(j * ns.z)
|
|
7509
|
-
vec4 y_ = floor(j - 7.0 * x_)
|
|
7510
|
-
vec4 x = x_ *ns.x + ns.yyyy
|
|
7511
|
-
vec4 y = y_ *ns.x + ns.yyyy
|
|
7512
|
-
vec4 h = 1.0 - abs(x) - abs(y)
|
|
7513
|
-
vec4 b0 = vec4(x.xy, y.xy)
|
|
7514
|
-
vec4 b1 = vec4(x.zw, y.zw)
|
|
7515
|
-
vec4 s0 = floor(b0)*2.0 + 1.0
|
|
7516
|
-
vec4 s1 = floor(b1)*2.0 + 1.0
|
|
7517
|
-
vec4 sh = -step(h, vec4(0.0))
|
|
7518
|
-
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy
|
|
7519
|
-
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww
|
|
7520
|
-
vec3 p0 = vec3(a0.xy, h.x)
|
|
7521
|
-
vec3 p1 = vec3(a0.zw, h.y)
|
|
7522
|
-
vec3 p2 = vec3(a1.xy, h.z)
|
|
7523
|
-
vec3 p3 = vec3(a1.zw, h.w)
|
|
7524
|
-
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)))
|
|
7525
|
-
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w
|
|
7526
|
-
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0)
|
|
7527
|
-
return 42.0 * dot(m*m*m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)))
|
|
7528
|
-
}
|
|
7529
|
-
|
|
7530
|
-
float fbm(vec3 p) {
|
|
7531
|
-
float value = 0.0
|
|
7532
|
-
float amplitude = 0.5
|
|
7533
|
-
float frequency = 1.0
|
|
7534
|
-
for(int i = 0; i < 3; i++) {
|
|
7535
|
-
value += amplitude * snoise(p * frequency)
|
|
7536
|
-
amplitude *= 0.5
|
|
7537
|
-
frequency *= 2.0
|
|
7538
|
-
}
|
|
7539
|
-
return value
|
|
7540
|
-
}
|
|
7541
|
-
|
|
7542
|
-
void main() {
|
|
7543
|
-
vec3 dir = normalize(vWorldPosition)
|
|
7544
|
-
float t = uTime * 0.02
|
|
7545
|
-
|
|
7546
|
-
// Layered nebula clouds
|
|
7547
|
-
vec3 p1 = dir * 2.0 + t * 0.5
|
|
7548
|
-
vec3 p2 = dir * 4.0 - t * 0.3
|
|
7549
|
-
vec3 p3 = dir * 1.5 + vec3(t * 0.2, -t * 0.1, t * 0.15)
|
|
7550
|
-
|
|
7551
|
-
float n1 = fbm(p1) * 0.5 + 0.5
|
|
7552
|
-
float n2 = fbm(p2) * 0.5 + 0.5
|
|
7553
|
-
float n3 = fbm(p3) * 0.5 + 0.5
|
|
7554
|
-
|
|
7555
|
-
// Create cloud density
|
|
7556
|
-
float density = n1 * n2
|
|
7557
|
-
density = pow(density, 1.5) * 1.5
|
|
7558
|
-
|
|
7559
|
-
// Wispy tendrils
|
|
7560
|
-
float tendrils = pow(n3, 3.0) * 0.8
|
|
7561
|
-
|
|
7562
|
-
// Color mixing based on field colors
|
|
7563
|
-
vec3 nebulaColor = mix(uColor1 * 0.3, uColor2 * 0.5, n1)
|
|
7564
|
-
nebulaColor = mix(nebulaColor, uColor3 * 0.4, n2 * 0.5)
|
|
7565
|
-
|
|
7566
|
-
// Add some complementary deep space colors
|
|
7567
|
-
vec3 deepSpace = vec3(0.02, 0.01, 0.05)
|
|
7568
|
-
vec3 starGlow = vec3(0.1, 0.08, 0.15)
|
|
7569
|
-
|
|
7570
|
-
// Audio reactivity
|
|
7571
|
-
float bassGlow = uBass * 0.3
|
|
7572
|
-
float midShimmer = uMid * 0.2 * sin(uTime * 2.0 + n1 * 10.0)
|
|
7573
|
-
float beatPulse = uBeatEnergy * 0.4
|
|
7574
|
-
|
|
7575
|
-
// Combine layers
|
|
7576
|
-
vec3 color = deepSpace
|
|
7577
|
-
color += nebulaColor * density * (0.4 + bassGlow + beatPulse)
|
|
7578
|
-
color += uColor2 * 0.15 * tendrils * (1.0 + uHigh * 0.5)
|
|
7579
|
-
color += starGlow * pow(n1 * n2, 4.0) * (1.0 + midShimmer)
|
|
7580
|
-
|
|
7581
|
-
// Scattered stars
|
|
7582
|
-
float stars = pow(snoise(dir * 50.0) * 0.5 + 0.5, 20.0)
|
|
7583
|
-
stars += pow(snoise(dir * 80.0 + 100.0) * 0.5 + 0.5, 25.0) * 0.5
|
|
7584
|
-
color += vec3(1.0, 0.95, 0.9) * stars * (0.5 + uHigh * 0.5)
|
|
7585
|
-
|
|
7586
|
-
// Vignette toward horizon
|
|
7587
|
-
float horizonFade = 1.0 - pow(abs(dir.y), 0.5) * 0.3
|
|
7588
|
-
color *= horizonFade
|
|
7589
|
-
|
|
7590
|
-
// Apply intensity
|
|
7591
|
-
color *= uIntensity
|
|
7592
|
-
|
|
7593
|
-
gl_FragColor = vec4(color, 1.0)
|
|
7594
|
-
}
|
|
7595
|
-
|
|
7596
|
-
|
|
7597
|
-
const nebulaMaterial = new THREE.ShaderMaterial({
|
|
7598
|
-
vertexShader: nebulaVertexShader
|
|
7599
|
-
fragmentShader: nebulaFragmentShader
|
|
7600
|
-
uniforms: {
|
|
7601
|
-
uTime: { value: 0 }
|
|
7602
|
-
uColor1: { value: new THREE.Color(def.c1) }
|
|
7603
|
-
uColor2: { value: new THREE.Color(def.c2) }
|
|
7604
|
-
uColor3: { value: new THREE.Color(def.c3) }
|
|
7605
|
-
uBass: { value: 0 }
|
|
7606
|
-
uMid: { value: 0 }
|
|
7607
|
-
uHigh: { value: 0 }
|
|
7608
|
-
uBeatEnergy: { value: 0 }
|
|
7609
|
-
uIntensity: { value: STATE.nebulaIntensity }
|
|
7610
|
-
}
|
|
7611
|
-
side: THREE.BackSide
|
|
7612
|
-
depthWrite: false
|
|
7613
|
-
})
|
|
7614
|
-
|
|
7615
|
-
const nebulaSphere = new THREE.Mesh(
|
|
7616
|
-
new THREE.SphereGeometry(500, 64, 64)
|
|
7617
|
-
nebulaMaterial
|
|
7618
|
-
)
|
|
7619
|
-
scene.add(nebulaSphere)
|
|
7620
|
-
|
|
7621
|
-
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 POST-PROCESSING COMPOSER \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7622
|
-
const composer = new EffectComposer(renderer)
|
|
7623
|
-
composer.addPass(new RenderPass(scene, camera))
|
|
7624
|
-
|
|
7625
|
-
const afterimagePass = new AfterimagePass()
|
|
7626
|
-
afterimagePass.uniforms['damp'].value = STATE.trails
|
|
7627
|
-
composer.addPass(afterimagePass)
|
|
7628
|
-
|
|
7629
|
-
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85)
|
|
7630
|
-
bloomPass.threshold = 0.25
|
|
7631
|
-
bloomPass.strength = STATE.bloom
|
|
7632
|
-
bloomPass.radius = 0.45
|
|
7633
|
-
composer.addPass(bloomPass)
|
|
7634
|
-
|
|
7635
|
-
const godRaysPass = new ShaderPass(GodRaysShader)
|
|
7636
|
-
godRaysPass.enabled = false
|
|
7637
|
-
composer.addPass(godRaysPass)
|
|
7638
|
-
|
|
7382
|
+
}
|
|
7383
|
+
|
|
7384
|
+
// Helper: find nearest neighbour within threshold
|
|
7385
|
+
function nearestNeighbour(fromIdx, excludeIdx) {
|
|
7386
|
+
const origin = particlePos(fromIdx);
|
|
7387
|
+
const threshold = STATE.connectionThreshold * (1.2 + highMid * 0.4);
|
|
7388
|
+
const threshSq = threshold * threshold;
|
|
7389
|
+
let bestDist = Infinity, bestIdx = -1;
|
|
7390
|
+
const check = Math.min(trackedParticles.length, 80); // sample subset for perf
|
|
7391
|
+
const start = Math.floor(Math.random() * (trackedParticles.length - check));
|
|
7392
|
+
for (let k = start; k < start + check; k++) {
|
|
7393
|
+
const ki = k % trackedParticles.length;
|
|
7394
|
+
if (ki === fromIdx || ki === excludeIdx) continue;
|
|
7395
|
+
const d = origin.distanceToSquared(particlePos(ki));
|
|
7396
|
+
if (d < threshSq && d < bestDist) { bestDist = d; bestIdx = ki; }
|
|
7397
|
+
}
|
|
7398
|
+
// fallback: pick random if no neighbour in range
|
|
7399
|
+
return bestIdx === -1 ? Math.floor(Math.random() * trackedParticles.length) : bestIdx;
|
|
7400
|
+
}
|
|
7401
|
+
|
|
7402
|
+
let segIdx = 0;
|
|
7403
|
+
|
|
7404
|
+
for (let i = 0; i < crawlers.length; i++) {
|
|
7405
|
+
const c = crawlers[i];
|
|
7406
|
+
|
|
7407
|
+
// Speed reacts to highMid (attack energy) and beat
|
|
7408
|
+
const spd = c.speed * (1.0 + highMid * 0.8 + beatEnergy * 0.5);
|
|
7409
|
+
c.t += spd;
|
|
7410
|
+
|
|
7411
|
+
// Reached target \u2014 advance to next node
|
|
7412
|
+
if (c.t >= 1.0) {
|
|
7413
|
+
c.t -= 1.0;
|
|
7414
|
+
c.fromIdx = c.toIdx;
|
|
7415
|
+
c.toIdx = nearestNeighbour(c.fromIdx, c.fromIdx);
|
|
7416
|
+
// Occasionally teleport to keep crawlers spread across the field
|
|
7417
|
+
if (Math.random() < 0.04) {
|
|
7418
|
+
c.fromIdx = Math.floor(Math.random() * trackedParticles.length);
|
|
7419
|
+
c.toIdx = nearestNeighbour(c.fromIdx, c.fromIdx);
|
|
7420
|
+
c.tail = [];
|
|
7421
|
+
c.lastTailPos = null;
|
|
7422
|
+
}
|
|
7423
|
+
}
|
|
7424
|
+
|
|
7425
|
+
// Current head position
|
|
7426
|
+
const from = particlePos(c.fromIdx);
|
|
7427
|
+
const to = particlePos(c.toIdx);
|
|
7428
|
+
const head = new THREE.Vector3().lerpVectors(from, to, c.t);
|
|
7429
|
+
|
|
7430
|
+
// Only record a tail point when head has moved far enough in world space
|
|
7431
|
+
const distMoved = c.lastTailPos ? head.distanceTo(c.lastTailPos) : Infinity;
|
|
7432
|
+
if (distMoved >= CRAWLER_MIN_STEP) {
|
|
7433
|
+
c.tail.push(head.clone());
|
|
7434
|
+
if (c.tail.length > CRAWLER_TAIL) c.tail.shift();
|
|
7435
|
+
c.lastTailPos = head.clone();
|
|
7436
|
+
}
|
|
7437
|
+
|
|
7438
|
+
// Write tail segments
|
|
7439
|
+
const tailLen = c.tail.length;
|
|
7440
|
+
for (let s = 0; s < tailLen - 1 && segIdx < crawlerMaxSegs; s++) {
|
|
7441
|
+
const p0 = c.tail[s];
|
|
7442
|
+
const p1 = c.tail[s + 1];
|
|
7443
|
+
// Alpha: head is brightest, tail fades
|
|
7444
|
+
const baseAlpha = ((s + 1) / (tailLen - 1));
|
|
7445
|
+
const alpha = baseAlpha * (0.25 + spectralCentroid * 0.3 + beatEnergy * 0.4);
|
|
7446
|
+
|
|
7447
|
+
crawlerPos[segIdx * 6 + 0] = p0.x; crawlerPos[segIdx * 6 + 1] = p0.y; crawlerPos[segIdx * 6 + 2] = p0.z;
|
|
7448
|
+
crawlerPos[segIdx * 6 + 3] = p1.x; crawlerPos[segIdx * 6 + 4] = p1.y; crawlerPos[segIdx * 6 + 5] = p1.z;
|
|
7449
|
+
crawlerAlp[segIdx * 2 + 0] = alpha;
|
|
7450
|
+
crawlerAlp[segIdx * 2 + 1] = alpha;
|
|
7451
|
+
segIdx++;
|
|
7452
|
+
}
|
|
7453
|
+
}
|
|
7454
|
+
|
|
7455
|
+
crawlerGeo.attributes.position.needsUpdate = true;
|
|
7456
|
+
crawlerGeo.attributes.alpha.needsUpdate = true;
|
|
7457
|
+
crawlerGeo.setDrawRange(0, segIdx * 2);
|
|
7458
|
+
}
|
|
7459
|
+
|
|
7460
|
+
STATE.crawlersEnabled = true;
|
|
7461
|
+
|
|
7462
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 NEBULA BACKGROUND \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7463
|
+
const nebulaVertexShader = \`
|
|
7464
|
+
varying vec3 vWorldPosition;
|
|
7465
|
+
varying vec2 vUv;
|
|
7466
|
+
void main() {
|
|
7467
|
+
vUv = uv;
|
|
7468
|
+
vec4 worldPos = modelMatrix * vec4(position, 1.0);
|
|
7469
|
+
vWorldPosition = worldPos.xyz;
|
|
7470
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
7471
|
+
}
|
|
7472
|
+
\`;
|
|
7473
|
+
|
|
7474
|
+
const nebulaFragmentShader = \`
|
|
7475
|
+
uniform float uTime;
|
|
7476
|
+
uniform vec3 uColor1;
|
|
7477
|
+
uniform vec3 uColor2;
|
|
7478
|
+
uniform vec3 uColor3;
|
|
7479
|
+
uniform float uBass;
|
|
7480
|
+
uniform float uMid;
|
|
7481
|
+
uniform float uHigh;
|
|
7482
|
+
uniform float uBeatEnergy;
|
|
7483
|
+
uniform float uIntensity;
|
|
7484
|
+
varying vec3 vWorldPosition;
|
|
7485
|
+
varying vec2 vUv;
|
|
7486
|
+
|
|
7487
|
+
// Simplex 3D noise
|
|
7488
|
+
vec4 permute(vec4 x) { return mod(((x*34.0)+1.0)*x, 289.0); }
|
|
7489
|
+
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
7490
|
+
|
|
7491
|
+
float snoise(vec3 v) {
|
|
7492
|
+
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
|
|
7493
|
+
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
|
7494
|
+
vec3 i = floor(v + dot(v, C.yyy));
|
|
7495
|
+
vec3 x0 = v - i + dot(i, C.xxx);
|
|
7496
|
+
vec3 g = step(x0.yzx, x0.xyz);
|
|
7497
|
+
vec3 l = 1.0 - g;
|
|
7498
|
+
vec3 i1 = min(g.xyz, l.zxy);
|
|
7499
|
+
vec3 i2 = max(g.xyz, l.zxy);
|
|
7500
|
+
vec3 x1 = x0 - i1 + C.xxx;
|
|
7501
|
+
vec3 x2 = x0 - i2 + C.yyy;
|
|
7502
|
+
vec3 x3 = x0 - D.yyy;
|
|
7503
|
+
i = mod(i, 289.0);
|
|
7504
|
+
vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0));
|
|
7505
|
+
float n_ = 1.0/7.0;
|
|
7506
|
+
vec3 ns = n_ * D.wyz - D.xzx;
|
|
7507
|
+
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
7508
|
+
vec4 x_ = floor(j * ns.z);
|
|
7509
|
+
vec4 y_ = floor(j - 7.0 * x_);
|
|
7510
|
+
vec4 x = x_ *ns.x + ns.yyyy;
|
|
7511
|
+
vec4 y = y_ *ns.x + ns.yyyy;
|
|
7512
|
+
vec4 h = 1.0 - abs(x) - abs(y);
|
|
7513
|
+
vec4 b0 = vec4(x.xy, y.xy);
|
|
7514
|
+
vec4 b1 = vec4(x.zw, y.zw);
|
|
7515
|
+
vec4 s0 = floor(b0)*2.0 + 1.0;
|
|
7516
|
+
vec4 s1 = floor(b1)*2.0 + 1.0;
|
|
7517
|
+
vec4 sh = -step(h, vec4(0.0));
|
|
7518
|
+
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
|
|
7519
|
+
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
|
|
7520
|
+
vec3 p0 = vec3(a0.xy, h.x);
|
|
7521
|
+
vec3 p1 = vec3(a0.zw, h.y);
|
|
7522
|
+
vec3 p2 = vec3(a1.xy, h.z);
|
|
7523
|
+
vec3 p3 = vec3(a1.zw, h.w);
|
|
7524
|
+
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
|
|
7525
|
+
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
|
|
7526
|
+
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
|
7527
|
+
return 42.0 * dot(m*m*m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
|
|
7528
|
+
}
|
|
7529
|
+
|
|
7530
|
+
float fbm(vec3 p) {
|
|
7531
|
+
float value = 0.0;
|
|
7532
|
+
float amplitude = 0.5;
|
|
7533
|
+
float frequency = 1.0;
|
|
7534
|
+
for(int i = 0; i < 3; i++) {
|
|
7535
|
+
value += amplitude * snoise(p * frequency);
|
|
7536
|
+
amplitude *= 0.5;
|
|
7537
|
+
frequency *= 2.0;
|
|
7538
|
+
}
|
|
7539
|
+
return value;
|
|
7540
|
+
}
|
|
7541
|
+
|
|
7542
|
+
void main() {
|
|
7543
|
+
vec3 dir = normalize(vWorldPosition);
|
|
7544
|
+
float t = uTime * 0.02;
|
|
7545
|
+
|
|
7546
|
+
// Layered nebula clouds
|
|
7547
|
+
vec3 p1 = dir * 2.0 + t * 0.5;
|
|
7548
|
+
vec3 p2 = dir * 4.0 - t * 0.3;
|
|
7549
|
+
vec3 p3 = dir * 1.5 + vec3(t * 0.2, -t * 0.1, t * 0.15);
|
|
7550
|
+
|
|
7551
|
+
float n1 = fbm(p1) * 0.5 + 0.5;
|
|
7552
|
+
float n2 = fbm(p2) * 0.5 + 0.5;
|
|
7553
|
+
float n3 = fbm(p3) * 0.5 + 0.5;
|
|
7554
|
+
|
|
7555
|
+
// Create cloud density
|
|
7556
|
+
float density = n1 * n2;
|
|
7557
|
+
density = pow(density, 1.5) * 1.5;
|
|
7558
|
+
|
|
7559
|
+
// Wispy tendrils
|
|
7560
|
+
float tendrils = pow(n3, 3.0) * 0.8;
|
|
7561
|
+
|
|
7562
|
+
// Color mixing based on field colors
|
|
7563
|
+
vec3 nebulaColor = mix(uColor1 * 0.3, uColor2 * 0.5, n1);
|
|
7564
|
+
nebulaColor = mix(nebulaColor, uColor3 * 0.4, n2 * 0.5);
|
|
7565
|
+
|
|
7566
|
+
// Add some complementary deep space colors
|
|
7567
|
+
vec3 deepSpace = vec3(0.02, 0.01, 0.05);
|
|
7568
|
+
vec3 starGlow = vec3(0.1, 0.08, 0.15);
|
|
7569
|
+
|
|
7570
|
+
// Audio reactivity
|
|
7571
|
+
float bassGlow = uBass * 0.3;
|
|
7572
|
+
float midShimmer = uMid * 0.2 * sin(uTime * 2.0 + n1 * 10.0);
|
|
7573
|
+
float beatPulse = uBeatEnergy * 0.4;
|
|
7574
|
+
|
|
7575
|
+
// Combine layers
|
|
7576
|
+
vec3 color = deepSpace;
|
|
7577
|
+
color += nebulaColor * density * (0.4 + bassGlow + beatPulse);
|
|
7578
|
+
color += uColor2 * 0.15 * tendrils * (1.0 + uHigh * 0.5);
|
|
7579
|
+
color += starGlow * pow(n1 * n2, 4.0) * (1.0 + midShimmer);
|
|
7580
|
+
|
|
7581
|
+
// Scattered stars
|
|
7582
|
+
float stars = pow(snoise(dir * 50.0) * 0.5 + 0.5, 20.0);
|
|
7583
|
+
stars += pow(snoise(dir * 80.0 + 100.0) * 0.5 + 0.5, 25.0) * 0.5;
|
|
7584
|
+
color += vec3(1.0, 0.95, 0.9) * stars * (0.5 + uHigh * 0.5);
|
|
7585
|
+
|
|
7586
|
+
// Vignette toward horizon
|
|
7587
|
+
float horizonFade = 1.0 - pow(abs(dir.y), 0.5) * 0.3;
|
|
7588
|
+
color *= horizonFade;
|
|
7589
|
+
|
|
7590
|
+
// Apply intensity
|
|
7591
|
+
color *= uIntensity;
|
|
7592
|
+
|
|
7593
|
+
gl_FragColor = vec4(color, 1.0);
|
|
7594
|
+
}
|
|
7595
|
+
\`;
|
|
7596
|
+
|
|
7597
|
+
const nebulaMaterial = new THREE.ShaderMaterial({
|
|
7598
|
+
vertexShader: nebulaVertexShader,
|
|
7599
|
+
fragmentShader: nebulaFragmentShader,
|
|
7600
|
+
uniforms: {
|
|
7601
|
+
uTime: { value: 0 },
|
|
7602
|
+
uColor1: { value: new THREE.Color(def.c1) },
|
|
7603
|
+
uColor2: { value: new THREE.Color(def.c2) },
|
|
7604
|
+
uColor3: { value: new THREE.Color(def.c3) },
|
|
7605
|
+
uBass: { value: 0 },
|
|
7606
|
+
uMid: { value: 0 },
|
|
7607
|
+
uHigh: { value: 0 },
|
|
7608
|
+
uBeatEnergy: { value: 0 },
|
|
7609
|
+
uIntensity: { value: STATE.nebulaIntensity }
|
|
7610
|
+
},
|
|
7611
|
+
side: THREE.BackSide,
|
|
7612
|
+
depthWrite: false
|
|
7613
|
+
});
|
|
7614
|
+
|
|
7615
|
+
const nebulaSphere = new THREE.Mesh(
|
|
7616
|
+
new THREE.SphereGeometry(500, 64, 64),
|
|
7617
|
+
nebulaMaterial
|
|
7618
|
+
);
|
|
7619
|
+
scene.add(nebulaSphere);
|
|
7620
|
+
|
|
7621
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 POST-PROCESSING COMPOSER \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7622
|
+
const composer = new EffectComposer(renderer);
|
|
7623
|
+
composer.addPass(new RenderPass(scene, camera));
|
|
7624
|
+
|
|
7625
|
+
const afterimagePass = new AfterimagePass();
|
|
7626
|
+
afterimagePass.uniforms['damp'].value = STATE.trails;
|
|
7627
|
+
composer.addPass(afterimagePass);
|
|
7628
|
+
|
|
7629
|
+
const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85);
|
|
7630
|
+
bloomPass.threshold = 0.25;
|
|
7631
|
+
bloomPass.strength = STATE.bloom;
|
|
7632
|
+
bloomPass.radius = 0.45;
|
|
7633
|
+
composer.addPass(bloomPass);
|
|
7634
|
+
|
|
7635
|
+
const godRaysPass = new ShaderPass(GodRaysShader);
|
|
7636
|
+
godRaysPass.enabled = false;
|
|
7637
|
+
composer.addPass(godRaysPass);
|
|
7638
|
+
|
|
7639
7639
|
const lensFlarePass = new ShaderPass(LensFlareShader);
|
|
7640
7640
|
lensFlarePass.enabled = STATE.lensFlareEnabled && STATE.lensFlareIntensity > 0;
|
|
7641
|
-
composer.addPass(lensFlarePass)
|
|
7642
|
-
|
|
7643
|
-
const anamorphicPass = new ShaderPass(AnamorphicShader)
|
|
7644
|
-
composer.addPass(anamorphicPass)
|
|
7645
|
-
|
|
7641
|
+
composer.addPass(lensFlarePass);
|
|
7642
|
+
|
|
7643
|
+
const anamorphicPass = new ShaderPass(AnamorphicShader);
|
|
7644
|
+
composer.addPass(anamorphicPass);
|
|
7645
|
+
|
|
7646
7646
|
const chromaticPass = new ShaderPass(ChromaticShader);
|
|
7647
7647
|
chromaticPass.enabled = STATE.chromaticAberration > 0;
|
|
7648
7648
|
composer.addPass(chromaticPass);
|
|
7649
|
-
|
|
7650
|
-
// Custom DOF pass (radial focus)
|
|
7651
|
-
const customDOFPass = new ShaderPass(CustomDOFShader)
|
|
7652
|
-
customDOFPass.uniforms.uFocus.value = STATE.dofFocus
|
|
7653
|
-
customDOFPass.uniforms.uFocalLength.value = STATE.dofFocalLength
|
|
7654
|
-
customDOFPass.uniforms.uBokehStrength.value = STATE.dofBokehStrength
|
|
7655
|
-
customDOFPass.uniforms.uResolution.value.set(window.innerWidth, window.innerHeight)
|
|
7656
|
-
customDOFPass.enabled = STATE.dofEnabled
|
|
7657
|
-
composer.addPass(customDOFPass)
|
|
7658
|
-
|
|
7659
|
-
const vignettePass = new ShaderPass(VignetteShader)
|
|
7660
|
-
composer.addPass(vignettePass)
|
|
7661
|
-
|
|
7649
|
+
|
|
7650
|
+
// Custom DOF pass (radial focus)
|
|
7651
|
+
const customDOFPass = new ShaderPass(CustomDOFShader);
|
|
7652
|
+
customDOFPass.uniforms.uFocus.value = STATE.dofFocus;
|
|
7653
|
+
customDOFPass.uniforms.uFocalLength.value = STATE.dofFocalLength;
|
|
7654
|
+
customDOFPass.uniforms.uBokehStrength.value = STATE.dofBokehStrength;
|
|
7655
|
+
customDOFPass.uniforms.uResolution.value.set(window.innerWidth, window.innerHeight);
|
|
7656
|
+
customDOFPass.enabled = STATE.dofEnabled;
|
|
7657
|
+
composer.addPass(customDOFPass);
|
|
7658
|
+
|
|
7659
|
+
const vignettePass = new ShaderPass(VignetteShader);
|
|
7660
|
+
composer.addPass(vignettePass);
|
|
7661
|
+
|
|
7662
7662
|
const grainPass = new ShaderPass(GrainShader);
|
|
7663
7663
|
grainPass.enabled = STATE.filmGrain > 0;
|
|
7664
7664
|
composer.addPass(grainPass);
|
|
7665
|
-
|
|
7665
|
+
|
|
7666
7666
|
const AlphaFromLumaShader = {
|
|
7667
7667
|
uniforms: { tDiffuse: { value: null } },
|
|
7668
7668
|
vertexShader: \`varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }\`,
|
|
@@ -7676,7 +7676,7 @@ void main() {\r
|
|
|
7676
7676
|
};
|
|
7677
7677
|
composer.addPass(new OutputPass());
|
|
7678
7678
|
composer.addPass(new ShaderPass(AlphaFromLumaShader));
|
|
7679
|
-
|
|
7679
|
+
|
|
7680
7680
|
// Keep the embedded CSS accents aligned with the kit palette.
|
|
7681
7681
|
function applyCssTheme(accent) {
|
|
7682
7682
|
const luminance = accent.r * 0.299 + accent.g * 0.587 + accent.b * 0.114;
|
|
@@ -7688,51 +7688,51 @@ void main() {\r
|
|
|
7688
7688
|
applyCssTheme(new THREE.Color(def.c1));
|
|
7689
7689
|
|
|
7690
7690
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 UI & EVENTS \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7691
|
-
const overlay = document.getElementById('overlay')
|
|
7692
|
-
const audioControls = document.getElementById('audio-controls')
|
|
7693
|
-
const playPauseBtn = document.getElementById('play-pause-btn')
|
|
7694
|
-
const progressBar = document.getElementById('progress-bar')
|
|
7695
|
-
const progressContainer = document.getElementById('progress-container')
|
|
7696
|
-
const timeDisplay = document.getElementById('time-display')
|
|
7697
|
-
const trackName = document.getElementById('track-name')
|
|
7698
|
-
const status = document.getElementById('status-bar')
|
|
7699
|
-
|
|
7700
|
-
const formatTime = s => \`\${Math.floor(s / 60)}:\${Math.floor(s % 60).toString().padStart(2, '0')}
|
|
7701
|
-
|
|
7702
|
-
function updateUI() {
|
|
7703
|
-
if (AUDIO.mode === 'mic') {
|
|
7704
|
-
playPauseBtn.textContent = '\u{1F399} live'
|
|
7705
|
-
playPauseBtn.disabled = true
|
|
7706
|
-
} else {
|
|
7707
|
-
playPauseBtn.textContent = AUDIO.isPlaying ? '\u23F8 pause' : '\u25B6 play'
|
|
7708
|
-
playPauseBtn.classList.toggle('playing', AUDIO.isPlaying)
|
|
7709
|
-
playPauseBtn.disabled = false
|
|
7710
|
-
}
|
|
7711
|
-
if (AUDIO.mode === 'file' && AUDIO.audioBuffer) {
|
|
7712
|
-
const cur = AUDIO.getCurrentTime(), dur = AUDIO.getDuration()
|
|
7713
|
-
progressBar.style.width = (cur / dur * 100) + '%'
|
|
7714
|
-
timeDisplay.textContent = \`\${formatTime(cur)} / \${formatTime(dur)}
|
|
7715
|
-
} else {
|
|
7716
|
-
progressBar.style.width = '0%'
|
|
7717
|
-
timeDisplay.textContent = AUDIO.mode === 'mic' ? 'LIVE' : '0:00 / 0:00'
|
|
7718
|
-
}
|
|
7719
|
-
}
|
|
7720
|
-
|
|
7721
|
-
function togglePlayPause() {
|
|
7722
|
-
if (AUDIO.mode !== 'file') return
|
|
7723
|
-
AUDIO.togglePlayPause()
|
|
7724
|
-
updateUI()
|
|
7725
|
-
status.innerHTML = AUDIO.isPlaying ? 'SYSTEM: <span>PLAYING</span>' : 'SYSTEM: <span>PAUSED</span>'
|
|
7726
|
-
}
|
|
7727
|
-
|
|
7728
|
-
function activate() {
|
|
7729
|
-
AUDIO.init()
|
|
7730
|
-
if (AUDIO.ctx.state === 'suspended') AUDIO.ctx.resume()
|
|
7731
|
-
overlay.style.opacity = 0
|
|
7732
|
-
setTimeout(() => overlay.style.display = 'none', 800)
|
|
7733
|
-
audioControls.classList.add('visible')
|
|
7734
|
-
}
|
|
7735
|
-
|
|
7691
|
+
const overlay = document.getElementById('overlay');
|
|
7692
|
+
const audioControls = document.getElementById('audio-controls');
|
|
7693
|
+
const playPauseBtn = document.getElementById('play-pause-btn');
|
|
7694
|
+
const progressBar = document.getElementById('progress-bar');
|
|
7695
|
+
const progressContainer = document.getElementById('progress-container');
|
|
7696
|
+
const timeDisplay = document.getElementById('time-display');
|
|
7697
|
+
const trackName = document.getElementById('track-name');
|
|
7698
|
+
const status = document.getElementById('status-bar');
|
|
7699
|
+
|
|
7700
|
+
const formatTime = s => \`\${Math.floor(s / 60)}:\${Math.floor(s % 60).toString().padStart(2, '0')}\`;
|
|
7701
|
+
|
|
7702
|
+
function updateUI() {
|
|
7703
|
+
if (AUDIO.mode === 'mic') {
|
|
7704
|
+
playPauseBtn.textContent = '\u{1F399} live';
|
|
7705
|
+
playPauseBtn.disabled = true;
|
|
7706
|
+
} else {
|
|
7707
|
+
playPauseBtn.textContent = AUDIO.isPlaying ? '\u23F8 pause' : '\u25B6 play';
|
|
7708
|
+
playPauseBtn.classList.toggle('playing', AUDIO.isPlaying);
|
|
7709
|
+
playPauseBtn.disabled = false;
|
|
7710
|
+
}
|
|
7711
|
+
if (AUDIO.mode === 'file' && AUDIO.audioBuffer) {
|
|
7712
|
+
const cur = AUDIO.getCurrentTime(), dur = AUDIO.getDuration();
|
|
7713
|
+
progressBar.style.width = (cur / dur * 100) + '%';
|
|
7714
|
+
timeDisplay.textContent = \`\${formatTime(cur)} / \${formatTime(dur)}\`;
|
|
7715
|
+
} else {
|
|
7716
|
+
progressBar.style.width = '0%';
|
|
7717
|
+
timeDisplay.textContent = AUDIO.mode === 'mic' ? 'LIVE' : '0:00 / 0:00';
|
|
7718
|
+
}
|
|
7719
|
+
}
|
|
7720
|
+
|
|
7721
|
+
function togglePlayPause() {
|
|
7722
|
+
if (AUDIO.mode !== 'file') return;
|
|
7723
|
+
AUDIO.togglePlayPause();
|
|
7724
|
+
updateUI();
|
|
7725
|
+
status.innerHTML = AUDIO.isPlaying ? 'SYSTEM: <span>PLAYING</span>' : 'SYSTEM: <span>PAUSED</span>';
|
|
7726
|
+
}
|
|
7727
|
+
|
|
7728
|
+
function activate() {
|
|
7729
|
+
AUDIO.init();
|
|
7730
|
+
if (AUDIO.ctx.state === 'suspended') AUDIO.ctx.resume();
|
|
7731
|
+
overlay.style.opacity = 0;
|
|
7732
|
+
setTimeout(() => overlay.style.display = 'none', 800);
|
|
7733
|
+
audioControls.classList.add('visible');
|
|
7734
|
+
}
|
|
7735
|
+
|
|
7736
7736
|
async function loadTrack(file) {
|
|
7737
7737
|
activate();
|
|
7738
7738
|
const buffer = await AUDIO.loadTrack(await file.arrayBuffer(), file.name);
|
|
@@ -7748,52 +7748,52 @@ void main() {\r
|
|
|
7748
7748
|
});
|
|
7749
7749
|
parent.postMessage({ type: 'ad-qf-waveform', value: peaks }, '*');
|
|
7750
7750
|
AUDIO.play();
|
|
7751
|
-
trackName.textContent = file.name.length > 25 ? file.name.slice(0, 22) + '...' : file.name
|
|
7752
|
-
updateUI()
|
|
7753
|
-
status.innerHTML = 'SYSTEM: <span>PLAYING</span>'
|
|
7754
|
-
}
|
|
7755
|
-
|
|
7756
|
-
async function startMic(preferredLabel = null) {
|
|
7757
|
-
console.log('Global startMic requested:', preferredLabel)
|
|
7758
|
-
try {
|
|
7759
|
-
activate()
|
|
7760
|
-
await AUDIO.startMic(preferredLabel)
|
|
7761
|
-
trackName.textContent = AUDIO.currentTrackName
|
|
7762
|
-
updateUI()
|
|
7763
|
-
// status update handled in AUDIO.startMic
|
|
7764
|
-
} catch (e) {
|
|
7765
|
-
console.error("Global startMic failed:", e)
|
|
7766
|
-
const status = document.getElementById('status-bar')
|
|
7767
|
-
status.innerHTML = \`ERROR: <span style="color:#ff6b6b">\${e.message}</span
|
|
7768
|
-
}
|
|
7769
|
-
}
|
|
7770
|
-
|
|
7771
|
-
// Using optional chaining/checks because one input was replaced by a button
|
|
7772
|
-
const enterBtn = document.getElementById('enter-btn')
|
|
7773
|
-
if (enterBtn) enterBtn.onclick = () => startMic(null)
|
|
7774
|
-
|
|
7775
|
-
const fileInput = document.getElementById('file-input')
|
|
7776
|
-
if (fileInput) fileInput.onchange = e => e.target.files[0] && loadTrack(e.target.files[0])
|
|
7777
|
-
|
|
7778
|
-
document.getElementById('file-input-2').onchange = e => { if (e.target.files[0]) loadTrack(e.target.files[0]); e.target.value = ''; }
|
|
7779
|
-
document.getElementById('mic-btn-2').onclick = () => startMic(null)
|
|
7780
|
-
playPauseBtn.onclick = togglePlayPause
|
|
7781
|
-
progressContainer.onclick = e => {
|
|
7782
|
-
if (AUDIO.mode !== 'file' || !AUDIO.audioBuffer) return
|
|
7783
|
-
const rect = progressContainer.getBoundingClientRect()
|
|
7784
|
-
AUDIO.seek((e.clientX - rect.left) / rect.width * AUDIO.getDuration())
|
|
7785
|
-
}
|
|
7786
|
-
document.getElementById('screenshot-btn')?.addEventListener('click', () => {
|
|
7787
|
-
composer.render()
|
|
7788
|
-
const a = document.createElement('a')
|
|
7789
|
-
a.download = 'quantum-' + Date.now() + '.png'
|
|
7790
|
-
a.href = renderer.domElement.toDataURL('image/png')
|
|
7791
|
-
a.click()
|
|
7792
|
-
})
|
|
7793
|
-
document.getElementById('fullscreen-btn')?.addEventListener('click', () => {
|
|
7794
|
-
document.fullscreenElement ? document.exitFullscreen() : document.documentElement.requestFullscreen()
|
|
7795
|
-
})
|
|
7796
|
-
|
|
7751
|
+
trackName.textContent = file.name.length > 25 ? file.name.slice(0, 22) + '...' : file.name;
|
|
7752
|
+
updateUI();
|
|
7753
|
+
status.innerHTML = 'SYSTEM: <span>PLAYING</span>';
|
|
7754
|
+
}
|
|
7755
|
+
|
|
7756
|
+
async function startMic(preferredLabel = null) {
|
|
7757
|
+
console.log('Global startMic requested:', preferredLabel);
|
|
7758
|
+
try {
|
|
7759
|
+
activate();
|
|
7760
|
+
await AUDIO.startMic(preferredLabel);
|
|
7761
|
+
trackName.textContent = AUDIO.currentTrackName;
|
|
7762
|
+
updateUI();
|
|
7763
|
+
// status update handled in AUDIO.startMic
|
|
7764
|
+
} catch (e) {
|
|
7765
|
+
console.error("Global startMic failed:", e);
|
|
7766
|
+
const status = document.getElementById('status-bar');
|
|
7767
|
+
status.innerHTML = \`ERROR: <span style="color:#ff6b6b">\${e.message}</span>\`;
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
|
|
7771
|
+
// Using optional chaining/checks because one input was replaced by a button
|
|
7772
|
+
const enterBtn = document.getElementById('enter-btn');
|
|
7773
|
+
if (enterBtn) enterBtn.onclick = () => startMic(null);
|
|
7774
|
+
|
|
7775
|
+
const fileInput = document.getElementById('file-input');
|
|
7776
|
+
if (fileInput) fileInput.onchange = e => e.target.files[0] && loadTrack(e.target.files[0]);
|
|
7777
|
+
|
|
7778
|
+
document.getElementById('file-input-2').onchange = e => { if (e.target.files[0]) loadTrack(e.target.files[0]); e.target.value = ''; };
|
|
7779
|
+
document.getElementById('mic-btn-2').onclick = () => startMic(null);
|
|
7780
|
+
playPauseBtn.onclick = togglePlayPause;
|
|
7781
|
+
progressContainer.onclick = e => {
|
|
7782
|
+
if (AUDIO.mode !== 'file' || !AUDIO.audioBuffer) return;
|
|
7783
|
+
const rect = progressContainer.getBoundingClientRect();
|
|
7784
|
+
AUDIO.seek((e.clientX - rect.left) / rect.width * AUDIO.getDuration());
|
|
7785
|
+
};
|
|
7786
|
+
document.getElementById('screenshot-btn')?.addEventListener('click', () => {
|
|
7787
|
+
composer.render();
|
|
7788
|
+
const a = document.createElement('a');
|
|
7789
|
+
a.download = 'quantum-' + Date.now() + '.png';
|
|
7790
|
+
a.href = renderer.domElement.toDataURL('image/png');
|
|
7791
|
+
a.click();
|
|
7792
|
+
});
|
|
7793
|
+
document.getElementById('fullscreen-btn')?.addEventListener('click', () => {
|
|
7794
|
+
document.fullscreenElement ? document.exitFullscreen() : document.documentElement.requestFullscreen();
|
|
7795
|
+
});
|
|
7796
|
+
|
|
7797
7797
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550 ANIMATION LOOP \u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
7798
7798
|
let renderVisible = true;
|
|
7799
7799
|
let connectionElapsed = 1 / 20;
|
|
@@ -7829,60 +7829,60 @@ void main() {\r
|
|
|
7829
7829
|
const dt = lastRenderedAt < 0 ? 0 : Math.min((now - lastRenderedAt) / 1000, 0.05);
|
|
7830
7830
|
lastRenderedAt = now;
|
|
7831
7831
|
elapsed += dt;
|
|
7832
|
-
|
|
7833
|
-
material.uniforms.uTime.value += dt * STATE.timeScale
|
|
7834
|
-
grainPass.uniforms.uTime.value = elapsed
|
|
7835
|
-
lensFlarePass.uniforms.uTime.value = elapsed
|
|
7836
|
-
|
|
7837
|
-
material.uniforms.uZoomFactor.value = camera.position.length() / CONFIG.defaultZoom
|
|
7838
|
-
material.uniforms.uMousePos.value.lerp(mouseWorldPos, 0.1)
|
|
7839
|
-
material.uniforms.uMouseVelocity.value.lerp(mouseVelocity, 0.08)
|
|
7840
|
-
|
|
7832
|
+
|
|
7833
|
+
material.uniforms.uTime.value += dt * STATE.timeScale;
|
|
7834
|
+
grainPass.uniforms.uTime.value = elapsed;
|
|
7835
|
+
lensFlarePass.uniforms.uTime.value = elapsed;
|
|
7836
|
+
|
|
7837
|
+
material.uniforms.uZoomFactor.value = camera.position.length() / CONFIG.defaultZoom;
|
|
7838
|
+
material.uniforms.uMousePos.value.lerp(mouseWorldPos, 0.1);
|
|
7839
|
+
material.uniforms.uMouseVelocity.value.lerp(mouseVelocity, 0.08);
|
|
7840
|
+
|
|
7841
7841
|
AUDIO.update();
|
|
7842
|
-
|
|
7843
|
-
const s = STATE.sensitivity, lf = 0.16
|
|
7844
|
-
material.uniforms.uSubBass.value = THREE.MathUtils.lerp(material.uniforms.uSubBass.value, AUDIO.gatedBands.subBass * s, lf)
|
|
7845
|
-
material.uniforms.uBass.value = THREE.MathUtils.lerp(material.uniforms.uBass.value, AUDIO.gatedBands.bass * s, lf)
|
|
7846
|
-
material.uniforms.uLowMid.value = THREE.MathUtils.lerp(material.uniforms.uLowMid.value, AUDIO.gatedBands.lowMid * s, lf)
|
|
7847
|
-
material.uniforms.uMid.value = THREE.MathUtils.lerp(material.uniforms.uMid.value, AUDIO.gatedBands.mid * s, lf)
|
|
7848
|
-
material.uniforms.uHighMid.value = THREE.MathUtils.lerp(material.uniforms.uHighMid.value, AUDIO.gatedBands.highMid * s, lf)
|
|
7849
|
-
material.uniforms.uHigh.value = THREE.MathUtils.lerp(material.uniforms.uHigh.value, AUDIO.gatedBands.high * s, lf)
|
|
7850
|
-
material.uniforms.uUltraHigh.value = THREE.MathUtils.lerp(material.uniforms.uUltraHigh.value, AUDIO.gatedBands.ultraHigh * s, lf)
|
|
7851
|
-
material.uniforms.uBeatEnergy.value = THREE.MathUtils.lerp(material.uniforms.uBeatEnergy.value, AUDIO.beatEnergy, 0.22)
|
|
7852
|
-
material.uniforms.uSpectralCentroid.value = THREE.MathUtils.lerp(material.uniforms.uSpectralCentroid.value, AUDIO.spectralCentroid, lf)
|
|
7853
|
-
material.uniforms.uSpectralFlux.value = THREE.MathUtils.lerp(material.uniforms.uSpectralFlux.value, AUDIO.spectralFlux * s, lf)
|
|
7854
|
-
|
|
7855
|
-
// === ENHANCEMENT: Update new uniforms
|
|
7856
|
-
material.uniforms.uOnsetEnergy.value = THREE.MathUtils.lerp(material.uniforms.uOnsetEnergy.value, AUDIO.onsetEnergy, 0.25)
|
|
7857
|
-
material.uniforms.uTerrainMode.value = FIELD_TYPES[STATE.field]?.terrain ? 1.0 : 0.0
|
|
7858
|
-
material.uniforms.uTerrainHeight.value = STATE.waveformTerrainHeight
|
|
7859
|
-
|
|
7860
|
-
// Update secondary particles
|
|
7861
|
-
secondaryPoints.visible = STATE.secondaryEnabled
|
|
7862
|
-
secondaryMaterial.uniforms.uTime.value = material.uniforms.uTime.value
|
|
7863
|
-
secondaryMaterial.uniforms.uSubBass.value = material.uniforms.uSubBass.value
|
|
7864
|
-
secondaryMaterial.uniforms.uBass.value = material.uniforms.uBass.value
|
|
7865
|
-
secondaryMaterial.uniforms.uLowMid.value = material.uniforms.uLowMid.value
|
|
7866
|
-
secondaryMaterial.uniforms.uMid.value = material.uniforms.uMid.value
|
|
7867
|
-
secondaryMaterial.uniforms.uHighMid.value = material.uniforms.uHighMid.value
|
|
7868
|
-
secondaryMaterial.uniforms.uHigh.value = material.uniforms.uHigh.value
|
|
7869
|
-
secondaryMaterial.uniforms.uUltraHigh.value = material.uniforms.uUltraHigh.value
|
|
7870
|
-
secondaryMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7871
|
-
secondaryMaterial.uniforms.uSpectralCentroid.value = material.uniforms.uSpectralCentroid.value
|
|
7872
|
-
secondaryMaterial.uniforms.uSpectralFlux.value = material.uniforms.uSpectralFlux.value
|
|
7873
|
-
secondaryMaterial.uniforms.uOnsetEnergy.value = material.uniforms.uOnsetEnergy.value
|
|
7874
|
-
secondaryMaterial.uniforms.uVortexStrength.value = STATE.vortexStrength
|
|
7875
|
-
|
|
7876
|
-
// Update nebula background
|
|
7877
|
-
nebulaSphere.visible = STATE.nebulaEnabled
|
|
7878
|
-
if (STATE.nebulaEnabled) {
|
|
7879
|
-
nebulaMaterial.uniforms.uTime.value = material.uniforms.uTime.value
|
|
7880
|
-
nebulaMaterial.uniforms.uBass.value = material.uniforms.uBass.value
|
|
7881
|
-
nebulaMaterial.uniforms.uMid.value = material.uniforms.uMid.value
|
|
7882
|
-
nebulaMaterial.uniforms.uHigh.value = material.uniforms.uHigh.value
|
|
7883
|
-
nebulaMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7884
|
-
}
|
|
7885
|
-
|
|
7842
|
+
|
|
7843
|
+
const s = STATE.sensitivity, lf = 0.16;
|
|
7844
|
+
material.uniforms.uSubBass.value = THREE.MathUtils.lerp(material.uniforms.uSubBass.value, AUDIO.gatedBands.subBass * s, lf);
|
|
7845
|
+
material.uniforms.uBass.value = THREE.MathUtils.lerp(material.uniforms.uBass.value, AUDIO.gatedBands.bass * s, lf);
|
|
7846
|
+
material.uniforms.uLowMid.value = THREE.MathUtils.lerp(material.uniforms.uLowMid.value, AUDIO.gatedBands.lowMid * s, lf);
|
|
7847
|
+
material.uniforms.uMid.value = THREE.MathUtils.lerp(material.uniforms.uMid.value, AUDIO.gatedBands.mid * s, lf);
|
|
7848
|
+
material.uniforms.uHighMid.value = THREE.MathUtils.lerp(material.uniforms.uHighMid.value, AUDIO.gatedBands.highMid * s, lf);
|
|
7849
|
+
material.uniforms.uHigh.value = THREE.MathUtils.lerp(material.uniforms.uHigh.value, AUDIO.gatedBands.high * s, lf);
|
|
7850
|
+
material.uniforms.uUltraHigh.value = THREE.MathUtils.lerp(material.uniforms.uUltraHigh.value, AUDIO.gatedBands.ultraHigh * s, lf);
|
|
7851
|
+
material.uniforms.uBeatEnergy.value = THREE.MathUtils.lerp(material.uniforms.uBeatEnergy.value, AUDIO.beatEnergy, 0.22);
|
|
7852
|
+
material.uniforms.uSpectralCentroid.value = THREE.MathUtils.lerp(material.uniforms.uSpectralCentroid.value, AUDIO.spectralCentroid, lf);
|
|
7853
|
+
material.uniforms.uSpectralFlux.value = THREE.MathUtils.lerp(material.uniforms.uSpectralFlux.value, AUDIO.spectralFlux * s, lf);
|
|
7854
|
+
|
|
7855
|
+
// === ENHANCEMENT: Update new uniforms ===
|
|
7856
|
+
material.uniforms.uOnsetEnergy.value = THREE.MathUtils.lerp(material.uniforms.uOnsetEnergy.value, AUDIO.onsetEnergy, 0.25);
|
|
7857
|
+
material.uniforms.uTerrainMode.value = FIELD_TYPES[STATE.field]?.terrain ? 1.0 : 0.0;
|
|
7858
|
+
material.uniforms.uTerrainHeight.value = STATE.waveformTerrainHeight;
|
|
7859
|
+
|
|
7860
|
+
// Update secondary particles
|
|
7861
|
+
secondaryPoints.visible = STATE.secondaryEnabled;
|
|
7862
|
+
secondaryMaterial.uniforms.uTime.value = material.uniforms.uTime.value;
|
|
7863
|
+
secondaryMaterial.uniforms.uSubBass.value = material.uniforms.uSubBass.value;
|
|
7864
|
+
secondaryMaterial.uniforms.uBass.value = material.uniforms.uBass.value;
|
|
7865
|
+
secondaryMaterial.uniforms.uLowMid.value = material.uniforms.uLowMid.value;
|
|
7866
|
+
secondaryMaterial.uniforms.uMid.value = material.uniforms.uMid.value;
|
|
7867
|
+
secondaryMaterial.uniforms.uHighMid.value = material.uniforms.uHighMid.value;
|
|
7868
|
+
secondaryMaterial.uniforms.uHigh.value = material.uniforms.uHigh.value;
|
|
7869
|
+
secondaryMaterial.uniforms.uUltraHigh.value = material.uniforms.uUltraHigh.value;
|
|
7870
|
+
secondaryMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7871
|
+
secondaryMaterial.uniforms.uSpectralCentroid.value = material.uniforms.uSpectralCentroid.value;
|
|
7872
|
+
secondaryMaterial.uniforms.uSpectralFlux.value = material.uniforms.uSpectralFlux.value;
|
|
7873
|
+
secondaryMaterial.uniforms.uOnsetEnergy.value = material.uniforms.uOnsetEnergy.value;
|
|
7874
|
+
secondaryMaterial.uniforms.uVortexStrength.value = STATE.vortexStrength;
|
|
7875
|
+
|
|
7876
|
+
// Update nebula background
|
|
7877
|
+
nebulaSphere.visible = STATE.nebulaEnabled;
|
|
7878
|
+
if (STATE.nebulaEnabled) {
|
|
7879
|
+
nebulaMaterial.uniforms.uTime.value = material.uniforms.uTime.value;
|
|
7880
|
+
nebulaMaterial.uniforms.uBass.value = material.uniforms.uBass.value;
|
|
7881
|
+
nebulaMaterial.uniforms.uMid.value = material.uniforms.uMid.value;
|
|
7882
|
+
nebulaMaterial.uniforms.uHigh.value = material.uniforms.uHigh.value;
|
|
7883
|
+
nebulaMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7884
|
+
}
|
|
7885
|
+
|
|
7886
7886
|
// Update particle connections
|
|
7887
7887
|
connectionMesh.visible = STATE.connectionsEnabled;
|
|
7888
7888
|
if (STATE.connectionsEnabled) {
|
|
@@ -7917,66 +7917,66 @@ void main() {\r
|
|
|
7917
7917
|
idleConnectionElapsed = 2;
|
|
7918
7918
|
lastConnectionState = null;
|
|
7919
7919
|
}
|
|
7920
|
-
|
|
7921
|
-
// Update crawlers
|
|
7922
|
-
crawlerMesh.visible = STATE.crawlersEnabled && STATE.connectionsEnabled && AUDIO.active && (AUDIO.isPlaying || AUDIO.mode === 'mic')
|
|
7923
|
-
if (crawlerMesh.visible) {
|
|
7924
|
-
updateCrawlers(
|
|
7925
|
-
dt
|
|
7926
|
-
material.uniforms.uHighMid.value
|
|
7927
|
-
material.uniforms.uBeatEnergy.value
|
|
7928
|
-
material.uniforms.uSpectralCentroid.value
|
|
7929
|
-
)
|
|
7930
|
-
crawlerMaterial.uniforms.uColor.value.lerp(
|
|
7931
|
-
new THREE.Color().lerpColors(
|
|
7932
|
-
new THREE.Color(FIELD_TYPES[STATE.field]?.c1 || '#ffffff')
|
|
7933
|
-
new THREE.Color(FIELD_TYPES[STATE.field]?.c3 || '#ffffff')
|
|
7934
|
-
material.uniforms.uSpectralCentroid.value
|
|
7935
|
-
), 0.05
|
|
7936
|
-
)
|
|
7937
|
-
crawlerMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7938
|
-
}
|
|
7939
|
-
|
|
7940
|
-
// Update post-processing
|
|
7941
|
-
chromaticPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7942
|
-
godRaysPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7943
|
-
lensFlarePass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7944
|
-
vignettePass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7945
|
-
customDOFPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
// Update trails
|
|
7950
|
-
trailSystem.update(elapsed, {
|
|
7951
|
-
bass: material.uniforms.uBass.value
|
|
7952
|
-
mid: material.uniforms.uMid.value
|
|
7953
|
-
high: material.uniforms.uHigh.value
|
|
7954
|
-
beat: material.uniforms.uBeatEnergy.value
|
|
7955
|
-
}, material.uniforms.uColor1.value)
|
|
7956
|
-
|
|
7957
|
-
// === ENHANCEMENT: Adaptive Quality System
|
|
7958
|
-
QUALITY_SYSTEM.update(dt * 1000)
|
|
7959
|
-
|
|
7960
|
-
// === ENHANCEMENT: Audio-Reactive Camera
|
|
7961
|
-
AUDIO_CAMERA.update(dt * 1000)
|
|
7962
|
-
|
|
7963
|
-
// Camera shake
|
|
7964
|
-
if (STATE.cameraShake > 0 && AUDIO.beatEnergy > 0.1) {
|
|
7965
|
-
const shake = AUDIO.beatEnergy * STATE.cameraShake
|
|
7966
|
-
cameraShakeOffset.set((Math.random() - 0.5) * shake * 0.6, (Math.random() - 0.5) * shake * 0.4, (Math.random() - 0.5) * shake * 0.25)
|
|
7967
|
-
camera.position.copy(baseCameraPos).add(cameraShakeOffset)
|
|
7968
|
-
}
|
|
7969
|
-
|
|
7970
|
-
controls.autoRotateSpeed = 0.35 + material.uniforms.uMid.value * 2.0 + material.uniforms.uBeatEnergy.value * 2.0
|
|
7920
|
+
|
|
7921
|
+
// Update crawlers
|
|
7922
|
+
crawlerMesh.visible = STATE.crawlersEnabled && STATE.connectionsEnabled && AUDIO.active && (AUDIO.isPlaying || AUDIO.mode === 'mic');
|
|
7923
|
+
if (crawlerMesh.visible) {
|
|
7924
|
+
updateCrawlers(
|
|
7925
|
+
dt,
|
|
7926
|
+
material.uniforms.uHighMid.value,
|
|
7927
|
+
material.uniforms.uBeatEnergy.value,
|
|
7928
|
+
material.uniforms.uSpectralCentroid.value
|
|
7929
|
+
);
|
|
7930
|
+
crawlerMaterial.uniforms.uColor.value.lerp(
|
|
7931
|
+
new THREE.Color().lerpColors(
|
|
7932
|
+
new THREE.Color(FIELD_TYPES[STATE.field]?.c1 || '#ffffff'),
|
|
7933
|
+
new THREE.Color(FIELD_TYPES[STATE.field]?.c3 || '#ffffff'),
|
|
7934
|
+
material.uniforms.uSpectralCentroid.value
|
|
7935
|
+
), 0.05
|
|
7936
|
+
);
|
|
7937
|
+
crawlerMaterial.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7938
|
+
}
|
|
7939
|
+
|
|
7940
|
+
// Update post-processing
|
|
7941
|
+
chromaticPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7942
|
+
godRaysPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7943
|
+
lensFlarePass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7944
|
+
vignettePass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7945
|
+
customDOFPass.uniforms.uBeatEnergy.value = material.uniforms.uBeatEnergy.value;
|
|
7946
|
+
|
|
7947
|
+
|
|
7948
|
+
|
|
7949
|
+
// Update trails
|
|
7950
|
+
trailSystem.update(elapsed, {
|
|
7951
|
+
bass: material.uniforms.uBass.value,
|
|
7952
|
+
mid: material.uniforms.uMid.value,
|
|
7953
|
+
high: material.uniforms.uHigh.value,
|
|
7954
|
+
beat: material.uniforms.uBeatEnergy.value
|
|
7955
|
+
}, material.uniforms.uColor1.value);
|
|
7956
|
+
|
|
7957
|
+
// === ENHANCEMENT: Adaptive Quality System ===
|
|
7958
|
+
QUALITY_SYSTEM.update(dt * 1000);
|
|
7959
|
+
|
|
7960
|
+
// === ENHANCEMENT: Audio-Reactive Camera ===
|
|
7961
|
+
AUDIO_CAMERA.update(dt * 1000);
|
|
7962
|
+
|
|
7963
|
+
// Camera shake
|
|
7964
|
+
if (STATE.cameraShake > 0 && AUDIO.beatEnergy > 0.1) {
|
|
7965
|
+
const shake = AUDIO.beatEnergy * STATE.cameraShake;
|
|
7966
|
+
cameraShakeOffset.set((Math.random() - 0.5) * shake * 0.6, (Math.random() - 0.5) * shake * 0.4, (Math.random() - 0.5) * shake * 0.25);
|
|
7967
|
+
camera.position.copy(baseCameraPos).add(cameraShakeOffset);
|
|
7968
|
+
}
|
|
7969
|
+
|
|
7970
|
+
controls.autoRotateSpeed = 0.35 + material.uniforms.uMid.value * 2.0 + material.uniforms.uBeatEnergy.value * 2.0;
|
|
7971
7971
|
bloomPass.strength = STATE.bloom + (AUDIO.gatedBands.bass + AUDIO.gatedBands.mid + AUDIO.beatEnergy) / 6;
|
|
7972
7972
|
bloomPass.enabled = bloomPass.strength > 0;
|
|
7973
7973
|
anamorphicPass.uniforms.uIntensity.value = STATE.anamorphicStretch + AUDIO.gatedBands.bass * 0.4;
|
|
7974
7974
|
anamorphicPass.enabled = anamorphicPass.uniforms.uIntensity.value > 0;
|
|
7975
|
-
|
|
7976
|
-
controls.update()
|
|
7977
|
-
composer.render()
|
|
7978
|
-
}
|
|
7979
|
-
|
|
7975
|
+
|
|
7976
|
+
controls.update();
|
|
7977
|
+
composer.render();
|
|
7978
|
+
}
|
|
7979
|
+
|
|
7980
7980
|
window.addEventListener('resize', () => {
|
|
7981
7981
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
7982
7982
|
camera.updateProjectionMatrix();
|
|
@@ -7984,14 +7984,14 @@ void main() {\r
|
|
|
7984
7984
|
secondaryMaterial.uniforms.uViewportAspect.value = material.uniforms.uViewportAspect.value;
|
|
7985
7985
|
material.uniforms.uViewportHalfHeight.value = Math.tan(camera.fov * Math.PI / 360) * CONFIG.defaultZoom;
|
|
7986
7986
|
secondaryMaterial.uniforms.uViewportHalfHeight.value = material.uniforms.uViewportHalfHeight.value;
|
|
7987
|
-
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
7987
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
7988
7988
|
composer.setSize(window.innerWidth, window.innerHeight);
|
|
7989
7989
|
STATE.pixelRatio = fitPixelRatio();
|
|
7990
7990
|
QUALITY_SYSTEM.applyQuality();
|
|
7991
7991
|
customDOFPass.uniforms.uResolution.value.set(window.innerWidth, window.innerHeight);
|
|
7992
|
-
baseCameraPos.copy(camera.position)
|
|
7993
|
-
})
|
|
7994
|
-
|
|
7992
|
+
baseCameraPos.copy(camera.position);
|
|
7993
|
+
});
|
|
7994
|
+
|
|
7995
7995
|
let pausedFromKit = false;
|
|
7996
7996
|
let requestedTimeScale = STATE.timeScale;
|
|
7997
7997
|
const sideEffects = {
|
|
@@ -8074,13 +8074,13 @@ void main() {\r
|
|
|
8074
8074
|
}
|
|
8075
8075
|
}, 250);
|
|
8076
8076
|
parent.postMessage({ type: 'ad-qf-ready' }, '*');
|
|
8077
|
-
|
|
8077
|
+
|
|
8078
8078
|
QUALITY_SYSTEM.applyQuality();
|
|
8079
8079
|
startFrames();
|
|
8080
|
-
<\/script
|
|
8081
|
-
</body
|
|
8082
|
-
|
|
8083
|
-
</html
|
|
8080
|
+
<\/script>
|
|
8081
|
+
</body>
|
|
8082
|
+
|
|
8083
|
+
</html>
|
|
8084
8084
|
`;
|
|
8085
8085
|
|
|
8086
8086
|
// src/components/artwork/QuantumField/QuantumFieldExperience.tsx
|