给定角度错误地绘制椭圆线的边缘

时间:2017-01-23 20:35:53

标签: javascript math canvas geometry

对于令人困惑的标题感到抱歉,我不知道如何简洁地描述我的问题。

我正在使用javascript在canvas元素上绘制一个椭圆,我试图弄清楚如何检测鼠标是否在椭圆内部被点击。我试图这样做的方法是将椭圆中心到鼠标的距离与鼠标点击时相同角度的椭圆半径进行比较。这是一张可怕的图片,代表我刚刚说的话,如果它仍然令人困惑: Visualization of click test

显然这不起作用,否则我不会问这个,所以下面是计算半径线(红色)和鼠标线(蓝色)的图片。在这张图片中,鼠标与椭圆中心成45°角点击,我计算出半径线的绘制角度约为34.99°。

Visualization of incorrect radius calculation

以下是计算代码:

//This would be the blue line in the picture above
var mouseToCenterDistance = distanceTo(centerX, centerY, mouseX, mouseY);
var angle = Math.acos((mouseX - centerX) / mouseToCenterDistance);
var radiusPointX = (radiusX * Math.cos(angle)) + centerX;
var radiusPointY = (radiusY * Math.sin(-angle)) + centerY;

//This would be the red line in the picture above
var radius = distanceTo(centerX, centerY, radiusPointX, radiusPointY);

var clickedInside = mouseToCenterDistance <= radius;

我真的不确定为什么这不起作用,我一直盯着这个数学,看起来是正确的。这是正确的吗?画布上有什么东西使它不起作用?请帮忙!

2 个答案:

答案 0 :(得分:1)

