■ 3D平面上の 5 点を通る楕円
CAD上で、3D 座標 5 点を指示します。
この 5 点のうち代表 3 点を通る平面を、X-Y平面に合わせるため、Z軸、Y軸、X軸を回転します。
結果、5 点の Z 値は、0 になります。
楕円の方程式 : AX^2 + BXY + CY^2 + DX + EY + 1 = 0 に、5 点の X,Y を代入すると、5 元連立方程式が出来ます。
これを、ガウス-ジョルダン法で解くと、変数 A, B, C, D, E が求まります。
(参考URL:「オッズ シェアの計算方法 - TigerOddsの最終レース日記」 http://ameblo.jp/tigerodds/entry-10060455494.html)
これらから、近似楕円の長軸、短軸、中心を求めます。
(参考URL:「最小二乗法による楕円近似 - 画像処理ソリューション」 http://imagingsolution.blog107.fc2.com/blog-entry-20.html)
※座標の取得、検証に Bricscad を使っていますが、計算には不要です。
■ ソースコード
unit Gauss2Unit; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,BricscadApp_TLB,BricscadDB_TLB, ComObj, Math, Vcl.ExtCtrls; type TForm3 = class(TForm) Button2: TButton; Edit6: TEdit; Edit7: TEdit; Edit8: TEdit; Edit9: TEdit; Edit10: TEdit; Edit11: TEdit; Edit12: TEdit; Edit13: TEdit; Edit14: TEdit; Edit15: TEdit; Edit22: TEdit; Edit23: TEdit; Edit26: TEdit; Label7: TLabel; Label8: TLabel; Label9: TLabel; Label10: TLabel; Label11: TLabel; Label12: TLabel; Label13: TLabel; Label16: TLabel; Label17: TLabel; Label18: TLabel; Image1: TImage; Edit21: TEdit; Edit24: TEdit; Label1: TLabel; Edit25: TEdit; Label2: TLabel; Button4: TButton; Button5: TButton; Label5: TLabel; Edit35: TEdit; Edit36: TEdit; Edit37: TEdit; Edit38: TEdit; Edit39: TEdit; procedure Button2Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button4Click(Sender: TObject); procedure Button5Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; type TGaussA = array[0..17, 0..17] of Extended; type TGaussB = array[0..17] of Extended; type cadpt = array[0..2] of double; var Form3: TForm3; cadpts : array of cadpt; implementation {$R *.dfm} //Z軸まわりに座標を回転 function Rotate(pt:cadPt;rad:extended):cadPt; var cosz,sinz:extended; begin cosz := Cos(rad); sinz := Sin(rad); Result[0] := pt[0] * cosz + pt[1] * -sinz; Result[1] := pt[0] * sinz + pt[1] * cosz; Result[2] := pt[2]; end; function RotateBase(base, pt: cadPt; rad: extended): cadPt; var cosz,sinz:extended; begin cosz := Cos(rad); sinz := Sin(rad); Result[0] := base[0] + (pt[0] - base[0]) * cosz + (pt[1] - base[1]) * -sinz; Result[1] := base[1] + (pt[0] - base[0]) * sinz + (pt[1] - base[1]) * cosz; Result[2] := pt[2]; end; //Y軸まわりに座標を回転 function RotateYAx(pt: cadPt; rad: extended): cadPt; var cosy,siny:extended; begin cosy := Cos(rad); siny := Sin(rad); Result[0] := pt[0] * cosy + pt[2] * siny; Result[1] := pt[1]; Result[2] := pt[0] * -siny + pt[2] * cosy; end; //X軸まわりに座標を回転 function RotateXAx(pt:cadPt;rad:extended):cadPt; var cosx,sinx:extended; begin cosx := Cos(rad); sinx := Sin(rad); Result[0] := pt[0]; Result[1] := pt[1] * cosx + pt[2] * -sinx; Result[2] := pt[1] * sinx + pt[2] * cosx; end; //**************************************** // 2d距離 //**************************************** function Distance(p1, p2: cadPt):extended; begin Result := sqrt(sqr(p1[1] - p2[1]) + sqr(p1[0] - p2[0])); end; //**************************************** // 線分2点の角度 //**************************************** function Angle(p1, p2: cadPt): extended; var ang, x, y : extended; begin x := p2[0] - p1[0]; y := p2[1] - p1[1]; if abs(x) < 10e-6 then begin if p1[1] < p2[1] then ang := pi * 0.5 else ang := pi * 1.5; end else if abs(y)< 10e-6 then begin if p1[0] < p2[0] then ang := 0 else ang := pi; end else begin ang := abs(ArcTan(y / x)); if (x < 0.0) and (y >= 0.0) then ang := pi - ang else if (x >= 0.0) and (y < 0.0) then ang := pi * 2.0 - ang else if (x < 0.0) and (y < 0.0) then ang := ang + pi; end; result := ang; end; //**************************************** // 座標:p0から角度:radの距離:distの点を求める //**************************************** function Polar(p0: cadPt; rad, dist: double): cadPt; begin result[0] := dist * cos(rad) + p0[0]; result[1] := dist * sin(rad) + p0[1]; result[2] := p0[2]; end; //**************************************** // 連立方程式ガウス-ジョルダン法 Delphi // n:n次方程式 a:係数を表すn次元配列 // b:非同次項を表す1次配列 // 戻り値:a:逆行列 b:解 // type TGaussA=array[0..17,0..17] of real; //添え字の上限=n-1 // type TGaussB=array[0..17] of real; //添え字の上限=n-1 // // 引用元:「オッズ シェアの計算方法 - TigerOddsの最終レース日記」 // http://ameblo.jp/tigerodds/entry-10060455494.html // その元 // http://akita-nct.jp/yamamoto/lecture/2004/5E/linear_equations/how_to_make_GJ/gaussj.pdf //**************************************** function gauss_jordan(n: integer; var a: TGaussA; var b: TGaussB): boolean; var ipv, i, j : integer; inv_pivot, temp : real; big : real; pivot_row : integer; row : array[0..17] of integer; begin result := True; pivot_row := -1; for ipv := 0 to n - 1 do begin //最大値検索 big := 0.0; for i := ipv to n - 1 do begin if abs(a[i, ipv]) > big then begin big := abs(a[i, ipv]); pivot_row := i; end; end; if big = 0 then begin Result := False; Exit; end; row[ipv] := pivot_row; // 行の入れ替え if (ipv <> pivot_row) then begin for i := 0 to n - 1 do begin temp := a[ipv, i]; a[ipv, i] := a[pivot_row, i]; a[pivot_row, i] := temp; end; temp := b[ipv]; b[ipv] := b[pivot_row]; b[pivot_row] := temp; end; // 対角成分=1 ピボット行の処理 inv_pivot := 1.0 / a[ipv, ipv]; a[ipv,ipv] := 1.0; for j := 0 to n - 1 do begin a[ipv, j] := a[ipv, j] * inv_pivot; end; b[ipv] := b[ipv] * inv_pivot; // ピボット列=0 ピボット行以外ノ処理 for i := 0 to n - 1 do begin if (i <> ipv) then begin temp := a[i, ipv]; a[i, ipv] := 0.0; for j := 0 to n - 1 do begin a[i, j] := a[i,j] - temp * a[ipv, j]; end; b[i] := b[i] - temp * b[ipv]; end; end; end; // 列の入れ替え(逆行列) for j := n - 1 downto 0 do begin if (j <> row[j]) then begin for i := 0 to n - 1do begin temp := a[i, j]; a[i, j] := a[i, row[j]]; a[i, row[j]] := temp; end; end; end; end; // 変数計算 procedure TForm3.Button2Click(Sender: TObject); var i : integer; Ga: TGaussA; Gb: TGaussB; A, B, C, D, E : Extended; xx,yy:array [0..4] of double; sc : double; x0, y0, ang : double; ra, rb, temp : double; p0, p1 :cadpt; begin Edit22.Text := '---'; Edit23.Text := '---'; Edit26.Text := '---'; with Image1 do begin Canvas.Brush.Color := clBlack; Canvas.FillRect(Rect(0,0,Width,Height)); end; xx[0] := StrToFloat(Edit6.Text); yy[0] := StrToFloat(Edit7.Text); xx[1] := StrToFloat(Edit8.Text); yy[1] := StrToFloat(Edit9.Text); xx[2] := StrToFloat(Edit10.Text); yy[2] := StrToFloat(Edit11.Text); xx[3] := StrToFloat(Edit12.Text); yy[3] := StrToFloat(Edit13.Text); xx[4] := StrToFloat(Edit14.Text); yy[4] := StrToFloat(Edit15.Text); // 楕円の方程式:AX^2 + BXY + CY^2 + DX + EY + 1 = 0 for i := 0 to 4 do begin Ga[i, 0] := xx[i] * xx[i]; Ga[i, 1] := xx[i] * yy[i]; Ga[i, 2] := yy[i] * yy[i]; Ga[i, 3] := xx[i]; Ga[i, 4] := yy[i]; Gb[i] := -1; end; // 連立方程式をガウス-ジョルダン法で解く if gauss_jordan(5, Ga, Gb) then begin // 「最小二乗法による楕円近似 - 画像処理ソリューション」 // http://imagingsolution.blog107.fc2.com/blog-entry-20.html // // aX^2 + bXY + cY^2 + dX + eY + 1 = 0 を // X^2 + AXY + BY^2 + CX + DY + E = 0 の形にするため、 // すべての変数を a で割る A := Gb[1] / Gb[0]; B := Gb[2] / Gb[0]; C := Gb[3] / Gb[0]; D := Gb[4] / Gb[0]; E := 1 / Gb[0]; // 中心の座標 x0 := (A * D - 2 * B * C) / (4 * B - A * A); y0 := (A * C - 2 * D) / (4 * B - A * A); // 傾き ang := ArcTan(A /(1 - B)) / 2.0; // X座標 Edit21.Text := Format('%.1f', [x0]); // Y座標 Edit24.Text := Format('%.1f', [y0]); // 傾き Edit25.Text := Format('%.1f', [RadToDeg(ang)]); // X軸方向の長さA ra := sqrt(sqr(x0 * cos(ang) + y0 * sin(ang)) - E * sqr(cos(ang)) - (sqr(x0 * sin(ang) - y0 * cos(ang)) - E * sqr(sin(ang))) * (sqr(sin(ang)) - B * sqr(cos(ang))) / (sqr(cos(ang))- B * sqr(sin(ang)))); // Y軸方向の長さB rb := sqrt(sqr(x0 * sin(ang) - y0 * cos(ang)) - E * sqr(sin(ang)) - (sqr(x0 * cos(ang) + y0 * sin(ang)) - E * sqr(cos(ang))) * (sqr(cos(ang)) - B * sqr(sin(ang))) / (sqr(sin(ang)) - B * sqr(cos(ang)))); if ra > rb then begin // 長軸 Edit22.Text := Format('%.2f', [ra * 2]); // 短軸 Edit23.Text := Format('%.2f', [rb * 2]); // 短軸/長軸の比 Edit26.Text := Format('%.3f', [rb / ra]); { // 切断角度 if (ra > 0) and ((rb/ra) <= 1) then Edit27.Text := Format('%.2f', [RadToDeg(pi / 2 - arccos(rb/ra))]) else Edit27.Text := '---'; } end else begin // 長軸 Edit22.Text := Format('%.2f', [rb * 2]); // 短軸 Edit23.Text := Format('%.2f', [ra * 2]); // 短軸/長軸の比 Edit26.Text := Format('%.3f', [ra / rb]); { // 切断角度 if (rb > 0) and ((ra / rb) <= 1) then Edit27.Text := Format('%.2f', [RadToDeg(pi / 2 - arccos(ra/rb))]) else Edit27.Text := '---'; } end; p0[0] := x0; p0[1] := y0; with Image1 do begin if ra > rb then sc := ra * 2 * 1.4 / width else sc := rb * 2 * 1.4 / width; // 指示された5点を描画 Canvas.Pen.Color := clAqua; for i := 0 to 4 do begin Canvas.Ellipse( Trunc((xx[i] - x0) / sc) + width div 2 + 3, Height - Trunc((yy[i] - y0) / sc) - width div 2 + 4, Trunc((xx[i] - x0) / sc) + width div 2 - 3, Height - Trunc((yy[i] - y0)/ sc) - width div 2 - 3); end; // 中心点「+」マーク Canvas.Pen.Color := clGreen; Canvas.MoveTo(0, Height div 2); Canvas.LineTo(width, Height div 2); Canvas.MoveTo(Width div 2, 0); Canvas.LineTo(width div 2, Height); Canvas.Pen.Color := clLime; Canvas.MoveTo(Trunc((p0[0] - x0) / sc) + width div 2 - 3, Height - Trunc((p0[1] - y0) / sc) - width div 2); Canvas.LineTo(Trunc((p0[0] - x0) / sc) + width div 2 + 4, Height - Trunc((p0[1] - y0) / sc) - width div 2); Canvas.MoveTo(Trunc((p0[0] - x0)/ sc) + width div 2, Height - Trunc((p0[1] - y0) / sc) - width div 2 - 3); Canvas.LineTo(Trunc((p0[0] - x0) / sc) + width div 2, Height - Trunc((p0[1] - y0) / sc) - width div 2 + 4); // 楕円を描く for i := 0 to 359 do begin p1 := Polar(p0, DegToRad(i), rb); p1[0] := p0[0] + (p1[0] - p0[0]) * (ra / rb); p1 := RotateBase(p0, p1, ang); Canvas.Pixels[Trunc((p1[0] - x0) / sc) + width div 2, Height - Trunc((p1[1] - y0) / sc) - width div 2] := clYellow; end; end; end; end; // 3D 座標 5 点指示 procedure TForm3.Button4Click(Sender: TObject); var app: IAcadApplication; vpt : OleVariant; i : integer; xx, yy, zz : array [0..4] of double; begin Edit21.Text := ''; Edit22.Text := ''; Edit23.Text := ''; Edit24.Text := ''; Edit25.Text := ''; Edit26.Text := ''; with Image1 do begin Canvas.Brush.Color := clBlack; Canvas.FillRect(Rect(0,0,Width,Height)); end; app := GetActiveOleObject('BricscadApp.AcadApplication') as IAcadApplication; try vpt := VarArrayCreate([0, 2], varDouble); for i := 0 to 4 do begin SetForegroundWindow(app.HWND); vpt := app.ActiveDocument.Utility.GetPoint(EmptyParam, IntToStr(i + 1) + ' 点目を指示'); xx[i] := Trunc(vpt[0]*10) / 10; yy[i] := Trunc(vpt[1]*10) / 10; zz[i] := Trunc(vpt[2]*10) / 10; end; Edit6.Text := FloatToStr(xx[0]); Edit7.Text := FloatToStr(yy[0]); Edit8.Text := FloatToStr(xx[1]); Edit9.Text := FloatToStr(yy[1]); Edit10.Text := FloatToStr(xx[2]); Edit11.Text := FloatToStr(yy[2]); Edit12.Text := FloatToStr(xx[3]); Edit13.Text := FloatToStr(yy[3]); Edit14.Text := FloatToStr(xx[4]); Edit15.Text := FloatToStr(yy[4]); Edit35.Text := FloatToStr(zz[0]); Edit36.Text := FloatToStr(zz[1]); Edit37.Text := FloatToStr(zz[2]); Edit38.Text := FloatToStr(zz[3]); Edit39.Text := FloatToStr(zz[4]); except ; end; end; // 2D化(代表3点の平面を、X-Y平面に合わせる) procedure TForm3.Button5Click(Sender: TObject); var pts : array[0..4] of cadpt; dist, xang , yang, zang: double; i, j : integer; pt0, pt1, pt2 : cadpt; distmax : double; pt0x, pt0y : double; begin pts[0][0]:= StrToFloat(Edit6.Text); pts[0][1]:= StrToFloat(Edit7.Text); pts[0][2]:= StrToFloat(Edit35.Text); pts[1][0]:= StrToFloat(Edit8.Text); pts[1][1]:= StrToFloat(Edit9.Text); pts[1][2]:= StrToFloat(Edit36.Text); pts[2][0]:= StrToFloat(Edit10.Text); pts[2][1]:= StrToFloat(Edit11.Text); pts[2][2]:= StrToFloat(Edit37.Text); pts[3][0]:= StrToFloat(Edit12.Text); pts[3][1]:= StrToFloat(Edit13.Text); pts[3][2]:= StrToFloat(Edit38.Text); pts[4][0]:= StrToFloat(Edit14.Text); pts[4][1]:= StrToFloat(Edit15.Text); pts[4][2]:= StrToFloat(Edit39.Text); // XY平面で相互距離の一番長い2点を得る // 仮想直線とする distmax := 0; for i := 0 to 3 do begin for j := i + 1 to 4 do begin dist := Distance(pts[i], pts[j]); if distmax < dist then begin distmax := dist; pt0 := pts[i]; pt1 := pts[j]; end; end; end; // 元の位置 pt0x := pt0[0]; pt0y := pt0[1]; // pt0 を原点にする // 原点に移動 for i := 0 to 4 do begin for j := 0 to 2 do pts[i][j] := pts[i][j] - pt0[j]; end; for j := 0 to 2 do begin pt1[j] := pt1[j] - pt0[j]; pt0[j] := pt0[j] - pt0[j]; end; // 仮想直線をX軸に合わせる zang := Angle(pt0, pt1); // Z軸周りに回転 pt1 := Rotate(pt1, -zang); for i := 0 to 4 do pts[i] := Rotate(pts[i], -zang); // y軸周りに回転 yang := ArcTan(pt1[2] / pt1[0]); pt1 := RotateYAx(pt1, yang); for i := 0 to 4 do pts[i] := RotateYAx(pts[i], yang); // y座標の一番遠い座標を得る distmax := 0; for i := 0 to 4 do begin dist := abs(pts[i][1]); if distmax < dist then begin distmax := dist; pt2 := pts[i]; end; end; // X軸周りに回転 xang := ArcTan(pt2[2] / pt2[1]); pt2 := RotateXAx(pt2, xang); for i := 0 to 4 do pts[i] := RotateXAx(pts[i], -xang); // Z軸周りの回転を戻す for i := 0 to 4 do pts[i] := Rotate(pts[i], zang); // X,Yのみ元の座標に戻す for i := 0 to 4 do begin pts[i][0] := pts[i][0] + pt0x; pts[i][1] := pts[i][1] + pt0y; end; Edit6.Text := Format('%.1f',[pts[0][0]]); Edit7.Text := Format('%.1f',[pts[0][1]]); Edit8.Text := Format('%.1f',[pts[1][0]]); Edit9.Text := Format('%.1f',[pts[1][1]]); Edit10.Text := Format('%.1f',[pts[2][0]]); Edit11.Text := Format('%.1f',[pts[2][1]]); Edit12.Text := Format('%.1f',[pts[3][0]]); Edit13.Text := Format('%.1f',[pts[3][1]]); Edit14.Text := Format('%.1f',[pts[4][0]]); Edit15.Text := Format('%.1f',[pts[4][1]]); Edit35.Text := Format('%.1f',[pts[0][2]]); Edit36.Text := Format('%.1f',[pts[1][2]]); Edit37.Text := Format('%.1f',[pts[2][2]]); Edit38.Text := Format('%.1f',[pts[3][2]]); Edit39.Text := Format('%.1f',[pts[4][2]]); end; procedure TForm3.FormCreate(Sender: TObject); begin with Image1 do begin Canvas.Brush.Color := clBlack; Canvas.FillRect(Rect(0,0,Width,Height)); end; end; end.