编程语言
首页 > 编程语言> > javascript – 旋转图像和像素碰撞检测

javascript – 旋转图像和像素碰撞检测

作者:互联网

我在这个plunker中有这个游戏.

当剑不旋转时,一切正常(你可以通过取消注释线221并注释掉222-223).当它们像上面的弹药一样旋转时,碰撞效果不佳.

我想这是因为“getImageData”会记住旧图像,但我认为重复计算是一件昂贵的事情.

有没有更好的方法来旋转我的图像并使其工作?或者我是否必须重新计算他​​们的像素图?

罪魁祸首:

for (var i = 0; i < monsters.length; i++) {
    var monster = monsters[i];
    if (monster.ready) {
        if (imageCompletelyOutsideCanvas(monster, monster.monsterImage)) {
            monster.remove = true;
        }
        //else {
        //ctx.drawImage(monster.monsterImage, monster.x, monster.y);
        drawRotatedImage(monster.monsterImage, monster.x, monster.y, monster);
        monster.rotateCounter += 0.05;
        //}
    }
}

解决方法:

几何解决方案

要通过更快的几何解决方案来做到这一点.

最简单的解决方案是具有圆形交叉算法的线段.

线段.

一条线以各种方式描述开始和结束.在这种情况下,我们将使用开始和结束坐标.

var line = {
    x1 : ?,
    y1 : ?,
    x2 : ?,
    y2 : ?,
}

圆圈由其位置和半径描述

var circle = {
   x : ?,
   y : ?,
   r : ?,
}

圆线段相交

以下描述了我如何测试圆线段碰撞.我不知道是否有更好的方法(最有可能)但是这对我有好处并且可靠,但需要注意的是线段必须具有长度并且圆圈必须具有区域.如果您无法保证这一点,那么您必须在代码中添加检查以确保不会被零除.

因此,为了测试一条线是否截取圆,我们首先找出线上最近点的距离(注意线的大小是无限的,而线段的长度是开始和结束)

// a quick convertion of vars to make it easier to read.
var x1 = line.x1;
var y1 = line.y1;
var x2 = line.x2;
var y2 = line.y2;

var cx = circle.x;
var cy = circle.y;
var r = circle.r;

如果发生碰撞,测试结果将是真实的.

var result; // the result of the test

将线转换为矢量.

var vx = x2 - x1;  // convert line to vector
var vy = y2 - y1;
var d2 = (vx * vx + vy * vy);  // get the length squared

获取距离线上近点圆的单位距离.单位距离是从0到1(包括)的数字,表示沿点矢量的距离.如果该值小于0,则该点在向量之前,如果大于1,则该点超过结束.

我通过记忆知道这一点而忘记了这个概念.它是线矢量的点积和从线段开始到圆心的矢量除以线矢量长度的平方.

// dot product of two vectors is v1.x * v2.x + v1.y * v2.y over v1 length squared 
u =  ((cx - x1) * vx + (cy - y1) * vy) / d2;

现在使用单位位置通过向线段起始位置添加线矢量乘以单位距离来获得最接近圆的线上的点的实际坐标.

 // get the closest point
var  xx = x1 + vx * u;
var  yy = y1 + vy * u;

现在我们在线上有一个点,我们使用毕达哥拉斯平方根计算两边平方的距离.

// get the distance from the circle center
var d =  Math.hypot(xx - cx, yy - cy);    

现在,如果线(不是线段)与圆相交,则距离将等于或小于圆半径.否则就是没有拦截.

if(d > r){ //is the distance greater than the radius
    result = false;  // no intercept
} else { // else we need some more calculations

为了确定线段是否拦截了圆圈,我们需要找到线圈已经越过的圆周上的两个点.我们有半径和圆与线的距离.由于距离线的距离始终是直角,我们有一个直角三角形,其中hypot是半径,一边是找到的距离.

弄清楚三角形缺失的长度. UPDATE在“更新”下的答案底部看到代码的改进版本,它使用单位长度而不是规范化线向量.

// ld for line distance is the square root of the hyp subtract side squared
var ld = Math.sqrt(r * r - d * d);

现在将该距离添加到我们在xx,yy线上找到的点上,通过将线矢量除以其长度来规范化线矢量(使线矢量长一个单位),然后将其乘以上面找到的距离

var len = Math.sqrt(d2); // get the line vector length
var nx = (vx / len) * ld;      
var ny = (vy / len) * ld;      

有些人可能会看到我可以使用单位长度并跳过一些计算.是的,但我可能会为重写演示而烦恼,所以会保留原样

现在通过向最接近圆的线上的点添加和减去新矢量来获得拦截点

ix1 = xx + nx; // the point furthest alone the line 
iy1 = xx + ny;
ix2 = xx - nx; // the point in the other direction
iy2 = xx - ny;

既然我们有这两个点,我们可以计算出它们是否在线段中,但计算它们在原始线矢量上的单位距离,使用点积除以平方距离.

    var u1 =  ((ix1 - x1) * vx + (iy1 - y1) * vy) / d2;
    var u2 =  ((ix2 - x1) * vx + (iy1 - y1) * vy) / d2; 

现在进行一些简单的测试,看看这些点的单位是否在线段上

    if(u1 < 0){  // is the forward intercept befor the line segment start
        result = false;  // no intercept            
    }else
    if(u2 > 1){ // is the rear intercept after the line end
        result = false;  // no intercept            
    } else {
        // though the line segment may not have intercepted the circle
        // circumference if we have got to here it must meet the conditions
        // of touching some part of the circle.
        result = true;
    }
}

