<!DOCTYPE html>

<html lang="zh-TW">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>攝像機粒子交互界面</title>

    <style>

        body {

            margin: 0;

            padding: 0;

            overflow: hidden;

            background-color: #000;

        }

        canvas {

            display: block;

        }

        #videoElement {

            display: none; /* 隱藏原始視頻,只顯示 Canvas 粒子 */

        }

    </style>

</head>

<body>


<video id="videoElement" autoplay playsinline></video>

<canvas id="particleCanvas"></canvas>


<script>

    const canvas = document.getElementById('particleCanvas');

    const ctx = canvas.getContext('2d', { willReadFrequently: true });

    const video = document.getElementById('videoElement');


    canvas.width = window.innerWidth;

    canvas.height = window.innerHeight;


    let particlesArray = [];

    let mappedImage = [];


    // 鼠標交互設定

    const mouse = {

        x: null,

        y: null,

        radius: 100 // 鼠標排斥範圍

    };


    window.addEventListener('mousemove', function(event){

        mouse.x = event.x;

        mouse.y = event.y;

    });


    // 1. 調用攝像頭

    navigator.mediaDevices.getUserMedia({ video: true })

        .then(function(stream) {

            video.srcObject = stream;

            video.play();

        })

        .catch(function(err) {

            console.error("無法調用攝像頭: ", err);

            alert("請允許攝像頭權限以查看粒子效果!");

        });


    // 粒子類別設計

    class Particle {

        constructor(x, y, color, size) {

            this.x = x;

            this.y = y;

            this.baseX = x;

            this.baseY = y;

            this.color = color;

            this.size = size;

            this.density = (Math.random() * 30) + 1; // 控制粒子彈回的速度

        }


        draw() {

            ctx.beginPath();

            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);

            ctx.fillStyle = this.color;

            ctx.fill();

        }


        update() {

            // 計算鼠標與粒子的距離

            let dx = mouse.x - this.x;

            let dy = mouse.y - this.y;

            let distance = Math.sqrt(dx * dx + dy * dy);

            

            // 物理交互:鼠標靠近時推開粒子

            let forceDirectionX = dx / distance;

            let forceDirectionY = dy / distance;

            let maxDistance = mouse.radius;

            let force = (maxDistance - distance) / maxDistance;

            let directionX = forceDirectionX * force * this.density;

            let directionY = forceDirectionY * force * this.density;


            if (distance < mouse.radius) {

                this.x -= directionX;

                this.y -= directionY;

            } else {

                // 粒子回歸原位

                if (this.x !== this.baseX) {

                    let dx = this.x - this.baseX;

                    this.x -= dx / 10;

                }

                if (this.y !== this.baseY) {

                    let dy = this.y - this.baseY;

                    this.y -= dy / 10;

                }

            }

        }

    }


    // 2. 初始化粒子系統

    function init() {

        particlesArray = [];

        

        // 為了效能,我們不需要處理每一個像素,而是每隔一段距離採樣一次(分辨率)

        const resolution = 10; 

        

        // 將視頻按比例縮放並畫到 canvas 上以獲取數據

        let width = canvas.width;

        let height = canvas.height;

        ctx.drawImage(video, 0, 0, width, height);

        let pixels = ctx.getImageData(0, 0, width, height);

        

        ctx.clearRect(0, 0, width, height);


        // 3. 遍歷像素數據並生成粒子

        for (let y = 0; y < height; y += resolution) {

            for (let x = 0; x < width; x += resolution) {

                let index = (y * width + x) * 4;

                let r = pixels.data[index];

                let g = pixels.data[index + 1];

                let b = pixels.data[index + 2];

                let a = pixels.data[index + 3];


                if (a > 0) { // 確保像素不是透明的

                    let color = `rgb(${r},${g},${b})`;

                    // 可以根據亮度來調整粒子大小

                    let brightness = Math.sqrt(r * r * 0.299 + g * g * 0.587 + b * b * 0.114);

                    let size = (brightness / 255) * 4; // 亮度越高,粒子越大

                    

                    particlesArray.push(new Particle(x, y, color, size));

                }

            }

        }

    }


    // 渲染循環

    function animate() {

        // 定期重新採樣視頻幀(這會帶來動態的攝像頭更新)

        if (video.readyState === 4) { 

            init(); 

        }


        // 使用帶有透明度的黑色背景,產生拖影效果 (Trail effect)

        ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';

        ctx.fillRect(0, 0, canvas.width, canvas.height);


        for (let i = 0; i < particlesArray.length; i++) {

            particlesArray[i].update();

            particlesArray[i].draw();

        }

        requestAnimationFrame(animate);

    }


    // 確保視頻準備好後再開始動畫

    video.addEventListener('play', () => {

        animate();

    });


    // 處理視窗大小調整

    window.addEventListener('resize', function(){

        canvas.width = window.innerWidth;

        canvas.height = window.innerHeight;

    });

</script>


</body>

</html>