eymiha_math3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/gem_package.rb ADDED
@@ -0,0 +1,33 @@
1
+ class GemPackage
2
+
3
+ attr_reader :name, :version, :files
4
+
5
+ def initialize
6
+ @name = 'eymiha_math3'
7
+ @version = '0.1.0'
8
+ @files = FileList[
9
+ '*.rb',
10
+ 'lib/*',
11
+ 'test/*',
12
+ 'html/**/*'
13
+ ]
14
+ end
15
+
16
+ def fill_spec(s)
17
+ s.name = name
18
+ s.version = version
19
+ s.summary = "Emiyha - basic 3D math extensions"
20
+ s.files = files.to_a
21
+ s.require_path = 'lib'
22
+ s.autorequire = name
23
+ s.has_rdoc = true
24
+ s.rdoc_options << "--all"
25
+ s.author = "Dave Anderson"
26
+ s.email = "dave@eymiha.com"
27
+ s.homepage = "http://www.eymiha.com"
28
+ s.rubyforge_project = "cori"
29
+ s.add_dependency("eymiha",">= 0.1.0")
30
+ s.add_dependency("eymiha_math",">= 0.1.0")
31
+ end
32
+
33
+ end
data/lib/envelope3.rb ADDED
@@ -0,0 +1,96 @@
1
+ require 'eymiha_math3'
2
+ require 'envelope'
3
+
4
+ # Envelope3 is the minimum 3D envelope that will completely contain a set of
5
+ # 3D points. 3D cartersian points or other 3D envelopes may be added to an
6
+ # instance, and it can return the number or points considered so far, the
7
+ # x, y and z envelopes, and the high and low Point3 boundaries.
8
+ class Envelope3 < BaseEnvelope
9
+
10
+ understands ThreeDimensions
11
+
12
+ # 3D cartesian x coordinate envelope reader.
13
+ attr_reader :x
14
+ # 3D cartesian y coordinate envelope reader.
15
+ attr_reader :y
16
+ # 3D cartesian z coordinate envelope reader.
17
+ attr_reader :z
18
+
19
+ # Creates and returns an instance. If arguments are given, they are passed to
20
+ # the set method to initialize the new instance.
21
+ def initialize(x=nil,y=0,z=0)
22
+ super()
23
+ @x = Envelope.new
24
+ @y = Envelope.new
25
+ @z = Envelope.new
26
+ add(x,y,z) unless x == nil
27
+ end
28
+
29
+ # Returns a string representation of the instance.
30
+ def to_s
31
+ values = (count > 0)? "\n high #{high}\n low #{low}" : ""
32
+ "Envelope3: count #{count}#{values}"
33
+ end
34
+
35
+ # Adds a value to the instance. When
36
+ # * x is an Envelope3, it is coalesced into the instance.
37
+ # * x responds like a Point3, its coordinates are added.
38
+ # * x us Numeric, the arguments are added.
39
+ # * otherwise, an EnvelopeException is raised.
40
+ # The modified instance is returned.
41
+ def add(x=0,y=0,z=0)
42
+ if x.kind_of? Envelope3
43
+ count = x.count
44
+ if count > 0
45
+ add x.high
46
+ add x.low
47
+ @count += (count-2)
48
+ end
49
+ self
50
+ elsif x.point3_like?
51
+ add x.x, x.y, x.z
52
+ elsif x.kind_of? Numeric
53
+ @x.add x
54
+ @y.add y
55
+ @z.add z
56
+ @count += 1
57
+ self
58
+ else
59
+ raise_no_compare x
60
+ end
61
+ end
62
+
63
+ # Returns a Point3 whose coordinates are the high values of the x, y, and z
64
+ # envelopes.
65
+ # * if there are no boundaries, an EnvelopeException is raised.
66
+ def high
67
+ raise_no_envelope if @count == 0
68
+ (@high ||= Point3.new).set @x.high, @y.high, @z.high
69
+ end
70
+
71
+ # Returns a Point3 whose coordinates are the low values of the x, y, and z
72
+ # envelopes.
73
+ # * if there are no boundaries, an EnvelopeException is raised.
74
+ def low
75
+ raise_no_envelope if @count == 0
76
+ (@low ||= Point3.new).set @x.low, @y.low, @z.low
77
+ end
78
+
79
+ # Returns true if the instance completely contains the arguments:
80
+ # * x is an Envelope3, its high and low are contained.
81
+ # * x responds like a Point3, its coordinates are contained.
82
+ # * x is Numeric, the arguments are contained.
83
+ # * otherwise, false is returned.
84
+ def contains?(x=0,y=0,z=0)
85
+ if x.kind_of? Envelope3
86
+ (contains? x.high) && (contains? x.low)
87
+ elsif x.point3_like?
88
+ contains?(x.x,x.y,x.z)
89
+ elsif x.kind_of? Numeric
90
+ (@x.contains? x) && (@y.contains? y) && (@z.contains? z)
91
+ else
92
+ raise_no_compare x
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,105 @@
1
+ require 'eymiha'
2
+ require 'eymiha_math'
3
+
4
+ # These modifications to objects add 3D point duck classifying.
5
+ class Object
6
+
7
+ # Returns true if the instance has 3D cartesian coordinates, ie. responds
8
+ # to x, y and z.
9
+ def point3_like?
10
+ (respond_to? :x)&&(respond_to? :y)&&(respond_to? :z)
11
+ end
12
+
13
+ # Returns true if the instance has 3D spherical coordinates, ie. responds
14
+ # to s_radius, theta and phi.
15
+ def point3s_like?
16
+ (respond_to? :s_radius)&&(respond_to? :theta)&&(respond_to? :phi)
17
+ end
18
+
19
+ # Returns true if the instance has 3D cylindrical coordinates, ie responds
20
+ # to c_radius, theta and z.
21
+ def point3c_like?
22
+ (respond_to? :c_radius)&&(respond_to? :theta)&&(respond_to? :z)
23
+ end
24
+
25
+ end
26
+
27
+ # The ThreeDimensions module adds shorthands for common 3D entities, mostly to
28
+ # improve code readability.
29
+ module ThreeDimensions
30
+
31
+ # shorthand for 2*pi.
32
+ def two_pi
33
+ 2.0*Math::PI
34
+ end
35
+
36
+ # shorthand for pi.
37
+ def pi
38
+ Math::PI
39
+ end
40
+
41
+ # shorthand for pi.
42
+ def half_pi
43
+ Math::PI/2.0
44
+ end
45
+
46
+ # shorthand for the square root of 2.
47
+ def sqrt2
48
+ Math.sqrt 2
49
+ end
50
+
51
+ # shorthand for the square root of 3.
52
+ def sqrt3
53
+ Math.sqrt 3
54
+ end
55
+
56
+ # the origin of 3D space.
57
+ def origin
58
+ Point3.origin
59
+ end
60
+
61
+ # shorthand for creating a 3D point in cartesian coordinates.
62
+ def point3(x=0, y=0, z=0)
63
+ Point3.new(x,y,z)
64
+ end
65
+
66
+ # shorthand for creating a 3D point in spherical coordinates.
67
+ def point3s(s_radius=0, theta=0, phi=0)
68
+ Point3s.new(s_radius,theta,phi)
69
+ end
70
+
71
+ # shorthand for creating a 3D point in cylindrical coordinates.
72
+ def point3c(c_radius=0, theta=0, z=0)
73
+ Point3c.new(c_radius,theta,z)
74
+ end
75
+
76
+ # shorthand for creating a quaternion.
77
+ def quaternion(axis=origin,real=0)
78
+ Quaternion.new(axis,real)
79
+ end
80
+
81
+ end
82
+
83
+
84
+ class Numeric
85
+
86
+ understands ThreeDimensions
87
+
88
+ # Returns the instance's value folded to lie between [0, 2*pi).
89
+ def rectify_theta
90
+ wrap_rectify two_pi
91
+ end
92
+
93
+ # Returns the instance's value cut to lie between [0, pi].
94
+ def rectify_phi
95
+ cut_rectify pi
96
+ end
97
+
98
+ end
99
+
100
+
101
+ require 'point3'
102
+ require 'point3s'
103
+ require 'point3c'
104
+ require 'envelope3'
105
+ require 'quaternion'
data/lib/point3.rb ADDED
@@ -0,0 +1,276 @@
1
+ require 'eymiha_math3'
2
+
3
+ # Point3 represents a 3D point in cartesian coordinates:
4
+ # * x is the signed distance from the 3D origin along the x axis.
5
+ # * y is the signed distance from the 3D origin along the y axis.
6
+ # * z is the signed distance from the 3D origin along the z axis.
7
+ # The cylindrical z coordinate is equal to the cartesian z coordinate.
8
+ #
9
+ # Point3 instances may be converted to Point3s and Point3c instances, but
10
+ # information at the "boundaries" may be lost. Besides responding as a Point3,
11
+ # an instance will also respond like a Point3s and Point3c as it has a full
12
+ # complement of readers for the different coordinate systems.
13
+ class Point3
14
+
15
+ understands ThreeDimensions
16
+
17
+ # x coordinate reader and writer.
18
+ attr_accessor :x
19
+ # y coordinate reader and writer.
20
+ attr_accessor :y
21
+ # z coordinate reader and writer.
22
+ attr_accessor :z
23
+
24
+ # Returns the origin of 3D space, where x, y, and z all have zero values.
25
+ def Point3.origin
26
+ @@origin3
27
+ end
28
+
29
+ # Creates and returns a Point3 instance. Sets the coordinates values using
30
+ # the set method.
31
+ def initialize(x=0, y=0, z=0)
32
+ set x, y, z
33
+ end
34
+
35
+ # Returns a string representation of the instance.
36
+ def to_s
37
+ "Point3: x #{x} y #{y} z #{z}"
38
+ end
39
+
40
+ # Sets the coordinate values of the instance. When
41
+ # * x is Numeric, the arguments are interpretted as coordinates.
42
+ # * x responds like a Point3, its cartesian coordinates are assigned.
43
+ # * otherwise a TypeError is raised.
44
+ # The modified instance is returned.
45
+ def set(x=0, y=0, z=0)
46
+ if x.kind_of? Numeric
47
+ @x, @y, @z = 1.0*x, 1.0*y, 1.0*z
48
+ elsif x.point3_like?
49
+ set x.x, x.y, x.z
50
+ else
51
+ raise_no_conversion x
52
+ end
53
+ self
54
+ end
55
+
56
+ # Returns true if the coordinates of the instance are equal to the
57
+ # coordinates of the given point.
58
+ def ==(point3)
59
+ (@x == point3.x)&&(@y == point3.y)&&(@z == point3.z)
60
+ end
61
+
62
+ # Returns true if the coordinates of the instance are approximately equal
63
+ # to the coordinates of the given point, each coordinate less than a distance
64
+ # epsilon from the target.
65
+ def approximately_equals?(point3,epsilon=Numeric.epsilon)
66
+ @x.approximately_equals?(point3.x,epsilon)&&
67
+ @y.approximately_equals?(point3.y,epsilon)&&
68
+ @z.approximately_equals?(point3.z,epsilon)
69
+ end
70
+
71
+ alias =~ approximately_equals?
72
+
73
+ # Returns a copy of point3 with the given cartesian coordinates:
74
+ # * x is Numeric, the arguments are copied as the coordinates.
75
+ # * x responds like a Point3, its coordinates are copied.
76
+ # * otherwise a TypeError is raised.
77
+ def point3(x=self.x,y=self.y,z=self.z)
78
+ Point3.new(x,y,z)
79
+ end
80
+
81
+ # Returns a 3D point in spherical coordinates, filling point3s if given,
82
+ # and copied from point3.
83
+ def point3s(point3s=nil,point3=self)
84
+ (point3s ||= Point3s.new).set(point3.s_radius,point3.theta,point3.phi)
85
+ end
86
+
87
+ # Returns a 3D point in cylindrical coordinates, filling point3c if given,
88
+ # and copied from point3.
89
+ def point3c(point3c=nil,point3=self)
90
+ (point3c ||= Point3c.new).set(point3.c_radius,point3.theta,point3.z)
91
+ end
92
+
93
+ # Returns the spherical radius coordinate of the instance.
94
+ def s_radius
95
+ modulus
96
+ end
97
+
98
+ # Returns the cylindrical radius coordinate of the instance.
99
+ def c_radius
100
+ Math.sqrt((@x*@x)+(@y*@y))
101
+ end
102
+
103
+ # Returns the spherical/cylindrical theta coordinate of the instance.
104
+ def theta
105
+ (@x == 0)? ((@y > 0)? @@pio2 : -@@pio2) : Math.atan2(@y,@x)
106
+ end
107
+
108
+ # Returns the spherical phi coordinate of the instance.
109
+ def phi
110
+ m = modulus
111
+ (m == 0)? 0 : Math.acos(z/m)
112
+ end
113
+
114
+ # Returns the 3D distance from the instance to point3.
115
+ def distance_to(point3)
116
+ Math.sqrt(((@x-point3.x)**2)+((@y-point3.y)**2)+((@z-point3.z)**2))
117
+ end
118
+
119
+ # Returns the 3D distance from the instance to the origin.
120
+ def modulus
121
+ distance_to(origin)
122
+ end
123
+
124
+ # Returns the dot product of the vectors represented by the instance and
125
+ # point3, with common endpoints at the origin.
126
+ def dot(point3)
127
+ (@x*point3.x)+(@y*point3.y)+(@z*point3.z)
128
+ end
129
+
130
+ # Returns a new Point3 instance whose coordinates are the original
131
+ # instance's mirrored through the origin.
132
+ def mirror
133
+ point3.mirror!
134
+ end
135
+
136
+ # Returns the modified instance whose coordinates have been mirrored through
137
+ # the origin.
138
+ def mirror!
139
+ set(-@x, -@y, -@z)
140
+ end
141
+
142
+ # Returns a new Point3 instance whose coordinates are the original
143
+ # instance's with the given amounts added:
144
+ # * x is Numeric, the arguments are added to the coordinates.
145
+ # * x responds like a Point3, its cartesian coordinates are added.
146
+ # * otherwise a TypeError is raised.
147
+ def add(x=0,y=0,z=0)
148
+ point3.add!(x,y,z)
149
+ end
150
+
151
+ alias + add
152
+
153
+ # Returns the modified instance with the arguments added.
154
+ def add!(x=0,y=0,z=0)
155
+ if x.kind_of? Numeric
156
+ set(@x+x, @y+y, @z+z)
157
+ elsif x.point3_like?
158
+ add! x.x, x.y, x.z
159
+ else
160
+ raise_no_conversion x
161
+ end
162
+ end
163
+
164
+ # Returns a new Point3 instance whose coordinates are the original
165
+ # instance's with the given amounts subtracted:
166
+ # * x is Numeric, the arguments are subtracted from the coordinates.
167
+ # * x responds like a Point3, its cartesian coordinates are subtracted.
168
+ # * otherwise a TypeError is raised.
169
+ def subtract(x=0,y=0,z=0)
170
+ point3.subtract!(x,y,z)
171
+ end
172
+
173
+ alias - subtract
174
+
175
+ # Returns the modified instance with the arguments subtracted.
176
+ def subtract!(x=0,y=0,z=0)
177
+ if x.kind_of? Numeric
178
+ add!(-x, -y, -z)
179
+ elsif x.point3_like?
180
+ subtract! x.x, x.y, x.z
181
+ else
182
+ raise_no_conversion x
183
+ end
184
+ end
185
+
186
+ # Returns a new Point3 instance whose coordinates are the original
187
+ # instance's multiplied by the given amounts:
188
+ # * x is Numeric, the coordinates are multiplied by the arguments.
189
+ # * x responds like a Point3, the instance's coordinates are multiplied by x's coordinates.
190
+ # * otherwise a TypeError is raised.
191
+ def multiply(x=1,y=1,z=1)
192
+ point3.multiply!(x,y,z)
193
+ end
194
+
195
+ alias * multiply
196
+
197
+ # Returns the modified instance as multiplied by the arguments.
198
+ def multiply!(x=1,y=1,z=1)
199
+ if x.kind_of? Numeric
200
+ set(@x*x, @y*y, @z*z)
201
+ elsif x.point3_like?
202
+ multiply! x.x, x.y, x.z
203
+ else
204
+ raise_no_conversion x
205
+ end
206
+ end
207
+
208
+ # Returns a new Point3 instance whose coordinates are the original
209
+ # instance's multiplied by the scalar.
210
+ # * scalar is Numeric, the arguments are multiplied by the coordinates.
211
+ # * x responds like a Point3, the instance is multiplied by the scalar.
212
+ # * otherwise a TypeError is raised.
213
+ def scale(scalar=1)
214
+ point3.scale!(scalar)
215
+ end
216
+
217
+ # Returns the modified instance as multiplied by the scalar.
218
+ def scale!(scalar=1)
219
+ if scalar.kind_of? Numeric
220
+ multiply! scalar, scalar, scalar
221
+ elsif scalar.point3_like?
222
+ multiply! scalar
223
+ else
224
+ raise_no_conversion scalar
225
+ end
226
+ end
227
+
228
+ # Returns a new Point3 instance representing the unit vector (with the same
229
+ # direction as the original instance, but whose length is 1.)
230
+ def unit(x=1,y=1,z=1)
231
+ point3.unit!
232
+ end
233
+
234
+ # Returns the modified instance as the unit vector.
235
+ def unit!(x=1,y=1,z=1)
236
+ scale!(1/modulus)
237
+ end
238
+
239
+ # Returns a new Point3 instance that is the cross product of the given
240
+ # arguments treated as vectors with endpoints at the origin:
241
+ # * x is Numeric, the cross product of the instance with the arguments.
242
+ # * x responds like a Point3,
243
+ # * y is Numeric, the cross product of the instance with x's coordinates.
244
+ # * y responds like a Point3, the cross product of x with y.
245
+ # * otherwise a TypeError is raised.
246
+ def cross(x=1/sqrt3,y=1/sqrt3,z=1/sqrt3)
247
+ point3.cross!(x,y,z)
248
+ end
249
+
250
+ # Returns the modified instance as the cross product.
251
+ def cross!(x=1/sqrt3,y=1/sqrt3,z=1/sqrt3)
252
+ if x.kind_of? Numeric
253
+ set((@y*z)-(@z*y), (@z*x)-(@x*z), (@x*y)-(@y*x))
254
+ elsif x.point3_like?
255
+ if y.kind_of? Numeric
256
+ cross! x.x, x.y, x.z
257
+ elsif y.point3_like?
258
+ set(x).cross!(y)
259
+ else
260
+ raise_no_conversion y
261
+ end
262
+ else
263
+ raise_no_conversion x
264
+ end
265
+ end
266
+
267
+ # Returns a new Point3 that is a distance d from the instance along the line to the Point3 e. If normalized is true, the d argument specifies the fraction of the distance from the instance (being 0) to e (being 1). If normalize is false, the d argument specifies an absolute distance.
268
+ def to_along (e, d, normalize=true)
269
+ scalar = normalize ? (distance_to e) : 1
270
+ point3(e).subtract!(self).unit!.scale!(d*scalar).add(self)
271
+ end
272
+
273
+ # The 3D origin
274
+ @@origin3 = Point3.new
275
+
276
+ end
data/lib/point3c.rb ADDED
@@ -0,0 +1,123 @@
1
+ require 'eymiha_math3'
2
+
3
+ # Point3c represents a 3D point in cylindrical coordinates:
4
+ # * c_radius is the distance from the cylindrical axis.
5
+ # * theta is the angular distance around the cylindrical axis.
6
+ # * z is the distance from the cylindrical base-plane.
7
+ # The cylindrical theta coordinate is equal to the spherical theta. The
8
+ # cylindrical z coordinate is equal to the cartesian z coordinate.
9
+ #
10
+ # From a cartesian reference, the cylindrical axis is the z axis, theta is
11
+ # measured using the right hand rule with the positive x axis representing 0,
12
+ # and the cylindrical plane is the plane z=0.
13
+ #
14
+ # Point3c instances may be converted to Point3 and Point3s instances, but
15
+ # information at the "boundaries" may be lost. Besides responding as a Point3c,
16
+ # an instance will also respond like a Point3 and Point3s as it has a full
17
+ # complement of readers for the different coordinate systems.
18
+ class Point3c
19
+
20
+ understands ThreeDimensions
21
+
22
+ # cylindrical radius coordinate reader and writer.
23
+ attr_accessor :c_radius
24
+ # cylindrical z coordinate reader and writer.
25
+ attr_accessor :z
26
+ # cylindrical theta reader.
27
+ attr_reader :theta
28
+
29
+ # cylindrical theta coordinate writer, rectifying theta to a value in
30
+ # [0, 2*pi).
31
+ def theta=(theta)
32
+ @theta = theta.rectify_theta
33
+ end
34
+
35
+ # Creates and returns a Point3c instance. Sets the coordinates values using
36
+ # the set method.
37
+ def initialize(c_radius=0, theta=0, z=0)
38
+ set c_radius, theta, z
39
+ end
40
+
41
+ # Returns a string representation of the instance.
42
+ def to_s
43
+ "Point3c: c_radius #{c_radius} theta #{theta} z #{z}"
44
+ end
45
+
46
+ # Sets the coordinate values of the instance. When
47
+ # * c_radius is Numeric, the arguments are interpretted as coordinates.
48
+ # * c_radius responds like a Point3c, its cylindrical coordinates are assigned.
49
+ # * otherwise a TypeError is raised.
50
+ # The modified instance is returned.
51
+ def set(c_radius=0, theta=0, z=0)
52
+ if c_radius.kind_of? Numeric
53
+ @c_radius, @theta, @z = 1.0*c_radius, (1.0*theta).rectify_theta, 1.0*z
54
+ elsif c_radius.point3c_like?
55
+ set c_radius.c_radius, c_radius.theta, c_radius.z
56
+ else
57
+ raise_no_conversion c_radius
58
+ end
59
+ self
60
+ end
61
+
62
+ # Returns true if the coordinates of the instance are effectively equal to the
63
+ # coordinates of the given point.
64
+ def ==(point3c)
65
+ ((point3c.c_radius == c_radius) && (point3c.z == z)) &&
66
+ (((c_radius == 0) && (z == 0)) || (point3c.theta == theta))
67
+ end
68
+
69
+ # Returns true if the coordinates of the instance are approximately
70
+ # effectively equal to the coordinates of the given point, each coordinate
71
+ # less than a distance epsilon from the target.
72
+ def approximately_equals?(point3c,epsilon=Numeric.epsilon)
73
+ (@c_radius.approximately_equals?(point3c.c_radius,epsilon) &&
74
+ @z.approximately_equals?(point3c.z,epsilon)) &&
75
+ ((@c_radius.approximately_equals?(0,epsilon) &&
76
+ @z.approximately_equals?(0,epsilon)) ||
77
+ @theta.approximately_equals?(point3c.theta,epsilon))
78
+ end
79
+
80
+ alias =~ approximately_equals?
81
+
82
+ # Returns a 3D point in cartesian coordinates, filling point3 if given,
83
+ # and copied from point3c.
84
+ def point3(point3=nil,point3c=self)
85
+ (point3 ||= Point3.new).set(point3c.x,point3c.y,point3c.z)
86
+ end
87
+
88
+ # Returns a 3D point in spherical coordinates, filling point3s if given,
89
+ # and copied from point3c.
90
+ def point3s(point3s=nil,point3c=self)
91
+ (point3s ||= Point3s.new).set(point3c.s_radius,point3c.theta,point3c.phi)
92
+ end
93
+
94
+ # Returns a copy of point3c with the given cylindrical coordinates:
95
+ # * c_radius is Numeric, the arguments are copied as the coordinates.
96
+ # * c_radius responds like a Point3c, its coordinates are copied.
97
+ # * otherwise a TypeError is raised.
98
+ def point3c(c_radius=self.c_radius,theta=self.theta,z=self.z)
99
+ Point3c.new(c_radius,theta,z)
100
+ end
101
+
102
+ # Returns the cartesian x coordinate of the instance.
103
+ def x
104
+ c_radius*Math.cos(theta)
105
+ end
106
+
107
+ # Returns the cartesian y coordinate of the instance.
108
+ def y
109
+ c_radius*Math.sin(theta)
110
+ end
111
+
112
+ # Returns the spherical radius coordinate of the instance.
113
+ def s_radius
114
+ Math.sqrt((x**2)+(y**2)+(z**2))
115
+ end
116
+
117
+ # Returns the spherical phi coordinate of the instance.
118
+ def phi
119
+ m = s_radius
120
+ (m == 0)? 0 : Math.acos(z/m)
121
+ end
122
+
123
+ end