演示

这里一直是一个演示逻辑的演示.圆圈以鼠标为中心.如果圆圈接触它们,有一些测试线会变红.它还将显示圆周与线交叉的点.如果在线段中该点将为红色,如果在外部,则该点将为绿色.这些点可用于添加效果或不添加效果

我今天很懒,所以这直接来自我的图书馆.注意我会在有机会时发布改进的数学.

更新

我通过使用单位长度来计算圆周相交来改进算法,从而消除了大量代码.我也将它添加到了演示中.

从距线的距离小于圆半径的点

            // get the unit distance to the intercepts
            var ld = Math.sqrt(r * r - d * d) / Math.sqrt(d2);

            // get that points unit distance along the line
            var u1 =  u + ld; 
            var u2 =  u - ld; 
            if(u1 < 0){  // is the forward intercept befor the line
                result = false;  // no intercept
            }else
            if(u2 > 1){  // is the backward intercept past the end of the line
                result = false;  // no intercept
            }else{
                result = true;
            }
        }
var demo = function(){
    
    // the function described in the answer with extra stuff for the demo
    // at the bottom you will find the function being used to test circle intercepts.
    
    
    /** GeomDependancies.js begin **/
        
    // for speeding up calculations.
    // usage may vary from descriptions. See function for any special usage notes
    var data = {
        x:0,   // coordinate
        y:0,
        x1:0,   // 2nd coordinate if needed
        y1:0,
        u:0,   // unit length
        i:0,   // index
        d:0,   // distance
        d2:0,  // distance squared
        l:0,   // length
        nx:0,  // normal vector
        ny:0,
        result:false, // boolean result
    }
    // make sure hypot is suported
    if(typeof Math.hypot !== "function"){
        Math.hypot = function(x, y){ return Math.sqrt(x * x + y * y);};
    }
    /** GeomDependancies.js end **/
    
    /** LineSegCircleIntercept.js begin **/
    // use data properties
    // result  // intercept bool for intercept
    // x, y    // forward intercept point on line ** 
    // x1, y1  // backward intercept point on line
    // u       // unit distance of intercept mid point
    // d2      // line seg length squared
    // d       // distance of closest point on line from circle
    // i       // bit 0 on for forward intercept on segment 
    //         // bit 1 on for backward intercept
    // ** x = null id intercept points dont exist
    var lineSegCircleIntercept = function(ret, x1, y1, x2, y2, cx, cy, r){
    var vx, vy, u, u1, u2, d, ld, len, xx, yy;
        vx = x2 - x1;  // convert line to vector
        vy = y2 - y1;
        ret.d2 = (vx * vx + vy * vy);
        
        // get the unit distance of the near point on the line
        ret.u = u =  ((cx - x1) * vx + (cy - y1) * vy) / ret.d2;
        xx = x1 + vx * u; // get the closest point
        yy = y1 + vy * u;
        
        // get the distance from the circle center
        ret.d = d =  Math.hypot(xx - cx, yy - cy);    
        if(d <= r){ // line is inside circle
            // get the distance to the two intercept points
            ld = Math.sqrt(r * r - d * d) / Math.sqrt(ret.d2);

            // get that points unit distance along the line
            u1 =  u + ld; 
            if(u1 < 0){  // is the forward intercept befor the line
                ret.result = false;  // no intercept
                return ret;
            }
            u2 =  u - ld; 
            if(u2 > 1){  // is the backward intercept past the end of the line
                ret.result = false;  // no intercept
                return ret;
            }
            ret.i = 0;
            if(u1 <= 1){
                ret.i += 1;
                // get the forward point line intercepts the circle
                ret.x = x1 + vx * u1;  
                ret.y = y1 + vy * u1;
            }else{
                ret.x = x2;
                ret.y = y2;
                
            }
            if(u2 >= 0){
                ret.x1 = x1 + vx * u2;  
                ret.y1 = y1 + vy * u2;
                ret.i += 2;
            }else{
                ret.x1 = x1;
                ret.y1 = y1;
            }
            
            // tough the points of intercept may not be on the line seg
            // the closest point to the must be on the line segment
            ret.result = true;
            return ret;
            
        }
        ret.x = null; // flag that no intercept found at all;
        ret.result = false;  // no intercept
        return ret;
            
    }
    /** LineSegCircleIntercept.js end **/
    

    // mouse and canvas functions for this demo.

    /** fullScreenCanvas.js begin **/
    var canvas = (function(){
        var canvas = document.getElementById("canv");
        if(canvas !== null){
            document.body.removeChild(canvas);
        }
        // creates a blank image with 2d context
        canvas = document.createElement("canvas"); 
        canvas.id = "canv";    
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight; 
        canvas.style.position = "absolute";
        canvas.style.top = "0px";
        canvas.style.left = "0px";
        canvas.style.zIndex = 1000;
        canvas.ctx = canvas.getContext("2d"); 
        document.body.appendChild(canvas);
        return canvas;
    })();
    var ctx = canvas.ctx;
    
    /** fullScreenCanvas.js end **/
    /** MouseFull.js begin **/
    
    var canvasMouseCallBack = undefined;  // if needed
    var mouse = (function(){
        var mouse = {
            x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
            interfaceId : 0, buttonLastRaw : 0,  buttonRaw : 0,
            over : false,  // mouse is over the element
            bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
            getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
            startMouse:undefined,
        };
        function mouseMove(e) {
            var t = e.type, m = mouse;
            m.x = e.offsetX; m.y = e.offsetY;
            if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
            m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
            if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
            } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
            } else if (t === "mouseover") { m.over = true;
            } else if (t === "mousewheel") { m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") { m.w = -e.detail;}
            if (canvasMouseCallBack) { canvasMouseCallBack(m.x, m.y); }
            e.preventDefault();
        }
        function startMouse(element){
            if(element === undefined){
                element = document;
            }
            "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",").forEach(
            function(n){element.addEventListener(n, mouseMove);});
            element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
        }
        mouse.mouseStart = startMouse;
        return mouse;
    })();
    if(typeof canvas === "undefined"){
        mouse.mouseStart(canvas);
    }else{
        mouse.mouseStart();
    }
    /** MouseFull.js end **/
    
    // helper function
    function drawCircle(ctx,x,y,r,col,col1,lWidth){
        if(col1){
            ctx.lineWidth = lWidth;
            ctx.strokeStyle = col1;
        }
        if(col){
            ctx.fillStyle = col;
        }
        
        ctx.beginPath();
        ctx.arc( x, y, r, 0, Math.PI*2);
        if(col){
            ctx.fill();
        }
        if(col1){
            ctx.stroke();
        }
    }
    
    // helper function
    function drawLine(ctx,x1,y1,x2,y2,col,lWidth){
        ctx.lineWidth = lWidth;
        ctx.strokeStyle = col;
        ctx.beginPath();
        ctx.moveTo(x1,y1);
        ctx.lineTo(x2,y2);
        ctx.stroke();
    }
    var h = canvas.height;
    var w = canvas.width;
    var unit = Math.ceil(Math.sqrt(Math.hypot(w, h)) / 32);
    const U80 = unit * 80;
    const U60 = unit * 60;
    const U40 = unit * 40;
    const U10 = unit * 10;
    var lines = [
        {x1 : U80, y1 : U80, x2 : w /2, y2 : h - U80},
        {x1 : w - U80, y1 : U80, x2 : w /2, y2 : h - U80},
        {x1 : w / 2 - U10, y1 : h / 2 - U40, x2 : w /2, y2 : h/2 + U10 * 2},
        {x1 : w / 2 + U10, y1 : h / 2 - U40, x2 : w /2, y2 : h/2 + U10 * 2},
    ];
    
    function update(){
        var i, l;
        ctx.clearRect(0, 0, w, h);
        
        drawCircle(ctx, mouse.x, mouse.y, U60, undefined, "black", unit * 3);
        drawCircle(ctx, mouse.x, mouse.y, U60, undefined, "yellow", unit * 2);
        for(i = 0; i < lines.length; i ++){
            l = lines[i]
            drawLine(ctx, l.x1, l.y1, l.x2, l.y2, "black" , unit * 3)
            drawLine(ctx, l.x1, l.y1, l.x2, l.y2, "yellow" , unit * 2)
            
            // test the lineSegment circle
            data = lineSegCircleIntercept(data,  l.x1, l.y1, l.x2, l.y2, mouse.x, mouse.y, U60);
            // if there is a result display the result
            if(data.result){
                drawLine(ctx, l.x1, l.y1, l.x2, l.y2, "red" , unit * 2)
                if((data.i & 1) === 1){
                    drawCircle(ctx, data.x, data.y, unit * 4, "white", "red", unit );
                }else{
                    drawCircle(ctx, data.x, data.y, unit * 2, "white", "green", unit );
                }
                if((data.i & 2) === 2){
                    drawCircle(ctx, data.x1, data.y1, unit * 4, "white", "red", unit );
                }else{
                    drawCircle(ctx, data.x1, data.y1, unit * 2, "white", "green", unit );
                }
            }
        }
        requestAnimationFrame(update);
    }
    
    update();
}
// resize if needed by just starting again
window.addEventListener("resize",demo);

// start the demo
demo();

标签:javascript,html5,canvas,collision-detection,game-physics
来源: https://codeday.me/bug/20190724/1526174.html