ベクトル演算ライブラリ

ここではMoNo.RAILに用意されている幾何計算を行うのに便利な型を紹介します。

型名について

多くの型は2次元版と3次元版が用意されています。 また、要素として倍精度浮動小数点数型、単精度浮動小数点数型、整数型のものがあります。 それぞれ型名に接尾語として2,3およびd,f,iがついています。

例えば Point3d はX,Y,Zの座標値を倍精度浮動小数点数型として持つ3次元空間上の点を表す構造体です。 また、 Vector2i はX,Yの要素を整数型として持つ2次元のベクトルを表す構造体です。

本項では主に3次元版の倍精度浮動小数点数型のもの(接尾語が3dのもの)について説明します。

点とベクトル

点を位置ベクトルとみなして同じ型で表現するライブラリもありますが、MoNo.RAILでは点とベクトルを幾何オブジェクトとして明確に区別します。

Vector3d は3次元空間での方向ベクトルを表す構造体です。 X,Y,Z各成分を持ち、一般的なベクトル演算が行えます。 ゼロベクトルを表す Zero , 各単位ベクトルを表す Ex , Ey , Ez などが用意されています。

let v1 = Vector3d(1.0, 2.0, 3.0)
let v2 = Vector3d(2.0, 4.0, 3.0)
// 和
v1 + v2 |> printfn "%A" // (3, 6, 6)
// 差
v1 - v2 |> printfn "%A" // (-1, -2, 0)
// スカラー倍
2.0 * vec1 |> printfn "%A" // (2, 4, 6)
// 内積
Vector3d.Dot(v1, v2) |> printfn "%f" // 19.000000
// 外積
v1 * v2 |> printfn "%A" // (-6, 3, 0)
// 長さ(ノルム)
v1.Length |> printfn "%f" // 3.741657
// X方向単位ベクトル
Vector3d.Ex |> printfn "%A" // (1, 0, 0)
...

Point3d は3次元空間中の点を表す構造体です。 X,Y,Zの各成分を持ちます。

Distance メソッドで引数に与えられた点との距離を計算でき、スタティックプロパティ Point3d.Zero で原点(0.0, 0.0, 0.0)が取得できます。

またベクトルを加減算して新たな点を計算でき、点と点の差を取るとベクトルとなります。

  • 点 + ベクトル = 点
  • 点 ― ベクトル = 点
  • 点 ― 点 = ベクトル
let p1 = Point3d(1.0, 2.0, 3.0)
p1.X |> printfn "%f" // 1.000000
p1.Y |> printfn "%f" // 2.000000
p1.Z |> printfn "%f" // 3.000000

let p2 = Point3d(1.0, 5.0, 7.0)
p2.Distance(p1) |> printfn "%f" // 5.000000

Point3d.Zero |> printfn "%A" // (0, 0, 0)

let vec = Vector3d(1.0, 1.0, 1.0)
p1 + vec |> printfn "%A" // (2, 3, 4) : Point3d
p1 - vec |> printfn "%A" // (0, 1, 2) : Point3d
p1 - p2 |> printfn "%A" // (0, -3, -4) : Vector3d

同次座標

HmCod3d は3次元の同次座標を表す構造体です。斉次座標や射影座標と呼ばれることもあります。 通常、3次元空間上の点やベクトルは(X,Y,Z)といったように3つの数字で表されますが、同次座標では(X,Y,Z;W)と4つの数字で表します。 Point3d から変換すると、Wの値が1の同次座標が作成されます。 また Vector3d から変換するとWの値が0の同時座標が作成されます。

let hm = HmCod3d(1.0, 2.0, 3.0, 1.0)
hm |> printfn "%A" // (1, 2, 3; 1)

let pnt = Point3d(1.0, 1.0, 1.0)
pnt |> HmCod3d |> printfn "%A" // (1, 1, 1; 1)

let vec = Vector3d(1.0, 1.0, 1.0)
vec |> HmCod3d |> printfn "%A" // (1, 1, 1; 0)

同次座標(X,Y,Z;W)は点(X/W, Y/W, Z/W)に対応します。点とW値を指定して同次座標を作成することもできます。 また Project() メソッドで対応する3次元空間上の点を取得することができます。

let pnt = Point3d(1.0, 2.0, 3.0)
let hm = HmCod3d(pnt, 2.0)
hm |> printfn "%A" // (2, 3, 6; 2)
hm.Project() |> printfn "%A" // (1, 2, 3)

