■ 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.