圆形碰撞算法 封装类

昨天群里有朋友问到, 顺便发下. FTP连不上, 我直接贴代码得了.

FTP弄好了, Demo地址 http://www.vilic.info/demo/collide/

沙发同学Lin.x爆料地下的代码部分半角被替换成了全角, 会报错. 如果需要代码的话, 打开Demo自己另存为吧.

groinup.js

/*
圆形碰撞算法
www.vilic.info
by Vilic Vane
©2010 Groinup
*/
var groinup = new function () {

    var array = this.array = new function () {
        this.clear = function (aimArray) {
            aimArray.length = 0;
        };

        this.remove = function (item, arr) {
            var i;
            for (i in arr)
                if (arr[i] == item)
                    arr.splice(i, 1);
        };

        this.addUnique = function (item, arr) {
            var found = false;
            var i;
            for (i in arr)
                if (arr[i] == item) {
                    found = true;
                    break;
                }
            if (!found) arr.push(item);
        };
    };

    var object = this.object = new function () {
        this.copy = function (obj) {
            var newObj = {};
            var i;
            for (i in obj)
                newObj[i] = obj[i];
            return newObj;
        };
    } ();

    var math = this.math = new function () {
        var sqr = this.sqr = function (n) { return n * n; };
        var sqrt = this.sqrt = function (n) { return Math.sqrt(n); };

        var modulus = this.modulus = function (a1, a2) {
            var x1 = a1.x;
            var y1 = a1.y;
            var x2 = a2.x;
            var y2 = a2.y;

            return (x1 * x2 + y1 * y2) / sqrt((sqr(x1) + sqr(y1)) * (sqr(x2) + sqr(y2)));
        };

        var point = this.point = new function () {
            var getDistance = this.getDistance = function (pos1, pos2) {
                return sqrt(sqr(pos1.x – pos2.x) + sqr(pos1.y – pos2.y));
            };
        } ();

        var circle = this.circle = new function () {
            this.getDistance = function (pos1, pos2, r1, r2) { return point.getDistance(pos1, pos2) – r1 – r2; };
        };

        var Position = this.Position = function (x, y) {
            this.x = x;
            this.y = y;
        };

    } ();

    var physics = this.physics = new function () {
        var sqr = math.sqr;
        var sqrt = math.sqrt;

        var getMovingDistance = this.getMovingDistance = function (v0, a, t) {
            return v0 * t + 0.5 * a * math.sqr(t);
        };

        var Velocity = this.Velocity = function (vx, vy) {
            this.x = vx;
            this.y = vy;
        };

        var Acceleration = this.Acceleration = function (ax, ay) {
            this.x = ax;
            this.y = ay;
        };
    } ();

    var collide = this.collide = new function () {

        //函数引用
        var sqr = math.sqr;
        var sqrt = math.sqrt;
        var modulus = math.modulus;

        var Circle = this.Circle = function (m, r, p, v, a, goFunc) {
            var _this = this;
            this.m = m;
            this.r = r;
            this.p = p;
            this.v = v;

            this.a = a || new physics.Acceleration(0, 0);
            a = this.a;

            var acTime = 0;
            this.resetAcTime = function () { acTime = 0; };
            this.onCollide = function () { };

            this.collideCallBack = function (p, c) {
                _this.onCollide(p, c);
            };

            this.onVelocityReverse = function () { };

            this.onSpeedUp = function () { };

            this._isOnSurface = false;

            if (goFunc)
                this.go = function (t) {
                    acTime += t;
                    if (goFunc(acTime) == false) go(t);
                };
            else
                this.go = go;

            function go(t) {
                var vx = v.x + a.x * t;
                var vy = v.y + a.y * t;

                var sqrVxy0 = sqr(v.x) + sqr(v.y);
                var sqrVxy = sqr(vx) + sqr(vy);

                p.x += (vx + v.x) / 2 * t;
                p.y += (vy + v.y) / 2 * t;

                v.x = vx;
                v.y = vy;

                if (sqrVxy >= sqrVxy0) _this.onSpeedUp();
            }
        };

        var Surface = this.Surface = function () {
            var kmax = sqrt(Number.MAX_VALUE) – 1;
            var circles = [];
            var isSearching = false;

            this.add = function (circle) {
                circle._isOnSurface = true;
                circles.push(circle);
            };

            this.remove = function (circle) {
                circle._isOnSurface = false;
                if (!isSearching) array.remove(circle, circles);
            };

            this.go = function (time, step) {
                collideSearch(time, step);
            };

            function collideSearch(time, step) {
                isSearching = true;

                var sAmt = Math.floor(time / step);
                for (var i = 0; i < sAmt; i++)
                    search();

                isSearching = false;

                function search() {
                    var i;

                    var removeList = [];

                    for (i in circles)
                        circles[i].go(step);

                    var getDis = math.circle.getDistance;
                    var len = circles.length;
                    for (var j = 0; j < len; j++) {
                        var c1 = circles[j];

                        //判断元素是否已经移除
                        if (!c1._isOnSurface) {
                            removeList.push(c1);
                            continue;
                        }
                        for (var k = j + 1; k < len; k++) {
                            var c2 = circles[k];

                            //判断元素是否已经移除
                            if (!c1._isOnSurface) {
                                removeList.push(c1);
                                break;
                            }
                            else
                                if (!c2._isOnSurface) {
                                    array.addUnique(c2, removeList);
                                    continue;
                                }

                            var d = getDis(c1.p, c2.p, c1.r, c2.r);
                            if (getDis(c1.p, c2.p, c1.r, c2.r) <= 0)
                                dealCollide(c1, c2);
                        }
                    }

                    //移除在搜索过程中被搁置的需要移除的项
                    for (i in removeList)
                        array.remove(removeList[i], circles);
                }

                function dealCollide(c1, c2) {
                    //获取相关数据
                    var p1 = c1.p, p2 = c2.p;
                    var v1 = c1.v, v2 = c2.v;
                    var m1 = c1.m, m2 = c2.m;
                    var x1 = p1.x, x2 = p2.x, y1 = p1.y, y2 = p2.y;
                    var vx1 = v1.x, vx2 = v2.x, vy1 = v1.y, vy2 = v2.y;

                    var a11 = { x: vx1, y: vy1 };
                    var a12 = { x: x2 – x1, y: y2 – y1 };
                    var a21 = { x: vx2, y: vy2 };
                    var a22 = { x: x1 – x2, y: y1 – y2 };

                    //计算两个圆圆心连线的斜率
                    var k;
                    var dx = x1 – x2;

                    //判断斜率是否存在
                    if (dx) k = (y1 – y2) / dx;
                    //如果不存在,则去尽可能大的值
                    else k = kmax;

                    //**速度分解

                    //*第一个圆
                    //圆心连线方向的分速度
                    var vox1 = apart(vx1, vy1);
                    var voy1 = vox1 * k;

                    //与连线方向垂直的分速度
                    var vpx1 = vx1 – vox1;
                    var vpy1 = vy1 – voy1;

                    //*第二个圆
                    //圆心连线方向的分速度
                    var vox2 = apart(vx2, vy2);
                    var voy2 = vox2 * k;

                    //与连线方向垂直的分速度
                    var vpx2 = vx2 – vox2;
                    var vpy2 = vy2 – voy2;

                    //碰撞后与连线方向垂直的分速度不变,连线方向的分速度变化
                    var vox1a = calculateV(m1, m2, vox1, vox2);
                    var vox2a = calculateV(m2, m1, vox2, vox1);

                    var voy1a = calculateV(m1, m2, voy1, voy2);
                    var voy2a = calculateV(m2, m1, voy2, voy1);

                    //重新复合速度
                    v1.x = vox1a + vpx1;
                    v1.y = voy1a + vpy1;
                    v2.x = vox2a + vpx2;
                    v2.y = voy2a + vpy2;

                    //告知动量变化量
                    var dp1 = sqrt(sqr(vox1a – vox1) + sqr(voy1a – voy1)) * m1;
                    var dp2 = sqrt(sqr(vox2a – vox2) + sqr(voy2a – voy2)) * m2;

                    c1.collideCallBack(dp1, c2);
                    c2.collideCallBack(dp2, c1);

                    //分解出与圆心连线方向的分速度的x方向速度
                    function apart(vx, vy) {
                        return (k * vy + vx) / (sqr(k) + 1);
                    }

                    //根据动量和能量守恒计算速度变化
                    function calculateV(m1, m2, v1, v2) {
                        return ((m1 – m2) * v1 + 2 * m2 * v2) / (m1 + m2);
                    }
                }
            }
        };
    } ();
}