同次座標のW成分はその点にかかる重みと考えることもでき、同次座標同士の和はその加重平均に対応します。

let pnt1 = Point3d.Zero
let pnt2 = Point3d(1.0, 1.0, 1.0)

let hm1 = HmCod3d(pnt1, 1.0) + HmCod3d(pnt2, 1.0) // 同じ重みの加重平均=中点
hm1.Project() |> printfn "%A" // (0.5, 0.5, 0.5)

let hm2 = HmCod3d(pnt1, 1.0) + HmCod3d(pnt2, 4.0) // pnt2に4倍の重みをつけた加重平均
hm2.Project() |> printfn "%A" // (0.8, 0.8, 0.8)

QuaternionとRotation

Quaternion は四元数を表す構造体です。 四元数とは3つの虚数単位i, j, kを使って

W + Xi + Yj + Zk (W, X, Y, Zはそれぞれ実数)

と表される数です。(i*i = j*j = k*k = ijk = -1)

let q1 = Quaternion(1.0, 1.0, 1.0, 1.0)
let q2 = Quaternion(0.0, 1.0, 2.0, 3.0)

// 和
(q1 + q2) |> printfn "%A" // 1 + 2 i + 3 j + 4 k
// 差
(q1 - q2) |> printfn "%A" // 1 + 0 i + -1 j + -2 k
// 積
(q1 * q2) |> printfn "%A" // -6 + 2 i + 0 j + 4 k
// 定数倍
(2.0 * q1) |> printfn "%A" // 2 + 2 i + 2 j + 2 k

// 絶対値
q1.Abs() |> printfn "%f" // 2.0
// 単位化
let normalized = q1.Normalize()
normalized |> printfn "%A" // 0.5 + 0.5 i + 0.5 j + 0.5 k
normalized.Abs() |> printfn "%A" // 1.0
// 逆元
let inv = q1.Inverse()
inv |> printfn "%A" // 0.25 + -0.25 i + -0.25 j + -0.25 k
(q1 * inv) |> printfn "%A" // 1 + 0 i + 0 j + 0 k
(inv * q1) |> printfn "%A" // 1 + 0 i + 0 j + 0 k

さて、この四元数ですが三次元空間の回転を表現するために利用できることが知られています。 とはいえそのためには四元数の数学的理解が必要となります。

そこでMoNo.RAILでは Quaternion をラップして3次元空間の回転を扱いやすくした構造体 Rotation3d が用意されています。 Rotation3d を使うと空間内での任意の回転が容易に記述できます。

// 回転軸を中心に指定した角度だけ回転
let axis = Vector3d(1.0, 1.0, 1.0) // 回転軸
let radius = 2.0*Math.PI/3.0 // 回転角度
let r1 = Rotation3d(axis, radius)
r1.Rotate(Point3d(1.0, 0.0, 0.0)) |> printfn "%A" // (0, 1, 0)

// from方向をto方向に向けるような回転
let fromVec = Vector3d(1.0, 1.0, 0.0)
let toVec = Vector3d(0.0, 0.0, 1.0)
let r2 = Rotation3d(fromVec, toVec)
// 回転軸
r2.Axis |> printfn "%A" // (0.707106781186547, -0.707106781186547, 0)
// 回転角
r2.Radian |> printfn "%A" // 1.570796327

なお、回転の中心は原点で回転方向は回転軸に対して右ねじの向きになります。

CodSys3d

CodSys3d はローカル座標系(局所座標系)を表す構造体です。

グローバル座標(絶対座標系)における原点位置 Point3d と、ローカル座標系がグローバル座標系に対してどのように回転しているかの Rotation を持ちます。 ローカル座標系を定義すると、グローバル座標系における座標をローカル座標系から見た座標に変換(Localize)したり、ローカル座標系における座標をグローバル座標系から見た座標に変換(Globalize)することができます。

// グローバル座標系で(1, 2, 3)を原点に持ち各座標軸はグローバル座標系と同じローカル座標系を定義
let codSys = CodSys3d(Rotation3d.Unit, Point3d(1.0, 2.0, 3.0))
codSys |> printfn "%A" // O = (1, 2, 3); U = (1, 0, 0); V = (0, 1, 0); N = (0, 0, 1)

