mageo 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,95 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require "rubygems"
5
+ gem "builtinextension"
6
+ require "array_include_eql.rb"
7
+
8
+ # 多面体を表現する抽象クラス。
9
+ # 面は必ず三角形で、たとえば四角形も2つの三角形であると考える。
10
+ # initialize メソッドは subclass で再定義する。
11
+ # subclass の注意点。
12
+ # - 凸包であることを前提とする。
13
+ # チェック機構は Polyhedron クラスで持っているべきだが、面倒なので後回し。
14
+ # 3次元凸包判定の方法をぐぐったが、これといったものが見つからない。
15
+ # - 定義された面同士の間に隙間がないことを前提とする。
16
+ # チェック機構は Polyhedron クラスで持っているべきだが、面倒なので後回し。
17
+ # - 頂点リスト @vertices と、面リスト @vertex_indices_of_triangles を持つ。
18
+ # ただし、@vertex_indices_of_triangles は Triangle クラスインスタンスではなく、
19
+ # @vertices 内の index。
20
+ # see Tetrahedron.rb
21
+ # - メインのテストは 四面体 Tetrahedron クラスで行っている。
22
+ class Polyhedron
23
+ attr_reader :vertices
24
+
25
+ class TypeError < Exception; end
26
+
27
+ # initialize で例外を返すことでインスタンスを生成できない抽象クラスを表現。
28
+ # subclass で再定義する。
29
+ def initialize()
30
+ raise NotImplementedError, "need to define `initialize'"
31
+ end
32
+
33
+ def edges
34
+ results = []
35
+ edges = triangles.each do |triangle|
36
+ triangle.edges.each do |edge|
37
+ results << edge unless results.include_eql?(edge)
38
+ end
39
+ end
40
+ return results
41
+ end
42
+
43
+ def triangles
44
+ results = @vertex_indices_of_triangles.map do |indices|
45
+ Triangle.new( indices.map{|i| @vertices[i] } )
46
+ end
47
+ return results
48
+ end
49
+
50
+ #面で囲まれた空間の中にあれば true を返す。
51
+ def inside?( pos )
52
+ raise TypeError if pos.class == Vector3DInternal
53
+
54
+ result = true
55
+ triangles.each do |triangle|
56
+ result = false unless triangle.same_side?( center, pos.to_v3d )
57
+ end
58
+ return result
59
+ end
60
+
61
+ def include?(pos, tolerance)
62
+ raise TypeError if pos.class == Vector3DInternal
63
+
64
+ return true if inside?( pos )
65
+ triangles.each do |triangle|
66
+ #pp pos
67
+ #pp triangle
68
+ return true if triangle.include?(pos.to_v3d, tolerance)
69
+ end
70
+ return false
71
+ end
72
+
73
+ # 体積を返す。
74
+ def volume
75
+ result = 0.0
76
+ @vertex_indices_of_triangles.each do |tri_vertices|
77
+ vectors = tri_vertices.map { |i| @vertices[i] - center }
78
+ volume = Vector3D.scalar_triple_product( *vectors ).abs
79
+ volume /= 6.0
80
+ result += volume
81
+ end
82
+ return result
83
+ end
84
+
85
+ #各頂点の座標の平均値を返す。
86
+ def center
87
+ tmp = Vector3D[ 0.0, 0.0, 0.0 ]
88
+ @vertices.each do |vertex|
89
+ tmp += vertex
90
+ end
91
+ return tmp * ( 1.0 / @vertices.size.to_f ) # 座標の平均の算出
92
+ end
93
+
94
+ end
95
+
@@ -0,0 +1,83 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require "mageo/vector3d.rb"
5
+
6
+ # 線分を表すクラス。
7
+ class Segment
8
+ attr_reader :endpoints
9
+
10
+ class InitializeError < Exception; end
11
+ class TypeError < Exception; end
12
+
13
+
14
+ # 端点を2つ渡す。
15
+ def initialize(vector0, vector1)
16
+ raise InitializeError if vector0.class != Vector3D
17
+ raise InitializeError if vector1.class != Vector3D
18
+ raise InitializeError if vector0 == vector1
19
+
20
+ @endpoints = [vector0, vector1]
21
+ end
22
+
23
+ # position で与えられた点が線分の中にある点か?
24
+ # tolerance = 0.0 では計算誤差のためにほとんど真とならない。
25
+ # position は Vector3D クラスインスタンスでなければならない。
26
+ def include?(position, tolerance)
27
+ raise TypeError if position.class != Vector3D
28
+
29
+ vec_self = @endpoints[1] - @endpoints[0]
30
+ vec_other = position - @endpoints[0]
31
+
32
+ # 両端の点は計算誤差で失敗しやすいので true にしておく。
33
+ return true if position == @endpoints[0]
34
+ return true if position == @endpoints[1]
35
+
36
+ # 長さ方向チェック
37
+ inner_product = vec_self.inner_product(vec_other)
38
+ return false if (inner_product < 0.0 )
39
+ return false if ( vec_self.r ** 2 < inner_product)
40
+
41
+ # 垂直方向チェック
42
+ vec_outer = vec_other - vec_self * (inner_product / (vec_self.r)**2)
43
+ return false if tolerance < vec_outer.r
44
+
45
+ # ここまでチェックを通過すれば true
46
+ return true
47
+
48
+ ##ex_product = vec_self.exterior_product(vec_other)
49
+ #あかんな。
50
+ #normalize して、
51
+ #この方向の成分を出さんと。
52
+ #外積もおかしい。
53
+
54
+ #return false if ( ex_product[2].abs > tolerance )
55
+
56
+ end
57
+
58
+ # endpoints で取り出せる座標2つのうち、最初のものから最後のものへのベクトルを表す
59
+ # Vector3D クラスインスタンスを返す。
60
+ def to_v3d
61
+ return @endpoints[1] - @endpoints[0]
62
+ end
63
+
64
+ # 等価チェック。
65
+ # uniq できるようにするため。
66
+ def eql?(other)
67
+ raise TypeError if other.class != Segment
68
+ @endpoints.each do |point|
69
+ return false unless other.endpoints.include?(point)
70
+ end
71
+ return true
72
+ end
73
+
74
+ def ==(other)
75
+ raise TypeError if other.class != Segment
76
+ @endpoints.size.times do |i|
77
+ return false unless other.endpoints[i] == @endpoints[i]
78
+ end
79
+ return true
80
+ end
81
+
82
+ end
83
+
@@ -0,0 +1,19 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require "mageo/vector3d.rb"
5
+
6
+ #
7
+ # 球を表現するクラス。
8
+ #
9
+ class Sphere
10
+ attr_reader :position, :radius
11
+
12
+ # 座標と半径
13
+ def initialize(position, radius)
14
+ @position = Vector3D[*position]
15
+ @radius = radius
16
+ end
17
+
18
+ end
19
+
@@ -0,0 +1,42 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require "mageo/vector3d.rb"
5
+ require "mageo/triangle.rb"
6
+ require "mageo/polyhedron.rb"
7
+
8
+
9
+ #
10
+ # 直交座標系 3次元空間内の四面体を表現するクラス。
11
+ #
12
+ class Tetrahedron < Polyhedron
13
+
14
+ class InitializeError < Exception; end
15
+
16
+ # vertices には四面体の頂点を順不同で入れた Array。
17
+ def initialize( vertices )
18
+ raise InitializeError if vertices.class != Array
19
+ raise InitializeError if vertices.size != 4
20
+ vertices.each do |vertex|
21
+ raise InitializeError if vertex.size != 3
22
+ raise InitializeError unless vertex.methods.include?( :[] )
23
+ raise InitializeError unless vertex.methods.include?( :map )
24
+ end
25
+ vertices.each do |vertex|
26
+ raise InitializeError if vertex.class == Vector3DInternal
27
+ end
28
+
29
+ @vertices = vertices.map { |vertex| vertex.to_v3d }
30
+
31
+ @vertex_indices_of_triangles = [
32
+ [ 0, 1, 2 ],
33
+ [ 1, 2, 3 ],
34
+ [ 2, 3, 0 ],
35
+ [ 3, 0, 1 ],
36
+ ]
37
+
38
+ raise InitializeError, "volume is zero." if volume == 0.0
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,225 @@
1
+ #! /usr/bin/env ruby
2
+ # coding: utf-8
3
+
4
+ require "mageo/vector3d.rb"
5
+ require "mageo/vector3dinternal.rb"
6
+ require "mageo/axes.rb"
7
+ require "mageo/segment.rb"
8
+
9
+ #3次元空間中の3角形を表現するクラス。
10
+ #
11
+ #法線ベクトル( Vector3D クラスインスタンス )を返すメソッドは定義しない。
12
+ # 法線ベクトルは2通りの方向を取りうるため。
13
+ # initialize 時に点の指定の順序を決めることで定義はできるが、
14
+ # そうすると簡潔性が損なわれる。
15
+ class Triangle
16
+ attr_reader :vertices
17
+
18
+ class InitializeError < Exception; end
19
+ class LinearException < Exception; end
20
+ class TypeError < Exception; end
21
+ #class InPlaneError < Exception; end
22
+ class NoIntersectionError < Exception; end
23
+
24
+ #An argument 'vertices' can be Array of 3 items, Vector of 3 items,
25
+ # or Vector3D class instance, which have [] and map methods.
26
+ #当面は Array を前提とする。
27
+ #座標が整数で入っていたとしても内部的には Float に変換して使用する。
28
+ #3点が1直線上に並んでいて三角形を囲まない場合は
29
+ #例外 Triangle::LinearException を投げる。
30
+ def initialize( vertices )
31
+ raise InitializeError unless vertices.methods.include?( :size )
32
+ raise InitializeError if vertices.size != 3
33
+ vertices.each do |pos|
34
+ raise InitializeError if pos.size != 3
35
+ raise InitializeError unless pos.methods.include?( :[] )
36
+ raise InitializeError unless pos.methods.include?( :map )
37
+ end
38
+ @vertices = vertices.map do |pos|
39
+ ( pos.map { |i| i.to_f }) . to_v3d
40
+ end
41
+
42
+ #Checking on linear.
43
+ edge1 = @vertices[1] - @vertices[0]
44
+ edge2 = @vertices[2] - @vertices[0]
45
+ if ( Vector3D[0.0, 0.0, 0.0] == Vector3D.vector_product( edge1, edge2 ))
46
+ raise LinearException
47
+ end
48
+
49
+ end
50
+
51
+ #引数で与えられた 2 つの座標が、三角形の面に対して同じ側にあれば true を返す。
52
+ #どちらか、もしくは両方が、面上の点(当然頂点、辺上を含む)であれば必ず false を返す。
53
+ def same_side?( pos0, pos1 )
54
+ raise TypeError if pos0.class != Vector3D
55
+ raise TypeError if pos1.class != Vector3D
56
+
57
+ edge1 = @vertices[1] - @vertices[0]
58
+ edge2 = @vertices[2] - @vertices[0]
59
+ pos0 = pos0.to_v3d - @vertices[0]
60
+ pos1 = pos1.to_v3d - @vertices[0]
61
+
62
+ triple_product_pos0 = Vector3D.scalar_triple_product( edge1, edge2, pos0 )
63
+ triple_product_pos1 = Vector3D.scalar_triple_product( edge1, edge2, pos1 )
64
+ if triple_product_pos0 * triple_product_pos1 > 0
65
+ return true
66
+ else
67
+ return false
68
+ end
69
+ end
70
+
71
+ # 3点で張られる面上にあり、三角形の内側にあれば true を返す。
72
+ # pos が Vector3D クラスインスタンスでなければ例外。
73
+ # ただし、面の法線方向には tolerance だけの許容値を設ける。
74
+ # 計算誤差の問題のため、これを設定しないと殆ど真とならない。
75
+ def include?(pos, tolerance)
76
+ raise TypeError if pos.class != Vector3D
77
+
78
+ axes = internal_axes
79
+ #一次独立チェックは initialize 時にされている筈。
80
+
81
+ pos = (pos - @vertices[0]).to_v3d
82
+ internal_pos = pos.to_v3di(axes)
83
+ return false if internal_pos[2].abs > tolerance #面の外にあれば false
84
+ return false if (internal_pos[0] < 0.0 )
85
+ return false if (1.0 < internal_pos[0])
86
+ return false if (internal_pos[1] < 0.0 )
87
+ return false if (1.0 < internal_pos[1])
88
+ return false if (1.0 < internal_pos[0] + internal_pos[1])
89
+ #たしざんで 1 いかとか。
90
+ return true
91
+ end
92
+
93
+ ## 三角形と線分が交差するか判定する。
94
+ ## 以下の and 条件になる。
95
+ ## - 面と直線が並行でないこと。
96
+ ## - 面と直線の交点が三角形の内側であること
97
+ ## - 面と直線の交点が線分の長さ方向に関して線分と同じ領域にあること
98
+ #def intersect?(segment)
99
+ #
100
+ #end
101
+
102
+ # 三角形を含む面と線分の交点を返す。
103
+ # 交点がない、無数にある、三角形の内側にない、という場合には
104
+ # 例外 NoIntersectionError を返す。
105
+ def intersection(segment, tolerance)
106
+ # 平行のとき、例外。
107
+ raise NoIntersectionError if parallel_segment?(segment)
108
+
109
+ # 面内の直線のとき、例外。
110
+ endpoints_v3di = segment.endpoints.map do |v|
111
+ (v - @vertices[0]).to_v3di(internal_axes)
112
+ end
113
+ if ( (endpoints_v3di[0][2] == 0.0) && (endpoints_v3di[1][2] == 0.0) )
114
+ raise NoIntersectionError
115
+ end
116
+
117
+ # 面と直線の交点を求める。
118
+ # endpoints_v3di をベクトルと見たとき、 c 軸成分が 0 になるのは
119
+ # ベクトルの何倍か?
120
+ # ゼロ割りになる条件は、ここまでに弾いた条件に含まれる筈。
121
+ c_0 = endpoints_v3di[0][2]
122
+ c_1 = endpoints_v3di[1][2]
123
+ factor = c_0 / (c_0 - c_1)
124
+ intersection = (segment.endpoints[0] + (segment.to_v3d) * factor)
125
+
126
+ # 交点が線分上にあるか?すなわち両端点が面を挟んでいるか?
127
+ raise NoIntersectionError if c_0 * c_1 > 0
128
+
129
+ # 交点が三角形の内側にあるか?
130
+ if (! include?(intersection, tolerance))
131
+ raise NoIntersectionError
132
+ end
133
+
134
+ return intersection
135
+ end
136
+
137
+ # 線分と並行であることを判定する。
138
+ # 線分が三角形の面内の場合は false になる。
139
+ def parallel_segment?(segment)
140
+ #p segment.endpoints
141
+ t = segment.endpoints.map{|v|
142
+ v.to_v3di(internal_axes)
143
+ }
144
+
145
+ # 傾いてるときは常に false
146
+ return false if t[0][2] != t[1][2]
147
+
148
+ # 傾きがない場合、面に含まれていれば false
149
+ return false if t[0][2] == 0.0
150
+
151
+ # 残った場合が true
152
+ return true
153
+ end
154
+
155
+ # 法線ベクトルの1つを返す。
156
+ # 法線ベクトルは正反対の方向を示す 2つがあるが、
157
+ # 内部的には頂点の順が右ねじとなる方向(正確には外積の定義される方向)
158
+ # のものが選ばれる。
159
+ # また、長さが1に規格化されたものが返される。
160
+ def normal_vector
161
+ edge1 = (@vertices[1] - @vertices[0])
162
+ edge2 = (@vertices[2] - @vertices[1])
163
+ vec = edge1.exterior_product( edge2 )
164
+ normal = vec * (1.0/vec.r)
165
+
166
+ return normal
167
+ end
168
+
169
+ # 3つの頂点の座標が順不同で対応すれば真を返す。
170
+ # other が Triangle クラス以外のインスタンスなら例外 Triangle::TypeError を投げる。
171
+ # MEMO:
172
+ # 当初 eql? という名前を付けていたが、
173
+ # これは hash メソッドと関連があるので危険。
174
+ # よって別の名前の equivalent? というメソッド名にした。
175
+ # しかし eql? に依存したコードが残っているので当面 alias を残す。
176
+ # そのうち obsolete する。
177
+ def equivalent?(other, tolerance = 0.0)
178
+ raise TypeError unless other.class == Triangle
179
+
180
+ vertices.each do |v_self|
181
+ if (other.vertices.find{|v_other| v_self.equal_in_delta?(v_other, tolerance) })
182
+ next
183
+ else
184
+ return false
185
+ end
186
+ end
187
+ #return false unless other.vertices.include?(v)
188
+ return true
189
+
190
+ #vertices.each do |v|
191
+ # return false unless other.vertices.include?(v)
192
+ #end
193
+ #return true
194
+ end
195
+ alias eql? equivalent?
196
+
197
+ # 3つの頂点の座標が順序通りに対応すれば真を返す。
198
+ def ==(other)
199
+ vertices.size.times do |i|
200
+ return false unless @vertices[i] == other.vertices[i]
201
+ end
202
+ return true
203
+ end
204
+
205
+ def edges
206
+ results = []
207
+ results << Segment.new(@vertices[0], @vertices[1])
208
+ results << Segment.new(@vertices[1], @vertices[2])
209
+ results << Segment.new(@vertices[2], @vertices[0])
210
+ return results
211
+ end
212
+
213
+ private
214
+
215
+ # 三角形の2辺のベクトルと、これらからなる外積ベクトル、
216
+ # 合計3つのベクトルから Axes クラスインスタンスを作る。
217
+ # vertices で取り出せる3頂点のうち、0th 要素を原点とし、
218
+ # 1st, 2nd 要素をそれぞれ順に軸としたものとする。
219
+ def internal_axes
220
+ edge1 = (@vertices[1] - @vertices[0])
221
+ edge2 = (@vertices[2] - @vertices[0])
222
+ return Axes.new([edge1, edge2, normal_vector])
223
+ end
224
+
225
+ end