如果你有一个形状的椭圆(xx 0 2 / a 2 +(yy 0 2 / b 2 = 1,然后点(x,y)在椭圆内部当且仅当(xx 0 2 / a 2 +(yy 0 2 / b 2 &lt;你可以测试不等式,看看鼠标是否在椭圆内。

为了能够在椭圆的边缘绘制一条线:用atan2获取鼠标的θ(不要使用acos,你会在象限III和放大器中得到错误的结果; IV),使用椭圆的极坐标方程求解 r ,然后转换回直角坐标并绘制。

答案 1 :(得分:0)

椭圆线截距

查找拦截包括解决该点是否在内部。

如果是通过2D上下文的椭圆绘制,则解决方案如下

// defines the ellipse
var cx = 100;  // center
var cy = 100;
var r1 = 20;  // radius 1
var r2 = 100; // radius 2
var ang = 1;  // angle in radians

// rendered with
ctx.beginPath();
ctx.ellipse(cx,cy,r1,r2,ang,0,Math.PI * 2,true)
ctx.stroke()

找到椭圆上与中心到x,y相交的点。为了解决这个问题,我将椭圆规范化,使其成为一个圆形(移动直线,使椭圆在其坐标空间中为圆形)。

var x = 200;
var y = 200;
var ratio = r1 / r2; // need the ratio between the two radius

//  get the vector from the ellipse center to end of line
var dx = x - cx;
var dy = y - cy;

// get the vector that will normalise the ellipse rotation
var vx = Math.cos(-ang);
var vy = Math.sin(-ang);

// use that vector to rotate the line 
var ddx = dx * vx - dy * vy;
var ddy = (dx * vy + dy * vx) * ratio;  // lengthen or shorten dy

// get the angle to the line in normalise circle space.
var c = Math.atan2(ddy,ddx);

// get the vector along the ellipse x axis
var eAx = Math.cos(ang);
var eAy = Math.sin(ang);

// get the intercept of the line and the normalised ellipse
var nx = Math.cos(c) * r1;
var ny = Math.sin(c) * r2;

// rotate the intercept to the ellipse space
var ix = nx * eAx - ny * eAy
var iy = nx * eAy + ny * eAx

// cx,cy to ix ,iy is from the center to the ellipse circumference

该程序可以进行优化,但目前可以解决所提出的问题。

指向内部

然后确定点是否在内部只是比较鼠标和拦截点的距离。

var x = 200; // point to test
var y = 200;

//  get the vector from the ellipse center to point to test
var dx = x - cx;
var dy = y - cy;

// get the vector that will normalise the ellipse rotation
var vx = Math.cos(ang);
var vy = Math.sin(ang);

// use that vector to rotate the line 
var ddx = dx * vx + dy * vy;
var ddy = -dx * vy + dy * vx;

if( 1 >= (ddx * ddx) / (r1 * r1) + (ddy * ddy) / (r2 * r2)){
    // point on circumference or inside ellipse 
}

方法的使用示例。

&#13;
&#13;
    function path(path){
        ctx.beginPath();
        var i = 0;
        ctx.moveTo(path[i][0],path[i++][1]);
        while(i < path.length){
            ctx.lineTo(path[i][0],path[i++][1]);
        }
        if(close){
            ctx.closePath();
        }
        ctx.stroke();
    }
    function strokeCircle(x,y,r){
        ctx.beginPath();
        ctx.moveTo(x + r,y);
        ctx.arc(x,y,r,0,Math.PI * 2);
        ctx.stroke();
    }
    function display() { 
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
        ctx.globalAlpha = 1; // reset alpha
        ctx.clearRect(0, 0, w, h);
        var cx = w/2;
        var cy = h/2;
        var r1 = Math.abs(Math.sin(globalTime/ 4000) * w / 4);
        var r2 = Math.abs(Math.sin(globalTime/ 4300) * h / 4);
        var ang = globalTime / 1500;
    
    
        // find the intercept from ellipse center to mouse on the ellipse 
        var ratio = r1 / r2
    
        var dx = mouse.x - cx;
        var dy = mouse.y - cy;
        var dist = Math.hypot(dx,dy);
        var ex = Math.cos(-ang);
        var ey = Math.sin(-ang);
        var c = Math.atan2((dx * ey + dy * ex) * ratio, dx * ex - dy * ey);
        var nx = Math.cos(c) * r1;
        var ny = Math.sin(c) * r2;
        var ix = nx * ex + ny * ey;
        var iy = -nx * ey + ny * ex;
        var dist = Math.hypot(dx,dy);
        var dist2Inter = Math.hypot(ix,iy);
        
        ctx.strokeStyle = "Blue";
        ctx.lineWidth = 4;
        ctx.beginPath();
        ctx.ellipse(cx,cy,r1,r2,ang,0,Math.PI * 2,true)
        ctx.stroke();
        
        if(dist2Inter > dist){
            ctx.fillStyle = "#7F7";
            ctx.globalAlpha = 0.5;
            ctx.fill();
            ctx.globalAlpha = 1;
            
        }
    
        
        // Display the intercept
        ctx.strokeStyle = "black";
        ctx.lineWidth = 2;
        path([[cx,cy],[mouse.x,mouse.y]])
        ctx.strokeStyle = "red";
        ctx.lineWidth = 5;
        path([[cx,cy],[cx + ix,cy+iy]])
        ctx.strokeStyle = "red";
        ctx.lineWidth = 4;
        strokeCircle(cx + ix, cy + iy, 6)
        ctx.fillStyle = "white";
        ctx.fill();
        ctx.strokeStyle = "red";
        ctx.lineWidth = 4;
        strokeCircle(cx, cy, 6)
        ctx.fillStyle = "white";
        ctx.fill();        
        
        ctx.strokeStyle = "black";
        ctx.lineWidth = 2;
        strokeCircle(mouse.x, mouse.y, 4)
        ctx.fillStyle = "white";
        ctx.fill();   
    }
    
    
    
    /** SimpleFullCanvasMouse.js begin **/
    //==============================================================================
    // Boilerplate code from here down and not related to the answer
    //==============================================================================
    var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
    ;(function(){
        const RESIZE_DEBOUNCE_TIME = 100;
        var  createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
        createCanvas = function () {
            var c,
            cs;
            cs = (c = document.createElement("canvas")).style;
            cs.position = "absolute";
            cs.top = cs.left = "0px";
            cs.zIndex = 1000;
            document.body.appendChild(c);
            return c;
        }
        resizeCanvas = function () {
            if (canvas === undefined) {
                canvas = createCanvas();
            }
            canvas.width = innerWidth;
            canvas.height = innerHeight;
            ctx = canvas.getContext("2d");
            if (typeof setGlobals === "function") {
                setGlobals();
            }
            if (typeof onResize === "function") {
                if(firstRun){
                    onResize();
                    firstRun = false;
                }else{
                    resizeCount += 1;
                    setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
                }
            }
        }
        function debounceResize() {
            resizeCount -= 1;
            if (resizeCount <= 0) {
                onResize();
            }
        }
        setGlobals = function () {
            cw = (w = canvas.width) / 2;
            ch = (h = canvas.height) / 2;
        }
        mouse = (function () {
            function preventDefault(e) {
                e.preventDefault();
            }
            var mouse = {
                x : 0,
                y : 0,
                w : 0,
                alt : false,
                shift : false,
                ctrl : false,
                buttonRaw : 0,
                over : false,
                bm : [1, 2, 4, 6, 5, 3],
                active : false,
                bounds : null,
                crashRecover : null,
                mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
            };
            var m = mouse;
            function mouseMove(e) {
                var t = e.type;
                m.bounds = m.element.getBoundingClientRect();
                m.x = e.pageX - m.bounds.left;
                m.y = e.pageY - m.bounds.top;
                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 (m.callbacks) {
                    m.callbacks.forEach(c => c(e));
                }
                if ((m.buttonRaw & 2) && m.crashRecover !== null) {
                    if (typeof m.crashRecover === "function") {
                        setTimeout(m.crashRecover, 0);
                    }
                }
                e.preventDefault();
            }
            m.addCallback = function (callback) {
                if (typeof callback === "function") {
                    if (m.callbacks === undefined) {
                        m.callbacks = [callback];
                    } else {
                        m.callbacks.push(callback);
                    }
                }
            }
            m.start = function (element) {
                if (m.element !== undefined) {
                    m.removeMouse();
                }
                m.element = element === undefined ? document : element;
                m.mouseEvents.forEach(n => {
                    m.element.addEventListener(n, mouseMove);
                });
                m.element.addEventListener("contextmenu", preventDefault, false);
                m.active = true;
            }
            m.remove = function () {
                if (m.element !== undefined) {
                    m.mouseEvents.forEach(n => {
                        m.element.removeEventListener(n, mouseMove);
                    });
                    m.element.removeEventListener("contextmenu", preventDefault);
                    m.element = m.callbacks = undefined;
                    m.active = false;
                }
            }
            return mouse;
        })();
        // Clean up. Used where the IDE is on the same page.
        var done = function () {
            window.removeEventListener("resize", resizeCanvas)
            mouse.remove();
            document.body.removeChild(canvas);
            canvas = ctx = mouse = undefined;
        }
        function update(timer) { // Main update loop
            if(ctx === undefined){ return; }
            globalTime = timer;
            display(); // call demo code
            requestAnimationFrame(update);
        }
        setTimeout(function(){
            resizeCanvas();
            mouse.start(canvas, true);
            //mouse.crashRecover = done;
            window.addEventListener("resize", resizeCanvas);
            requestAnimationFrame(update);
        },0);
    })();
    /** SimpleFullCanvasMouse.js end **/
&#13;
&#13;
&#13;

相关问题