优化弧以绘制仅在画布上可见的扇区

时间:2015-12-20 02:35:52

标签: javascript html5 canvas

我有一个相当大的弧,其笔划使用rgba个值。它有50%的alpha值,因此,它对我的​​浏览器的cpu配置文件造成了很大的打击。

所以我想找到一种方法来优化它,以便在画布中绘制弧的位置,它只会从一个角度绘制到另一个角度,在屏幕上可见。

我遇到的困难是找出正确的角度范围。

这是一个直观的例子:

enter image description here

顶部图像是画布实际上即使您没有看到它的图像,而底部图像是我想要做的以节省处理时间。

我创建了一个JSFiddle,您可以在其中单击并拖动圆,但是,这两个角度当前是固定的: 的 https://jsfiddle.net/44tawd81/

这是绘图代码:

var canvas = document.getElementById('canvas');
var ctx    = canvas.getContext('2d');
    ctx.strokeStyle = 'red';

var radius = 50;
var pos    = {
              'x': canvas.width - 20,
              'y': canvas.height /2
             };


function draw(){
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.beginPath();

    ctx.arc(pos.x,pos.y,radius,0,2*Math.PI); //need to adjust angle range
    ctx.stroke();

    requestAnimationFrame(draw);
}

draw();

根据画布中的位置和大小找到要绘制的角度范围的最简单方法是什么?

2 个答案:

答案 0 :(得分:3)

剪切圆圈

这是如何将圆形剪裁到与x和y轴对齐的矩形区域。

要剪切圆圈,我会搜索圆圈与剪裁区域相交的点列表。从一侧开始,我按顺时针方向添加剪辑点,因为它们被找到。当测试所有4个边时,我绘制了连接所找到的点的弧段。

要查找某个点是否拦截了裁剪边缘,您会找到圆心与该边缘之间的距离。知道了半径和距离,您可以完成直角三角形以找到截距的坐标。

左边缘

// define the clip edge and circle
var clipLeftX = 100;
var radius = 200;
var centerX = 200;
var centerY = 200; 