// グローバル座標での原点座標をローカル座標に変換
let globalPos1 = Point3d.Zero
let localPos1 = codSys.Localize(globalPos1)
localPos1 |> printfn "%A" // (-1, -2, -3)

// ローカル座標をグローバル座標に変換
let localPos2 = Point3d(1.0, 1.0, 1.0)
let globalPos2 = codSys.Globalize(localPos2)
globalPos2 |> printfn "%A" // (2, 3, 4)

また座標系の変換は、同次座標 HmCod3d に4x4行列 HmMatrix3d を掛けることでも行えます。 CodSys3d からその変換行列を取得することもできます。

// 先程のコードの続き

// ローカライズに対応する行列を取得し、その行列で座標変換を行う
let localizeHmMat = codSys.ToLocalizeMatrix()
(localizeHmMat * HmCod3d(globalPos1)).Project() |> printfn "%A" // (-1, -2, -3)

// グローバライズに対応する行列を取得し、その行列で座標変換を行う
let globalizeHmMat = codSys.ToGlobalizeMatrix()
(globalizeHmMat * HmCod3d(localPos2)).Project() |> printfn "%A" // (-1, -2, -3)

RangeとBoxおよびSphere

Range は数直線上のある区間を表現する構造体です。下限 Lower と上限 Upper を持ちます。 Includes メソッドで数値がその半開区間[Lower, Upper)に内包されているかを取得できます。 Interior メソッドでは開区間(Lower, Upper)に内包されているかどうかを取得できます。

let range = Range(1.0, 2.0, -3.0, 4.0)
range |> printfn "%A" // [-3, 4)

let x1 = -3.0
let x2 = 5.0
range.Includes(-3.0) |> printfn "%A" // true
range.Includes(5.0) |> printfn "%A" // false
range.Includes(4.0) |> printfn "%A" // false
range.Interior(-3.0) |> printfn "%A" // false

Box2d および Box3dRange の2次元版/3次元版です。 Box3d はX軸、Y軸、3次元版ではZ軸も含め、各軸にそれぞれ区間を持ち、Range の直積と捉えられます。 視覚的には矩形/直方体の領域となります。 Lower は各軸の下限を組にした点、 Upper は各軸の上限を組にした点で、 Size プロパティで領域矩形/直方体の大きさを取得できます。 Size プロパティで取得できる型 Size2d / Size3d はプロパティ X , Y , Z を持ち、それぞれの軸向きの長さを持ちます。

2つの Box の共通部分を &&& 演算子で、2つの Box をともに含む最小の Box||| 演算子で得ることもできます。

let box1 = Box3d(Range(-1.0, 2.0), Range(1.0, 3.0), Range(-4.0, -2.0))

box1.Lower |> printfn "%A" // (-1, 1, -4)
box1.Upper |> printfn "%A" // (2, 3, -2)

box1.Includes(Point3d(1.0, 2.0, 3.0)) |> printfn "%A" // false
box1.Includes(Point3d(1.0, 1.0, -3.0)) |> printfn "%A" // true
box1.Interior(Point3d(-1.0, 1.0, -4.0)) |> printfn "%A" // false

box1.Size |> printfn "%A" // (3, 2, 2)

let box2 = Box3d(Range(-1.0, 3.0), Range(2.0, 6.0), Range(-3.0, 3.0))

(box1 &&& box2) |> printfn "%A" // Lower = (-1, 2, -3), Upper = (2, 3, -2)
(box1 ||| box2) |> printfn "%A" // Lower = (-1, 1, -4), Upper = (3, 6, 3)

Sphere3d も領域を表す構造体です。これは中心と半径を持つ球形の領域です。 Boxと同様、 IncludesInterior メソッドでその領域に点を含むかどうかを判定できます。 Includes は境界上の点(球面上の点)を含みますが Interior は含みません。

let sphere = Sphere3d(Point3d.Zero, 10.0)
sphere.Includes(Point3d(5.0, 0.0, 0.0)) |> printfn "%A" // true
sphere.Includes(Point3d(0.0, 10.0, 0.0)) |> printfn "%A" // true
sphere.Interior(Point3d(0.0, 10.0, 0.0)) |> printfn "%A" // false

IPoint3d と IBoundary3d

