簡體   English   中英

錯誤地將線繪制到給定角度的橢圓邊緣

[英]Incorrectly drawing line to edge of ellipse given angle

抱歉,標題令人困惑,我不知道如何簡潔地描述我的問題。

我正在使用 javascript 在canvas元素上繪制一個橢圓,我試圖弄清楚如何檢測鼠標是否在橢圓內被點擊。 我嘗試這樣做的方法是將橢圓中心到鼠標的距離與鼠標單擊相同角度的橢圓半徑進行比較。 如果它仍然令人困惑,這是一張糟糕的圖片,代表了我剛剛所說的內容:點擊測試的可視化

顯然這不起作用,否則我不會問這個,所以下面是計算出的半徑線(紅色)和鼠標線(藍色)的圖片。 在這張圖片中,鼠標與橢圓中心成 45° 角,我計算出半徑線是以大約 34.99° 角繪制的。

錯誤半徑計算的可視化

下面是計算代碼:

//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;

我真的不知道為什么這不起作用,我一直在盯着這個數學,它似乎是正確的。 它是否正確,並且在畫布上繪圖有什么問題使它不起作用? 請幫忙!

如果你有一個 (xx 0 ) 2 /a 2 + (yy 0 ) 2 /b 2 = 1 形式的橢圓,那么點 (x, y) 在橢圓內當且僅當 (xx 0 ) 2 / a 2 + (yy 0 ) 2 /b 2 < 1. 您可以測試該不等式以查看鼠標是否在橢圓內。

為了能夠在橢圓的邊緣畫一條線:用atan2得到鼠標的 theta(不要使用acos ,你會在第三和第四象限中得到不正確的結果),使用橢圓的極坐標方程來求解r ,然后轉換回直角坐標並繪制。

橢圓線截距

找到截距包括解決點是否在里面。

如果是通過 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 
}

方法的使用示例。

 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(); mx = e.pageX - m.bounds.left; my = 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") { mw = e.wheelDelta; } else if (t === "DOMMouseScroll") { mw = -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 **/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM