File size: 6,738 Bytes
ef78915
 
 
 
a0ef328
ef78915
a0ef328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef78915
 
 
 
 
a0ef328
ef78915
a0ef328
78637a0
a0ef328
 
 
ef78915
a0ef328
ef78915
 
78637a0
ef78915
78637a0
ef78915
18ce6eb
 
 
 
a0ef328
 
 
18ce6eb
a0ef328
 
 
 
 
 
 
 
 
a06b807
 
 
a0ef328
 
 
90b9d4f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ef78915
 
 
a0ef328
ef78915
a0ef328
ef78915
a0ef328
 
 
ef78915
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
export class DisplacementFilter {
    constructor(rendererCanvas, videoElement) {
        this.canvas = rendererCanvas;
        this.video = videoElement;
        this.enabled = true;
        this.time = 0;

        // Create SVG turbulence filter for real per-pixel displacement
        this._createTurbulenceSVG();
    }

    _createTurbulenceSVG() {
        // SVG filter = web equivalent of After Effects "Turbulent Displace"
        var svgNS = 'http://www.w3.org/2000/svg';
        this.svg = document.createElementNS(svgNS, 'svg');
        this.svg.setAttribute('width', '0');
        this.svg.setAttribute('height', '0');
        this.svg.style.position = 'absolute';

        var defs = document.createElementNS(svgNS, 'defs');

        // Filter for the webcam video
        var filter = document.createElementNS(svgNS, 'filter');
        filter.setAttribute('id', 'turbulence-displace');
        filter.setAttribute('x', '-10%');
        filter.setAttribute('y', '-10%');
        filter.setAttribute('width', '120%');
        filter.setAttribute('height', '120%');

        // feTurbulence = generates Perlin noise pattern
        // "Evolution" = seed, animated over time
        // "Amount" = scale parameter in feDisplacementMap
        this.turbulence = document.createElementNS(svgNS, 'feTurbulence');
        this.turbulence.setAttribute('type', 'fractalNoise');
        this.turbulence.setAttribute('baseFrequency', '0.008 0.006'); // Size (lower = larger swirls)
        this.turbulence.setAttribute('numOctaves', '3');
        this.turbulence.setAttribute('seed', '0'); // Will animate this = "Evolution"
        this.turbulence.setAttribute('result', 'noise');

        // feDisplacementMap = displaces pixels based on the noise
        this.displacement = document.createElementNS(svgNS, 'feDisplacementMap');
        this.displacement.setAttribute('in', 'SourceGraphic');
        this.displacement.setAttribute('in2', 'noise');
        this.displacement.setAttribute('scale', '12'); // Amount — subtle but visible
        this.displacement.setAttribute('xChannelSelector', 'R');
        this.displacement.setAttribute('yChannelSelector', 'G');

        filter.appendChild(this.turbulence);
        filter.appendChild(this.displacement);
        defs.appendChild(filter);

        // Second filter for the Three.js canvas (slightly different params)
        var filter2 = document.createElementNS(svgNS, 'filter');
        filter2.setAttribute('id', 'turbulence-canvas');
        filter2.setAttribute('x', '-10%');
        filter2.setAttribute('y', '-10%');
        filter2.setAttribute('width', '120%');
        filter2.setAttribute('height', '120%');

        this.turbulence2 = document.createElementNS(svgNS, 'feTurbulence');
        this.turbulence2.setAttribute('type', 'fractalNoise');
        this.turbulence2.setAttribute('baseFrequency', '0.005 0.004');
        this.turbulence2.setAttribute('numOctaves', '2');
        this.turbulence2.setAttribute('seed', '42');
        this.turbulence2.setAttribute('result', 'noise');

        this.displacement2 = document.createElementNS(svgNS, 'feDisplacementMap');
        this.displacement2.setAttribute('in', 'SourceGraphic');
        this.displacement2.setAttribute('in2', 'noise');
        this.displacement2.setAttribute('scale', '8');
        this.displacement2.setAttribute('xChannelSelector', 'R');
        this.displacement2.setAttribute('yChannelSelector', 'G');

        filter2.appendChild(this.turbulence2);
        filter2.appendChild(this.displacement2);
        defs.appendChild(filter2);

        this.svg.appendChild(defs);
        document.body.appendChild(this.svg);

        // Apply filters
        this.video.style.filter += ' url(#turbulence-displace)';
        this.canvas.style.filter = 'url(#turbulence-canvas)';
    }

    toggle() {
        this.enabled = !this.enabled;
        if (!this.enabled) {
            this.canvas.style.filter = '';
            this.canvas.style.transform = '';
            this.video.style.filter = '';
            this.video.style.transform = 'scaleX(-1)';
        } else {
            this.video.style.filter = 'url(#turbulence-displace)';
            this.canvas.style.filter = 'url(#turbulence-canvas)';
        }
        console.log('Turbulence displacement: ' + (this.enabled ? 'ON' : 'OFF'));
    }

    update(amplitude) {
        if (!this.enabled) return;
        this.time += 0.016;

        // Throttle SVG DOM updates to ~10fps to prevent layout thrashing
        this._frameCount = (this._frameCount || 0) + 1;
        if (this._frameCount % 6 !== 0) return;

        var amp = Math.max(0.15, amplitude || 0);

        // Animate "Evolution" — change the noise seed over time
        var seed = Math.floor(this.time * 1.5) % 1000;
        this.turbulence.setAttribute('seed', String(seed));
        this.turbulence2.setAttribute('seed', String(seed + 500));

        // Animate "Offset Turbulence" — shift the noise pattern's base frequency
        // Subtle variation creates the "breathing" organic feel
        var freqX = 0.008 + Math.sin(this.time * 0.3) * 0.003;
        var freqY = 0.006 + Math.cos(this.time * 0.25) * 0.002;
        this.turbulence.setAttribute('baseFrequency', freqX + ' ' + freqY);

        // Audio-reactive Amount — MAX STRENGTH displacement
        var videoScale = 20 + amp * 30; // 20-50 range (always visible, big on audio)
        var canvasScale = 14 + amp * 22; // 14-36 range
        this.displacement.setAttribute('scale', String(videoScale));
        this.displacement2.setAttribute('scale', String(canvasScale));

        // Smoke/wave distortion — sinusoidal waves rippling the canvas
        var breathScale = 1.0 + 0.01 * Math.sin(this.time * 0.7);
        var waveSkewX = Math.sin(this.time * 0.4) * 0.8;
        var waveSkewY = Math.cos(this.time * 0.35) * 0.5;
        var waveX = Math.sin(this.time * 0.6) * 3;
        var waveY = Math.cos(this.time * 0.5) * 2;

        this.canvas.style.transform =
            'scale(' + breathScale + ') ' +
            'skew(' + waveSkewX + 'deg, ' + waveSkewY + 'deg) ' +
            'translate(' + waveX + 'px, ' + waveY + 'px)';
        this.video.style.transform =
            'scaleX(-1) scale(' + breathScale + ') ' +
            'skew(' + (-waveSkewX * 0.6) + 'deg, ' + (-waveSkewY * 0.6) + 'deg) ' +
            'translate(' + (-waveX * 0.5) + 'px, ' + (-waveY * 0.5) + 'px)';
    }

    dispose() {
        this.canvas.style.filter = '';
        this.canvas.style.transform = '';
        this.video.style.filter = '';
        this.video.style.transform = 'scaleX(-1)';
        if (this.svg && this.svg.parentNode) {
            this.svg.parentNode.removeChild(this.svg);
        }
    }
}