IBoundary3dIPoint3d という2つの重要なインターフェイスについて説明します。 これらは次のように定義されています(C#)。

IBoundary3d
public interface IBoundary3d
{
  Box3d    BoundingBox    { get; }
  Sphere3d BoundingSphere { get; }
}
IPoint3d
public interface IPoint3d : IBoundary3d
{
  Point3d Position { get; }
}
  • IBoundary3d とは:

    「位置と大きさ」を持つ幾何データを表すインターフェイスです。 幾何オブジェクトの軸平行境界ボックス(BoundingBox)や境界球(BoundingSphere)を取得する機能を持ちます。

    このインターフェイスはMoNo.RAILの広い範囲で重要な役割を果たします。 特に3Dビューの視野錐体(Viewing Frustum)の設定やフィットの処理において重要です。 MoNo.RAILの3Dビューは、オブジェクトが実装する IBoundary3d インターフェイスから描画対象の大きさを取得し、適切に視野錐体を設定したりフィットの処理を行ったりします。ユーザーが独自にエンティティ型を定義し3Dビューに表示したい場合には、そのエンティティ型に IBoundary3d を実装する必要があります。

  • IPoint3d とは:

    「点」を表すインターフェイスです。 例えば Point3dPoint3f は異なるデータ型ですが、どちらも点を表すデータとして統一的に扱いたいというニーズがあります。そのため、これらのデータ型は共に IPoint3d インターフェイスを実装し統一的に扱える設計となっています。

    IPoint3dIBoundary3d を継承しています。 IPoint3d は「点」であって大きさを持ちませんので、BoundingBox は大きさを保たないボックス(LowerUpper が共に Position)を、BoundingSphere は半径ゼロの球(Center == Position かつ Radius == 0)を返します。

IBoundary3d を実装する代表的なデータ型を下記の表に示します。 特に Box3dSphere3dIBoundary3d と相互依存した定義となっています。

Type Assembly
Box3d MoNo.dll
Box3f MoNo.dll
Sphere3d MoNo.dll
Sphere3f MoNo.dll
Segment3d MoNo.dll
Segment<T> MoNo.Geometries.dll
Triangle<T> MoNo.Geometries.dll
Polyline<T> MoNo.Geometries.dll
Cloud<T> MoNo.Geometries.dll
Soup<T> MoNo.Geometries.dll
Mesh<T> MoNo.Geometries.dll
CG.MeshObj MoNo.Geometries.dll

IPoint3d を実装する MoNo.dll 中のデータ型を下記の表に示します。 特に Point3dIPoint3d と相互依存した定義となっています。

Type Position { get; } の実装
Point3d return this;
Point3f return this.To3d();
PointNormal3d return this.Point;
PointNormal3f return this.Point.To3d();
PointNormalUV3f return this.Point.To3d();
PointUV3f return this.Point.To3d();
ColoredPoint3f return this.Point.To3d();
ColoredPointNormal3f return this.Point.To3d();

AngleとAngular

AngleAngular は角度を表現するのに特化した構造体です。弧度法(radian)および度数法(degree)の変換をサポートします。 Angle は[-π, π) の範囲を表現する構造体です。

let angle1 = Angle.FromDegree(90.0)
angle1.Degree |> printfn "%A" // 90.0
angle1.Radian |> printfn "%A" // 1.5707963237

let angle2 = Angle.FromRadian(Math.PI/4.0)
angle2.Degree |> printfn "%A" // 45.0
angle2.Radian |> printfn "%A" // 0.7853981634

let angle3 = angle1 + angle2
angle3.Degree |> printfn "%A" // 135.0
angle3.Radian |> printfn "%A" // 2.35619449

let angle4 = angle1 + angle3
angle4.Degree |> printfn "%A" // -135.0
angle4.Radian |> printfn "%A" // -2.35619449

Angular は[-π, π)を超える範囲を表現できます。 Angle の場合と比較してみて下さい。

let angle1 = Angular.FromDegree(90.0)
angle1.Degree |> printfn "%A" // 90.0
angle1.Radian |> printfn "%A" // 1.5707963237

let angle2 = Angular.FromRadian(Math.PI/4.0)
angle2.Degree |> printfn "%A" // 45.0
angle2.Radian |> printfn "%A" // 0.7853981634

let angle3 = angle1 + angle2
angle3.Degree |> printfn "%A" // 135.0
angle3.Radian |> printfn "%A" // 2.35619449

let angle4 = angle1 + angle3
angle4.Degree |> printfn "%A" // 225.0
angle4.Radian |> printfn "%A" // 3.926990817