index.html

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN” “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd“>

<html xmlns=”http://www.w3.org/1999/xhtml“>
<head>
    <title>碰撞类</title>
    <style type=”text/css”>
        #c1 { position: absolute; margin: -10px; width: 20px; height: 20px; background-color: Red; -webkit-border-radius: 10px; -moz-border-radius: 10px; -o-border-radius: 10px; }
        #c2 { position: absolute; margin: -15px; width: 30px; height: 30px; background-color: Blue; -webkit-border-radius: 15px; -moz-border-radius: 15px; -o-border-radius: 15px; }
        #c3 { position: absolute; margin: -6px; width: 12px; height: 12px; background-color: Green; -webkit-border-radius: 6px; -moz-border-radius: 6px; -o-border-radius: 6px; }
    </style>
    <script src=”groinup.js” type=”text/javascript”></script>
</head>
<body>
    <div id=”c1″></div>
    <div id=”c2″></div>
    <div id=”c3″></div>
</body>
<script type=”text/javascript”>
    //使用举例

    //获取元素
    var c1 = document.getElementById(‘c1’);
    var c2 = document.getElementById(‘c2’);
    var c3 = document.getElementById(‘c3’);

    //容器
    var surface = new groinup.collide.Surface();

    //定义碰撞的圆形, 参数依次为 质量, 半径, 位置, 速度, 加速度, 搜索过程中的回调函数
    var ci1 = new groinup.collide.Circle(10, 10, new groinup.math.Position(210, 200), new groinup.physics.Velocity(40, 10), 0, null);
    var ci2 = new groinup.collide.Circle(5, 15, new groinup.math.Position(400, 220), new groinup.physics.Velocity(-50, -10), 0, null);
    var ci3 = new groinup.collide.Circle(2, 6, new groinup.math.Position(100, 320), new groinup.physics.Velocity(60, -10), 0, null);

    //向容器中添加圆形
    surface.add(ci1);
    surface.add(ci2);
    surface.add(ci3);

    //控制运动
    var i = 0;
    var interval = setInterval(setPos, 30);

    function setPos() {
        if (i++ > 100) clearInterval(interval);

        //移动, 参数分别为移动的时间和单次搜索的时间(单次搜索的时间越短, 运动越精确)
        surface.go(0.05, 0.005);

        //设置位置
        sP(c1, ci1.p.x, ci1.p.y);
        sP(c2, ci2.p.x, ci2.p.y);
        sP(c3, ci3.p.x, ci3.p.y);

        function sP(o, x, y) {
            o.style.left = x + ‘px’;
            o.style.top = y + ‘px’;
        }
    }

</script>
</html>