<!DOCTYPE html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Gravity</title>
    <link rel="manifest" href="/site.webmanifest">
    <link rel="stylesheet" href="/css/bootstrap.min.css" crossorigin="anonymous">
    <link rel="stylesheet" href="/css/style.css">
    <link rel="preconnect" href="https://fonts.gstatic.com">
    <link href="https://fonts.loli.net/css2?family=Anonymous+Pro:ital,wght@0,400;0,700;1,400;1,700
&family=Noto+Serif+SC:wght@300;400;500;600;700&display=swap" rel="stylesheet">
    <script src="https://apps.bdimg.com/libs/jquery/1.10.2/jquery.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
        }

        .btn {
            width: auto;
            height: auto;
        }

        body {
            background: radial-gradient(rgb(235, 235, 235), floralwhite);
            padding: 0;
        }
    </style>
</head>

<body>
    <a class="btn btn-default" href="/" onmouseenter="alert('单击屏幕添加元素。\n所有元素互相吸引,鼠标亦有引力。');$('.btn').hide()"
        style="position:fixed"><span class="glyphicon glyphicon-question-sign" aria-hidden="true"></span></a>
    <canvas id="canvas" onclick='init(10)' width="500" height="500"></canvas>
</body>
<script>
    const LOST = 0.3;
    var canvas = document.getElementById('canvas');
    var context = canvas.getContext('2d');
    var g = [];
    const MAX_MASS = 3000;
    const PLANET_COUNT = 300;
    const FRAGMENT_COUNT = 100;
    const FRAGMENT_SPEED = 20;
    const MAX_SPEED = 8;
    const G = 0.5;
    var maxLineLength = 45;
    const DEFAULT_MASS = 20;
    const LOCK_TIME = 50;
    const COLOR = '#333';
    const LINE_COLOR = '#2344';
    const MOUSE_MASS = 100;
    var mousepoint = { x: 0, y: 0 };
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    if (!window.AudioContext) {
        console.log('当前浏览器不支持Web Audio API');
    }

    var audioCtx = new AudioContext();

    var arrFrequency = [196.00, 220.00, 246.94, 261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, 587.33, 659.25, 698.46, 783.99, 880.00, 987.77, 1046.50];

    function init(count) {
        if (g.length > 300) return;
        var w = canvas.clientWidth;
        var h = canvas.clientHeight;
        for (var i = 0; i < count; i++) {
            g.push({ x: Math.random() * w, y: Math.random() * h, mass: DEFAULT_MASS, vx: Math.random() * 2 - 1, vy: Math.random() * 2 - 1, lock: 0 });
        }
    }
    function fillRound(cx, cy, r, color) {
        context.beginPath();
        context.fillStyle = color;
        context.arc(cx, cy, Math.sqrt(r) / Math.PI, 0 * Math.PI, 2 * Math.PI, true);
        context.fill();
    }
    function drawLine(x1, y1, x2, y2, color) {
        context.beginPath();
        context.moveTo(x1, y1);
        context.strokeStyle = color;
        context.lineTo(x2, y2);
        context.stroke();
    }
    function collide() {
        for (var i = 0, count = g.length; i < count; i++) {
            for (var i1 = 0; i1 < count; i1++) {
                if (i1 != i && g[i1]) {
                    if (g[i].lock > 0 && g[i1].lock > 0) continue;
                    if ((g[i].lock > 0 || g[i1].lock > 0) && Math.random() > 0.1) continue;
                    var r = Math.sqrt(Math.pow((g[i].x - g[i1].x), 2) + Math.pow((g[i].y - g[i1].y), 2));
                    if (r <= Math.max(Math.sqrt(g[i1].mass) / Math.PI + Math.sqrt(g[i].mass) / Math.PI) && g[i1].mass >= g[i].mass) {
                        var amplitude = Math.min(g[i1].mass, g[i].mass) / 1000 + (g[i1].mass + g[i].mass) / 2000;
                        g[i1].vy = (g[i1].mass * g[i1].vy + g[i].mass * g[i].vy) / (g[i1].mass + g[i].mass);
                        g[i1].vx = (g[i1].mass * g[i1].vx + g[i].mass * g[i].vx) / (g[i1].mass + g[i].mass);
                        g[i1].mass += g[i].mass;
                        g[i] = null;
                        var frequency = arrFrequency[Math.floor(Math.random() * 15)];
                        var oscillator = audioCtx.createOscillator();
                        var gainNode = audioCtx.createGain();
                        oscillator.connect(gainNode);
                        gainNode.connect(audioCtx.destination);
                        oscillator.type = 'sawtooth';
                        oscillator.frequency.value = frequency;
                        gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
                        gainNode.gain.linearRampToValueAtTime(amplitude, audioCtx.currentTime + 0.01);
                        oscillator.start(audioCtx.currentTime);
                        gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1);
                        oscillator.stop(audioCtx.currentTime + 1);
                        break;
                    }
                }
            }
        }
    }
    function explode() {
        for (var i = 0, count = g.length; i < count; i++) {
            if (g[i] && g[i].mass >= MAX_MASS) {
                for (var i1 = 0; i1 < FRAGMENT_COUNT; i1++) {
                    g.push({
                        x: g[i].x + Math.random(),
                        y: g[i].y + Math.random(),
                        mass: g[i].mass / FRAGMENT_COUNT,
                        vx: FRAGMENT_SPEED * (Math.random() * 2 - 1),
                        vy: FRAGMENT_SPEED * (Math.random() * 2 - 1),
                        lock: LOCK_TIME * (Math.random() + 1)
                    });
                }
                var frequency = arrFrequency[Math.floor(Math.random() * 10)];
                var oscillator = audioCtx.createOscillator();
                var gainNode = audioCtx.createGain();
                oscillator.connect(gainNode);
                gainNode.connect(audioCtx.destination);
                oscillator.type = 'sine';
                oscillator.frequency.value = frequency;
                gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
                gainNode.gain.linearRampToValueAtTime(1.3, audioCtx.currentTime + 0.01);
                oscillator.start(audioCtx.currentTime);
                gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 10);
                oscillator.stop(audioCtx.currentTime + 10);
                g[i] = null;
            }
        }
    }

    canvas.onmousemove = function (event) {
        var e = event || window.event;
        mousepoint = { 'x': e.clientX, 'y': e.clientY };
    }

    function step() {
        var w = canvas.clientWidth;
        var h = canvas.clientHeight;
        collide();
        explode();
        for (var i = 0, count = g.length; i < count; i++) {
            if (g[i]) {
                if (g[i].vx === NaN) {
                    g[i].vx = 0
                }
                if (g[i].vy === NaN) {
                    g[i].vy = 0
                }
            }
        }
        g = g.filter(e => e && e.x !== NaN && e.y !== NaN && e.vx !== NaN && e.vy !== NaN && e.mass !== NaN);

        for (var i = 0, count = g.length; i < count; i++) {
            if (g[i].x > w) {
                g[i].x -= (g[i].x - w) * 2;
                g[i].vx *= -(1 - LOST);
            } else if (g[i].x < 0) {
                g[i].x += g[i].x * -2;
                g[i].vx *= -(1 - LOST);
            }
            if (g[i].y > h) {
                g[i].y -= (g[i].y - h) * 2;
                g[i].vy *= -(1 - LOST);
            } else if (g[i].y < 0) {
                g[i].y += g[i].y * -2;
                g[i].vy *= -(1 - LOST);
            }
            fillRound(g[i].x, g[i].y, g[i].mass, COLOR)
            for (var i1 = 0; i1 < count; i1++) {
                if (i1 != i) {
                    var r = Math.sqrt(Math.pow((g[i].x - g[i1].x), 2) + Math.pow((g[i].y - g[i1].y), 2));
                    var f = r != 0 ? G * G * g[i1].mass / Math.pow(r, 2) : 0;
                    var dx = g[i1].x - g[i].x;
                    var dy = g[i1].y - g[i].y;
                    if (g[i].mass > 0) {
                        g[i].vy += f / r * dy;
                        g[i].vx += f / r * dx;
                    }
                    if (i1 > i && r <= maxLineLength) {
                        drawLine(g[i].x, g[i].y, g[i1].x, g[i1].y, LINE_COLOR)
                    }
                }
            }
            //
            var r = Math.max(1, Math.sqrt(Math.pow((g[i].x - mousepoint.x), 2) + Math.pow((g[i].y - mousepoint.y), 2)));
            if (r > maxLineLength / 8) {
                var f = r != 0 ? G * G * MOUSE_MASS / Math.pow(r, 2) : 0;
                var dx = mousepoint.x - g[i].x;
                var dy = mousepoint.y - g[i].y;
                if (g[i].mass > 0) {
                    g[i].vy += f / r * dy;
                    g[i].vx += f / r * dx;
                }
                if (r <= maxLineLength) {
                    drawLine(g[i].x, g[i].y, mousepoint.x, mousepoint.y, LINE_COLOR);
                }
            }
            //
            var speedNow = Math.sqrt(Math.pow(g[i].vx, 2) + Math.pow(g[i].vy, 2))
            if (speedNow > MAX_SPEED) {
                g[i].vx = g[i].vx * MAX_SPEED / speedNow;
                g[i].vy = g[i].vy * MAX_SPEED / speedNow;
            }
            g[i].x += g[i].vx;
            g[i].y += g[i].vy;
            g[i].lock--;
        }
    }
    // init(PLANET_COUNT);
    setInterval(function () {
        var w = document.body.clientWidth;
        var h = document.body.clientHeight;
        maxLineLength = Math.max(30, Math.min(w / 10, h / 10));
        canvas.setAttribute('width', w);
        canvas.setAttribute('height', h);
        canvas.style.width = w + 'px';
        canvas.style.height = h + 'px';
        step();
    }, 30)

</script>