start, stop, clear

打ち出したボールが何かに衝突して跳ね返るみたいなコードを探す。
究極かもしれないと思えるサンプルコードを発見!

endo blog: HTML5 – Canvas 円同士の衝突アニメーション

しかし例のごとく setInterval で clearInterval していない。
無限に増殖する円が延々衝突し続けるという究極の CPU 殺しでもあった。
一生懸命自己解析していたら CPU ファンが凄い勢いで回り初めて焦った。
もしファンの無いスマホなんかで見ていたら…

しかたがないので停止や初期化のボタンを付けてコピペ。
TimerClass をインスタンス化したら start(func), stop() メソッドで簡単に使えるようにしてみた。
それをスマートフォン用にレイアウト、のつもり。
肝心のアルゴリズムはコピペです、すんません。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>球の衝突(負荷強)</title>
<!-- for Smart Phone -->
<meta name="viewport" content="
    width=device-width,
    initial-scale=1.0,
    minimum-scale=1.0,
    maximum-scale=1.0,
    user-scalable=no" />
<style>
body {
    margin: 0;
    overflow: hidden;
    -webkit-text-size-adjust: 100%;
}
img {
    margin: 0;
    padding: 0;
    overflow: hidden;
}
#ID_CANVAS {
    background-color: #FFFFFF;
}
</style>
<script type="text/javascript"><!--

    var canvas = null;
    var context = null;
    var timer = null;
    var cw = 0;
    var ch = 0;

    var g = 0.1;
    const INTERVAL = 10;
    var circles = [];
    var colorList = ['0','1','2','3','4','5','6','7',
                     '8','9','a','b','c','d','e','f'];

    var TimerClass = function() {
        this.count = 0;
        this.id = -1;
        this.start = function(func) {
            if (this.id == -1) {
                this.id = setInterval(func, INTERVAL);
            }
        }
        this.stop = function() {
            if (this.id > -1) {
                clearInterval(this.id);
                this.id = -1;
            }
        }
    }

    var onTimer = function() {
        context.clearRect(0, 0, cw, ch);
        if (timer.count>100 || timer.count==0) {
            timer.count = 0;
            circles.push(new Circle());
            circles[circles.length-1].init(circles.length % 2);
        }
        for (var i=0; i<circles.length; i++) {
            for(var j=i+1; j<circles.length; j++) {
                collisionCircleCircle(circles[i], circles[j]);
            }
        }
        for (var i=0; i<circles.length; i++) {
            circles[i].move();
            circles[i].collisionWall();
            circles[i].view();
        }
        timer.count++;
    }

    function Circle(){
        this.h = 0.9;
        this.x = 0;
        this.y = 100;
        this.vx = 0;
        this.vy = 0;
        this.r = 0;
        this.color = '#';

        this.init = function(vec) {
            this.vx = Math.random() * 9 + 1;
            this.r = Math.random() * 25 + 5;
            if (vec) {
                this.x = cw + 100;
                this.vx *= -1;
            } else {
                this.x = -100;
            }
            this.color += colorList[Math.floor(Math.random() * 3) + 3];
            this.color += colorList[Math.floor(Math.random() * 5) + 5];
            this.color += colorList[Math.floor(Math.random() * 7) + 9];
        }
        this.move = function() {
            this.x += this.vx;
            this.y += this.vy;
            this.vy += g;
        }
     
        this.view = function() {
            context.beginPath();
            context.fillStyle = this.color;
            context.arc(this.x, this.y, this.r, 0, 360, false);
            context.fill();
        }
     
        this.collisionWall = function() {
            if (this.x+this.r > cw && this.vx > 0 || this.x-this.r < 0 && this.vx < 0) {
                this.vx *= -this.h;
            } else if (this.y+this.r > ch && this.vy > 0 || this.y-this.r < 0 && this.vy < 0) {
                this.vy *= -this.h;
            }
        }
    }

    function collisionCircleCircle(a,b){
        if( (b.x-a.x)*(b.x-a.x)+(b.y-a.y)*(b.y-a.y) < (a.r+b.r)*(a.r+b.r) ){
            var vx = a.x-b.x;
            var vy = a.y-b.y;
            var len = Math.sqrt(vx*vx+vy*vy);
            var d = a.r+b.r-len;
            if (len>0)
                len =1/len;

            vx *= len;
            vy *= len;

            d /= 2.0;
            a.x += vx*d;
            a.y += vy*d;
            b.x -= vx*d;
            b.y -= vy*d;

            var t;
            t = -(vx*a.vx+vy*a.vy)/(vx*vx+vy*vy);
            var arx = a.vx+vx*t;
            var ary = a.vy+vy*t;

            t = -(-vy*a.vx+vx*a.vy)/(vy*vy+vx*vx);
            var amx = a.vx-vy*t;
            var amy = a.vy+vx*t;

            t = -(vx*b.vx+vy*b.vy)/(vx*vx+vy*vy);
            var brx = b.vx+vx*t;
            var bry = b.vy+vy*t;

            t = -(-vy*b.vx+vx*b.vy)/(vy*vy+vx*vx);
            var bmx = b.vx-vy*t;
            var bmy = b.vy+vx*t;

            var e = 0.8;
            var adx = (a.r*amx+b.r*bmx+bmx*e*b.r-amx*e*b.r)/(a.r+b.r);
            var bdx = -e*(bmx-amx)+adx;
            var ady = (a.r*amy+b.r*bmy+bmy*e*b.r-amy*e*b.r)/(a.r+b.r);
            var bdy = -e*(bmy-amy)+ady;

            a.vx = adx+arx;
            a.vy = ady+ary;
            b.vx = bdx+brx;
            b.vy = bdy+bry;
        }
    }
    var init = function() {
        if (window.TouchEvent) {
            cw = window.innerWidth;
            ch = window.innerHeight - 60;
            timer = new TimerClass();
            // Create Canvas and Context
            canvas = document.getElementById("ID_CANVAS");
            canvas.width = cw;
            canvas.height = ch;
            context = canvas.getContext("2d");
            // Create ToolBar
            var back = document.getElementById("ID_BACK");
            back.style.top = ch + "px";
            back.style.left = 0 + "px";
            back.addEventListener("touchend", function() {
                document.location = ".";
            });
            var start = document.getElementById("ID_START");
            start.style.top = ch + "px";
            start.style.left = 80 + "px";
            start.addEventListener("touchend", function() {
                timer.start(onTimer);
            });
            var stop = document.getElementById("ID_STOP");
            stop.style.top = ch + "px";
            stop.style.left = 160 + "px";
            stop.addEventListener("touchend", function() {
                timer.stop();
            });
            var clear = document.getElementById("ID_CLEAR");
            clear.style.top = ch + "px";
            clear.style.left = 240 + "px";
            clear.addEventListener("touchend", function() {
                timer.stop();
                circles = [];
                context.clearRect(0, 0, cw, ch);
            });
        }
    }
    //-->
</script>

</head>
<body onLoad="init()">

<canvas id="ID_CANVAS"></canvas>
<img id="ID_BACK" src="button1/back.png" style="position:absolute">
<img id="ID_START" src="button1/start.png" style="position:absolute">
<img id="ID_STOP" src="button1/stop.png" style="position:absolute">
<img id="ID_CLEAR" src="button1/clear.png" style="position:absolute">

</body>
</html>

球の衝突(負荷強)

clash

はっきりいって何がどうなってこうなるかよくワカンネェ!
重力で落下するみたいな処理も多分入っているよな、どこだか解らないけど。
何日かチマチマ弄っていればそのうち少しは理解できる、といいな。
今回は覚書とタイマーの簡単な利用方法、ということで。

それにしても神様、私にもっと絵心というものを下さい。
ボタンが我ながらダサすぎる、今後の課題はイラストかも。