[回答への追加: 楕円上の最近点を近似する方法]
実用性のために完璧さを犠牲にしても構わないと思っているなら…</p>
目標点に「近い」楕円点を計算する方法を次に示します。
メソッド:
- 目標点が楕円のどの象限にあるかを決定します。
- その象限の開始ラジアン角と終了ラジアン角を計算します。
- その楕円象限に沿ってポイントを計算します (「楕円を歩く」)。
- 計算された楕円点ごとに、ターゲット ポイントまでの距離を計算します。
- ターゲットまでの距離が最短の楕円点を保存します。
短所:
- 結果は概算です。
- これは、数学的に完全な計算よりもエレガントではありません。力ずくの方法を使用します。
- (ただし、効率的な力ずくの方法)。
長所:
- 近似結果はかなり良好です。
- パフォーマンスはかなり良いです。
- 計算ははるかに簡単です。
- 計算は、数学的に完全な計算よりも (おそらく) 高速です。
- (約 20 の三角関数の計算と加算/減算のコストがかかります)
- より高い精度が必要な場合は、1 つの変数を変更するだけです
- (もちろん、精度が高いほど、より多くの計算が必要になります)
パフォーマンスに関する注意:
- 楕円上のすべての「歩行点」を事前に計算して、パフォーマンスをさらに向上させることができます。
このメソッドのコードは次のとおりです。
// calc a point on the ellipse that is "near-ish" the target point
// uses "brute force"
function getEllipsePt(targetPtX,targetPtY){
// calculate which ellipse quadrant the targetPt is in
var q;
if(targetPtX>cx){
q=(targetPtY>cy)?0:3;
}else{
q=(targetPtY>cy)?1:2;
}
// calc beginning and ending radian angles to check
var r1=q*halfPI;
var r2=(q+1)*halfPI;
var dr=halfPI/steps;
var minLengthSquared=200000000;
var minX,minY;
// walk the ellipse quadrant and find a near-point
for(var r=r1;r<r2;r+=dr){
// get a point on the ellipse at radian angle == r
var ellipseX=cx+radiusX*Math.cos(r);
var ellipseY=cy+radiusY*Math.sin(r);
// calc distance from ellipsePt to targetPt
var dx=targetPtX-ellipseX;
var dy=targetPtY-ellipseY;
var lengthSquared=dx*dx+dy*dy;
// if new length is shortest, save this ellipse point
if(lengthSquared<minLengthSquared){
minX=ellipseX;
minY=ellipseY;
minLengthSquared=lengthSquared;
}
}
return({x:minX,y:minY});
}
ここにコードとフィドルがあります: http://jsfiddle.net/m1erickson/UDBkV/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; padding:20px; }
#wrapper{
position:relative;
width:300px;
height:300px;
}
#canvas{
position:absolute; top:0px; left:0px;
border:1px solid green;
width:100%;
height:100%;
}
#canvas2{
position:absolute; top:0px; left:0px;
border:1px solid red;
width:100%;
height:100%;
}
</style>
<script>
$(function(){
// get canvas references
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvas2=document.getElementById("canvas2");
var ctx2=canvas2.getContext("2d");
// calc canvas position on page
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
// define the ellipse
var cx=150;
var cy=150;
var radiusX=50;
var radiusY=25;
var halfPI=Math.PI/2;
var steps=8; // larger == greater accuracy
// get mouse position
// calc a point on the ellipse that is "near-ish"
// display a line between the mouse and that ellipse point
function handleMouseMove(e){
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
var pt=getEllipsePt(mouseX,mouseY);
// testing: draw results
drawResults(mouseX,mouseY,pt.x,pt.y);
}
// calc a point on the ellipse that is "near-ish" the target point
// uses "brute force"
function getEllipsePt(targetPtX,targetPtY){
// calculate which ellipse quadrant the targetPt is in
var q;
if(targetPtX>cx){
q=(targetPtY>cy)?0:3;
}else{
q=(targetPtY>cy)?1:2;
}
// calc beginning and ending radian angles to check
var r1=q*halfPI;
var r2=(q+1)*halfPI;
var dr=halfPI/steps;
var minLengthSquared=200000000;
var minX,minY;
// walk the ellipse quadrant and find a near-point
for(var r=r1;r<r2;r+=dr){
// get a point on the ellipse at radian angle == r
var ellipseX=cx+radiusX*Math.cos(r);
var ellipseY=cy+radiusY*Math.sin(r);
// calc distance from ellipsePt to targetPt
var dx=targetPtX-ellipseX;
var dy=targetPtY-ellipseY;
var lengthSquared=dx*dx+dy*dy;
// if new length is shortest, save this ellipse point
if(lengthSquared<minLengthSquared){
minX=ellipseX;
minY=ellipseY;
minLengthSquared=lengthSquared;
}
}
return({x:minX,y:minY});
}
// listen for mousemoves
$("#canvas").mousemove(function(e){handleMouseMove(e);});
// testing: draw the ellipse on the background canvas
function drawEllipse(){
ctx2.beginPath()
ctx2.moveTo(cx+radiusX,cy)
for(var r=0;r<2*Math.PI;r+=2*Math.PI/60){
var ellipseX=cx+radiusX*Math.cos(r);
var ellipseY=cy+radiusY*Math.sin(r);
ctx2.lineTo(ellipseX,ellipseY)
}
ctx2.closePath();
ctx2.lineWidth=5;
ctx2.stroke();
}
// testing: draw line from mouse to ellipse
function drawResults(mouseX,mouseY,ellipseX,ellipseY){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.beginPath();
ctx.moveTo(mouseX,mouseY);
ctx.lineTo(ellipseX,ellipseY);
ctx.lineWidth=1;
ctx.strokeStyle="red";
ctx.stroke();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="wrapper">
<canvas id="canvas2" width=300 height=300></canvas>
<canvas id="canvas" width=300 height=300></canvas>
</div>
</body>
</html>
元の回答
円と楕円の関係は次のとおりです
水平に配置された楕円の場合:
(x x) / (a a) + (y y) / (b b) == 1;
どこでa
は水平頂点までの長さで、どこでb
は垂直頂点までの長さです。
円と楕円の関係:
a==b の場合、楕円は円です。
でも...!
任意の点から楕円上の点までの最小距離を計算するには、円よりもはるかに多くの計算が必要です。
計算へのリンクは次のとおりです (DistancePointEllipseEllipsoid.cpp をクリックします)。
http://www.geometrictools.com/SampleMathematics/DistancePointEllipseEllipsoid/DistancePointEllipseEllipsoid.html