var dist = centerX - clipLeftX;
if(dist > radius) { // circle inside }
if(dist < -radius) {// circle completely outside}
// we now know the circle is clipped 

现在计算两个剪辑点的圆圈y的距离

// the right triangle with hypotenuse and one side know can be solved with
var clipDist = Math.sqrt(radius * radius - dist * dist);

所以圆圈截取剪切线的点

var clipPointY1 = centerY - clipDist;
var clipPointY2 = centerY + clipDist;

通过测试两个点位于左侧顶部或底部的内侧或外侧,您可以通过测试左侧顶部和底部的两个点来计算出来。

你最终会得到0,1或2个裁剪点。

因为弧需要绘制角度,所以需要计算从圆心到找到点的角度。您已经拥有了所需的所有信息

// dist is the x distance from the clip
var angle = Math.acos(radius/dist); // for left and right side

困难的部分是确保剪切点的所有角度都是正确的顺序。这是一个有点旗帜的旗帜,以确保弧线的顺序正确。

在检查所有四个边之后,您将得到0,2,4,6或8个剪切点,表示各种修剪弧的起点和终点。然后简单地迭代弧段并渲染它们。

&#13;
&#13;
// Helper functions are not part of the answer
var canvas;
var ctx;
var mouse;
var resize = function(){
    /** fullScreenCanvas.js begin **/
    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;
    })();
    ctx = canvas.ctx;
    /** fullScreenCanvas.js end **/
    /** MouseFull.js begin **/
    var canvasMouseCallBack = undefined;  // if needed
    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 **/
resize();
// Answer starts here
var w = canvas.width;
var h = canvas.height;
var d = Math.sqrt(w * w + h * h); // diagnal size
var cirLWidth = d * (1 / 100);
var rectCol = "black";
var rectLWidth = d * (1 / 100);
const PI2 = Math.PI * 2;
const D45_LEN = 0.70710678;
var angles = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // declared outside to stop GC


// create a clipArea
function rectArea(x, y, x1, y1) {
    return {
        left : x,
        top : y,
        width : x1 - x,
        height : y1 - y
    };
}
// create a arc
function arc(x, y, radius, start, end, col) {
    return {
        x : x,
        y : y,
        r : radius,
        s : start,
        e : end,
        c : col
    };
}

// draws an arc
function drawArc(arc, dir) {
    ctx.strokeStyle = arc.c;
    ctx.lineWidth = cirLWidth;
    ctx.beginPath();
    ctx.arc(arc.x, arc.y, arc.r, arc.s, arc.e, dir);
    ctx.stroke();
}

// draws a clip area
function drawRect(r) {
    ctx.strokeStyle = rectCol;
    ctx.lineWidth = rectLWidth;
    ctx.strokeRect(r.left, r.top, r.width, r.height);

}



// clip and draw an arc
// arc is the arc to clip
// clip is the clip area
function clipArc(arc, clip){
    var count, distTop, distLeft, distBot, distRight, dist, swap, radSq, bot,right;

   // cir1 is used to draw the clipped circle
   cir1.x = arc.x;
   cir1.y = arc.y;   

   count = 0;  // number of clip points found;

   bot = clip.top + clip.height;  // no point adding these two over and over
   right = clip.left + clip.width;

   // get distance from all edges
   distTop = arc.y - clip.top;
   distBot = bot - arc.y;
   distLeft = arc.x - clip.left;
   distRight = right - arc.x;
   
   radSq = arc.r * arc.r; // get the radius squared
   
   // check if outside
   if(Math.min(distTop, distBot, distRight, distLeft) < -arc.r){
       return; // nothing to see so go home
   }
   // check inside
   if(Math.min(distTop, distBot, distRight, distLeft) > arc.r){
       drawArc(cir1);  
       return;
   }
   swap = true;
   if(distLeft < arc.r){
       // get the distance up and down to clip
       dist = Math.sqrt(radSq - distLeft * distLeft);
       // check the point is in the clip area
       if(dist + arc.y < bot && arc.y + dist > clip.top){
           // get the angel
           angles[count] = Math.acos(distLeft / -arc.r);
           count += 1;
       }
       if(arc.y - dist < bot && arc.y - dist > clip.top){
           angles[count] = PI2 - Math.acos(distLeft / -arc.r); // get the angle
           if(count === 0){  // if first point then set direction swap
               swap = false;
           }
           count += 1;
       }
   }
   if(distTop < arc.r){
       dist = Math.sqrt(radSq - distTop * distTop);
       if(arc.x - dist < right && arc.x - dist > clip.left){
           angles[count] = Math.PI + Math.asin(distTop / arc.r);
           count += 1;
       }
       if(arc.x+dist < right && arc.x+dist > clip.left){
           angles[count] = PI2-Math.asin(distTop/arc.r);
           if(count === 0){
               swap = false;
           }
           count += 1;
       }
   }
   if(distRight < arc.r){
       dist = Math.sqrt(radSq - distRight * distRight);
       if(arc.y - dist < bot && arc.y - dist > clip.top){
           angles[count] = PI2 - Math.acos(distRight / arc.r);
           count += 1;
       }
       if(dist + arc.y < bot && arc.y + dist > clip.top){
           angles[count] = Math.acos(distRight / arc.r);
           if(count === 0){
               swap = false;
           }
           count += 1;
       }
   }
   if(distBot < arc.r){
       dist = Math.sqrt(radSq - distBot * distBot);
       if(arc.x + dist < right && arc.x + dist > clip.left){
           angles[count] = Math.asin(distBot / arc.r);
           count += 1;
       }
       if(arc.x - dist < right && arc.x - dist > clip.left){
           angles[count] =  Math.PI + Math.asin(distBot / -arc.r);
           if(count === 0){
               swap = false;
           }
           count += 1;
       }
   }
   //  now draw all the arc segments
   if(count === 0){
       return;
   }
   if(count === 2){
        cir1.s = angles[0];
        cir1.e = angles[1];
        drawArc(cir1,swap);
   }else
   if(count === 4){
        if(swap){
            cir1.s = angles[1];
            cir1.e = angles[2];
            drawArc(cir1);
            cir1.s = angles[3];
            cir1.e = angles[0];
            drawArc(cir1);
        }else{
            cir1.s = angles[2];
            cir1.e = angles[3];
            drawArc(cir1);
            cir1.s = angles[0];
            cir1.e = angles[1];
            drawArc(cir1);
        }
   }else
   if(count === 6){
        cir1.s = angles[1];
        cir1.e = angles[2];
        drawArc(cir1);
        cir1.s = angles[3];
        cir1.e = angles[4];
        drawArc(cir1);
        cir1.s = angles[5];
        cir1.e = angles[0];
        drawArc(cir1);
        
   }else
   if(count === 8){
        cir1.s = angles[1];
        cir1.e = angles[2];
        drawArc(cir1);
        cir1.s = angles[3];
        cir1.e = angles[4];
        drawArc(cir1);
        cir1.s = angles[5];
        cir1.e = angles[6];
        drawArc(cir1);
        cir1.s = angles[7];
        cir1.e = angles[0];
        drawArc(cir1);
        
   }
   return;
}


var rect = rectArea(50, 50, w - 50, h - 50);
var circle = arc(w * (1 / 2), h * (1 / 2), w * (1 / 5), 0, Math.PI * 2, "#AAA");
var cir1 = arc(w * (1 / 2), h * (1 / 2), w * (1 / 5), 0, Math.PI * 2, "red");
var counter = 0;
var countStep = 0.03;
function update() {
    var x, y;
    ctx.clearRect(0, 0, w, h);
    circle.x = mouse.x;
    circle.y = mouse.y;
    drawArc(circle, "#888"); // draw unclipped arc
    x = Math.cos(counter * 0.1);
    y = Math.sin(counter * 0.3);
    rect.top = h / 2 - Math.abs(y * (h * 0.4)) - 5;
    rect.left = w / 2 - Math.abs(x * (w * 0.4)) - 5;
    rect.width = Math.abs(x * w * 0.8) + 10;
    rect.height = Math.abs(y * h * 0.8) + 10;
    cir1.col = "RED";  
    clipArc(circle, rect); // draw the clipped arc
    
    drawRect(rect); // draw the clip area. To find out why this method
                    // sucks move this to before drawing the clipped arc.
    requestAnimationFrame(update);
    if(mouse.buttonRaw !== 1){
        counter += countStep;
    }
    ctx.font = Math.floor(w * (1 / 50)) + "px verdana";
    ctx.fillStyle = "white";
    ctx.strokeStyle = "black";
    ctx.lineWidth = Math.ceil(w * (1 / 300));
    ctx.textAlign = "center";
    ctx.lineJoin = "round";
    ctx.strokeText("Left click and hold to pause", w/ 2, w * (1 / 40));
    ctx.fillText("Left click and hold to pause", w/ 2, w * (1 / 40));
}

update();
window.addEventListener("resize",function(){
   resize();
   w = canvas.width;
   h = canvas.height;
   rect = rectArea(50, 50, w - 50, h - 50);
   circle = arc(w * (1 / 2), h * (1 / 2), w * (1 / 5), 0, Math.PI * 2, "#AAA");
   cir1 = arc(w * (1 / 2), h * (1 / 2), w * (1 / 5), 0, Math.PI * 2, "red");
});
&#13;
&#13;
&#13;

剪辑圆圈的最快捷方式。

这是我能在代码中设法做到的最快。有一些优化空间,但在agorithum中没那么多。

最佳解决方案当然是使用画布2D上下文API clip()方法。

ctx.save();
ctx.rect(10,10,200,200); // define the clip region
ctx.clip();  // activate the clip.

//draw your circles

ctx.restore(); // remove the clip.

这比上面显示的方法要快得多,除非你真的需要知道剪辑区域内部或外部的剪辑点和弧段,否则应该使用它。

答案 1 :(得分:0)

根据圆形位置,画布位置,圆形大小和画布大小找到要绘制的角度:

  1. 确定圆和画布的交点
  2. 计算交叉点发生的圆上的点
  3. 然后你有一个等腰三角形。

    您可以使用余弦公式计算角度。

    C ^ 2 = A ^ 2 + B ^ 2-2abcos(α) a和b是与角度α相邻的边,它们是中心r的半径。 c是两点P1和P2之间的距离。所以我们得到:

    | P1-P2 | ^ 2 = 2R,2-2R ^ 2COS(α)

    2R ^ 2- | P1-P2 | ^ 2 / 2R2 = COS(α)

    α= COS-1(2R,2- | P1-P2 | ^ 2 / 2R ^ 2)