eymiha_math3 1.0.1 → 1.0.2

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.
@@ -4,10 +4,10 @@ class GemPackage
4
4
 
5
5
  def initialize
6
6
  @name = 'eymiha_math3'
7
- @version = '1.0.1'
7
+ @version = '1.0.2'
8
8
  @files = FileList[
9
9
  '*.rb',
10
- 'lib/*',
10
+ 'lib/**/*',
11
11
  'test/*',
12
12
  'html/**/*'
13
13
  ]
@@ -0,0 +1,7 @@
1
+ require 'eymiha/math3/math3'
2
+
3
+ require 'eymiha/math3/envelope3'
4
+ require 'eymiha/math3/point3'
5
+ require 'eymiha/math3/point3c'
6
+ require 'eymiha/math3/point3s'
7
+ require 'eymiha/math3/quaternion'
@@ -0,0 +1,100 @@
1
+ require 'eymiha/math3'
2
+ require 'eymiha/util/envelope'
3
+
4
+ module Eymiha
5
+
6
+ # Envelope3 is the minimum 3D envelope that will completely contain a set of
7
+ # 3D points. 3D cartersian points or other 3D envelopes may be added to an
8
+ # instance, and it can return the number or points considered so far, the
9
+ # x, y and z envelopes, and the high and low Point3 boundaries.
10
+ class Envelope3 < BaseEnvelope
11
+
12
+ include ThreeDimensions
13
+
14
+ # 3D cartesian x coordinate envelope reader.
15
+ attr_reader :x
16
+ # 3D cartesian y coordinate envelope reader.
17
+ attr_reader :y
18
+ # 3D cartesian z coordinate envelope reader.
19
+ attr_reader :z
20
+
21
+ # Creates and returns an instance. If arguments are given, they are passed to
22
+ # the set method to initialize the new instance.
23
+ def initialize(x=nil,y=0,z=0)
24
+ super()
25
+ @x = Envelope.new
26
+ @y = Envelope.new
27
+ @z = Envelope.new
28
+ add(x,y,z) unless x == nil
29
+ end
30
+
31
+ # Returns a string representation of the instance.
32
+ def to_s
33
+ values = (count > 0)? "\n high #{high}\n low #{low}" : ""
34
+ "Envelope3: count #{count}#{values}"
35
+ end
36
+
37
+ # Adds a value to the instance. When
38
+ # * x is an Envelope3, it is coalesced into the instance.
39
+ # * x responds like a Point3, its coordinates are added.
40
+ # * x us Numeric, the arguments are added.
41
+ # * otherwise, an EnvelopeException is raised.
42
+ # The modified instance is returned.
43
+ def add(x=0,y=0,z=0)
44
+ if x.kind_of? Envelope3
45
+ count = x.count
46
+ if count > 0
47
+ add x.high
48
+ add x.low
49
+ @count += (count-2)
50
+ end
51
+ self
52
+ elsif x.point3_like?
53
+ add x.x, x.y, x.z
54
+ elsif x.kind_of? Numeric
55
+ @x.add x
56
+ @y.add y
57
+ @z.add z
58
+ @count += 1
59
+ self
60
+ else
61
+ raise_no_compare x
62
+ end
63
+ end
64
+
65
+ # Returns a Point3 whose coordinates are the high values of the x, y, and z
66
+ # envelopes.
67
+ # * if there are no boundaries, an EnvelopeException is raised.
68
+ def high
69
+ raise_no_envelope if @count == 0
70
+ (@high ||= Point3.new).set @x.high, @y.high, @z.high
71
+ end
72
+
73
+ # Returns a Point3 whose coordinates are the low values of the x, y, and z
74
+ # envelopes.
75
+ # * if there are no boundaries, an EnvelopeException is raised.
76
+ def low
77
+ raise_no_envelope if @count == 0
78
+ (@low ||= Point3.new).set @x.low, @y.low, @z.low
79
+ end
80
+
81
+ # Returns true if the instance completely contains the arguments:
82
+ # * x is an Envelope3, its high and low are contained.
83
+ # * x responds like a Point3, its coordinates are contained.
84
+ # * x is Numeric, the arguments are contained.
85
+ # * otherwise, false is returned.
86
+ def contains?(x=0,y=0,z=0)
87
+ if x.kind_of? Envelope3
88
+ (contains? x.high) && (contains? x.low)
89
+ elsif x.point3_like?
90
+ contains?(x.x,x.y,x.z)
91
+ elsif x.kind_of? Numeric
92
+ (@x.contains? x) && (@y.contains? y) && (@z.contains? z)
93
+ else
94
+ raise_no_compare x
95
+ end
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,100 @@
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 Eymiha
30
+ module ThreeDimensions
31
+
32
+ # shorthand for 2*pi.
33
+ def two_pi
34
+ 2.0*Math::PI
35
+ end
36
+
37
+ # shorthand for pi.
38
+ def pi
39
+ Math::PI
40
+ end
41
+
42
+ # shorthand for pi/2.
43
+ def half_pi
44
+ Math::PI/2.0
45
+ end
46
+
47
+ # shorthand for the square root of 2.
48
+ def sqrt2
49
+ Math.sqrt 2
50
+ end
51
+
52
+ # shorthand for the square root of 3.
53
+ def sqrt3
54
+ Math.sqrt 3
55
+ end
56
+
57
+ # the origin of 3D space.
58
+ def origin
59
+ Point3.origin
60
+ end
61
+
62
+ # shorthand for creating a 3D point in cartesian coordinates.
63
+ def point3(x=0, y=0, z=0)
64
+ Point3.new(x,y,z)
65
+ end
66
+
67
+ # shorthand for creating a 3D point in spherical coordinates.
68
+ def point3s(s_radius=0, theta=0, phi=0)
69
+ Point3s.new(s_radius,theta,phi)
70
+ end
71
+
72
+ # shorthand for creating a 3D point in cylindrical coordinates.
73
+ def point3c(c_radius=0, theta=0, z=0)
74
+ Point3c.new(c_radius,theta,z)
75
+ end
76
+
77
+ # shorthand for creating a quaternion.
78
+ def quaternion(axis=origin,real=0)
79
+ Quaternion.new(axis,real)
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+
86
+ class Numeric
87
+
88
+ include Eymiha::ThreeDimensions
89
+
90
+ # Returns the instance's value folded to lie between [0, 2*pi).
91
+ def rectify_theta
92
+ wrap_rectify two_pi
93
+ end
94
+
95
+ # Returns the instance's value cut to lie between [0, pi].
96
+ def rectify_phi
97
+ cut_rectify pi
98
+ end
99
+
100
+ end
@@ -0,0 +1,283 @@
1
+ require 'eymiha/math3'
2
+
3
+ module Eymiha
4
+
5
+ # Point3 represents a 3D point in cartesian coordinates:
6
+ # * x is the signed distance from the 3D origin along the x axis.
7
+ # * y is the signed distance from the 3D origin along the y axis.
8
+ # * z is the signed distance from the 3D origin along the z axis.
9
+ # The cylindrical z coordinate is equal to the cartesian z coordinate.
10
+ #
11
+ # Point3 instances may be converted to Point3s and Point3c instances, but
12
+ # information at the "boundaries" may be lost. Besides responding as a Point3,
13
+ # an instance will also respond like a Point3s and Point3c as it has a full
14
+ # complement of readers for the different coordinate systems.
15
+ class Point3
16
+
17
+ include ThreeDimensions
18
+
19
+ # x coordinate reader and writer.
20
+ attr_accessor :x
21
+ # y coordinate reader and writer.
22
+ attr_accessor :y
23
+ # z coordinate reader and writer.
24
+ attr_accessor :z
25
+
26
+ # Returns the origin of 3D space, where x, y, and z all have zero values.
27
+ def Point3.origin
28
+ @@origin3
29
+ end
30
+
31
+ # Creates and returns a Point3 instance. Sets the coordinates values using
32
+ # the set method.
33
+ def initialize(x=0, y=0, z=0)
34
+ set x, y, z
35
+ end
36
+
37
+ # Returns a string representation of the instance.
38
+ def to_s
39
+ "Point3: x #{x} y #{y} z #{z}"
40
+ end
41
+
42
+ # Sets the coordinate values of the instance. When
43
+ # * x is Numeric, the arguments are interpretted as coordinates.
44
+ # * x responds like a Point3, its cartesian coordinates are assigned.
45
+ # * otherwise a TypeError is raised.
46
+ # The modified instance is returned.
47
+ def set(x=0, y=0, z=0)
48
+ if x.kind_of? Numeric
49
+ @x, @y, @z = 1.0*x, 1.0*y, 1.0*z
50
+ elsif x.point3_like?
51
+ set x.x, x.y, x.z
52
+ else
53
+ raise_no_conversion x
54
+ end
55
+ self
56
+ end
57
+
58
+ # Returns true if the coordinates of the instance are equal to the
59
+ # coordinates of the given point.
60
+ def ==(point3)
61
+ (@x == point3.x)&&(@y == point3.y)&&(@z == point3.z)
62
+ end
63
+
64
+ # Returns true if the coordinates of the instance are approximately equal
65
+ # to the coordinates of the given point, each coordinate less than a distance
66
+ # epsilon from the target.
67
+ def approximately_equals?(point3,epsilon=Numeric.epsilon)
68
+ @x.approximately_equals?(point3.x,epsilon)&&
69
+ @y.approximately_equals?(point3.y,epsilon)&&
70
+ @z.approximately_equals?(point3.z,epsilon)
71
+ end
72
+
73
+ alias =~ approximately_equals?
74
+
75
+ # Returns a copy of point3 with the given cartesian coordinates:
76
+ # * x is Numeric, the arguments are copied as the coordinates.
77
+ # * x responds like a Point3, its coordinates are copied.
78
+ # * otherwise a TypeError is raised.
79
+ def point3(x=self.x,y=self.y,z=self.z)
80
+ Point3.new(x,y,z)
81
+ end
82
+
83
+ # Returns a 3D point in spherical coordinates, filling point3s if given,
84
+ # and copied from point3.
85
+ def point3s(point3s=nil,point3=self)
86
+ (point3s ||= Point3s.new).set(point3.s_radius,point3.theta,point3.phi)
87
+ end
88
+
89
+ # Returns a 3D point in cylindrical coordinates, filling point3c if given,
90
+ # and copied from point3.
91
+ def point3c(point3c=nil,point3=self)
92
+ (point3c ||= Point3c.new).set(point3.c_radius,point3.theta,point3.z)
93
+ end
94
+
95
+ # Returns the spherical radius coordinate of the instance.
96
+ def s_radius
97
+ modulus
98
+ end
99
+
100
+ # Returns the cylindrical radius coordinate of the instance.
101
+ def c_radius
102
+ Math.sqrt((@x*@x)+(@y*@y))
103
+ end
104
+
105
+ # Returns the spherical/cylindrical theta coordinate of the instance.
106
+ def theta
107
+ (@x == 0)? ((@y > 0)? @@pio2 : -@@pio2) : Math.atan2(@y,@x)
108
+ end
109
+
110
+ # Returns the spherical phi coordinate of the instance.
111
+ def phi
112
+ m = modulus
113
+ (m == 0)? 0 : Math.acos(z/m)
114
+ end
115
+
116
+ # Returns the 3D distance from the instance to point3.
117
+ def distance_to(point3)
118
+ Math.sqrt(((@x-point3.x)**2)+((@y-point3.y)**2)+((@z-point3.z)**2))
119
+ end
120
+
121
+ # Returns the 3D distance from the instance to the origin.
122
+ def modulus
123
+ distance_to(origin)
124
+ end
125
+
126
+ # Returns the dot product of the vectors represented by the instance and
127
+ # point3, with common endpoints at the origin.
128
+ def dot(point3)
129
+ (@x*point3.x)+(@y*point3.y)+(@z*point3.z)
130
+ end
131
+
132
+ # Returns a new Point3 instance whose coordinates are the original
133
+ # instance's mirrored through the origin.
134
+ def mirror
135
+ point3.mirror!
136
+ end
137
+
138
+ # Returns the modified instance whose coordinates have been mirrored through
139
+ # the origin.
140
+ def mirror!
141
+ set(-@x, -@y, -@z)
142
+ end
143
+
144
+ # Returns a new Point3 instance whose coordinates are the original
145
+ # instance's with the given amounts added:
146
+ # * x is Numeric, the arguments are added to the coordinates.
147
+ # * x responds like a Point3, its cartesian coordinates are added.
148
+ # * otherwise a TypeError is raised.
149
+ def add(x=0,y=0,z=0)
150
+ point3.add!(x,y,z)
151
+ end
152
+
153
+ alias + add
154
+
155
+ # Returns the modified instance with the arguments added.
156
+ def add!(x=0,y=0,z=0)
157
+ if x.kind_of? Numeric
158
+ set(@x+x, @y+y, @z+z)
159
+ elsif x.point3_like?
160
+ add! x.x, x.y, x.z
161
+ else
162
+ raise_no_conversion x
163
+ end
164
+ end
165
+
166
+ # Returns a new Point3 instance whose coordinates are the original
167
+ # instance's with the given amounts subtracted:
168
+ # * x is Numeric, the arguments are subtracted from the coordinates.
169
+ # * x responds like a Point3, its cartesian coordinates are subtracted.
170
+ # * otherwise a TypeError is raised.
171
+ def subtract(x=0,y=0,z=0)
172
+ point3.subtract!(x,y,z)
173
+ end
174
+
175
+ alias - subtract
176
+
177
+ # Returns the modified instance with the arguments subtracted.
178
+ def subtract!(x=0,y=0,z=0)
179
+ if x.kind_of? Numeric
180
+ add!(-x, -y, -z)
181
+ elsif x.point3_like?
182
+ subtract! x.x, x.y, x.z
183
+ else
184
+ raise_no_conversion x
185
+ end
186
+ end
187
+
188
+ # Returns a new Point3 instance whose coordinates are the original
189
+ # instance's multiplied by the given amounts:
190
+ # * x is Numeric, the coordinates are multiplied by the arguments.
191
+ # * x responds like a Point3, the instance's coordinates are multiplied by x's coordinates.
192
+ # * otherwise a TypeError is raised.
193
+ def multiply(x=1,y=1,z=1)
194
+ point3.multiply!(x,y,z)
195
+ end
196
+
197
+ alias * multiply
198
+
199
+ # Returns the modified instance as multiplied by the arguments.
200
+ def multiply!(x=1,y=1,z=1)
201
+ if x.kind_of? Numeric
202
+ set(@x*x, @y*y, @z*z)
203
+ elsif x.point3_like?
204
+ multiply! x.x, x.y, x.z
205
+ else
206
+ raise_no_conversion x
207
+ end
208
+ end
209
+
210
+ # Returns a new Point3 instance whose coordinates are the original
211
+ # instance's multiplied by the scalar.
212
+ # * scalar is Numeric, the arguments are multiplied by the coordinates.
213
+ # * x responds like a Point3, the instance is multiplied by the scalar.
214
+ # * otherwise a TypeError is raised.
215
+ def scale(scalar=1)
216
+ point3.scale!(scalar)
217
+ end
218
+
219
+ # Returns the modified instance as multiplied by the scalar.
220
+ def scale!(scalar=1)
221
+ if scalar.kind_of? Numeric
222
+ multiply! scalar, scalar, scalar
223
+ elsif scalar.point3_like?
224
+ multiply! scalar
225
+ else
226
+ raise_no_conversion scalar
227
+ end
228
+ end
229
+
230
+ # Returns a new Point3 instance representing the unit vector (with the same
231
+ # direction as the original instance, but whose length is 1.)
232
+ def unit(x=1,y=1,z=1)
233
+ point3.unit!
234
+ end
235
+
236
+ # Returns the modified instance as the unit vector.
237
+ def unit!(x=1,y=1,z=1)
238
+ scale!(1/modulus)
239
+ end
240
+
241
+ # Returns a new Point3 instance that is the cross product of the given
242
+ # arguments treated as vectors with endpoints at the origin:
243
+ # * x is Numeric, the cross product of the instance with the arguments.
244
+ # * x responds like a Point3,
245
+ # * y is Numeric, the cross product of the instance with x's coordinates.
246
+ # * y responds like a Point3, the cross product of x with y.
247
+ # * otherwise a TypeError is raised.
248
+ def cross(x=1/sqrt3,y=1/sqrt3,z=1/sqrt3)
249
+ point3.cross!(x,y,z)
250
+ end
251
+
252
+ # Returns the modified instance as the cross product.
253
+ def cross!(x=1/sqrt3,y=1/sqrt3,z=1/sqrt3)
254
+ if x.kind_of? Numeric
255
+ set((@y*z)-(@z*y), (@z*x)-(@x*z), (@x*y)-(@y*x))
256
+ elsif x.point3_like?
257
+ if y.kind_of? Numeric
258
+ cross! x.x, x.y, x.z
259
+ elsif y.point3_like?
260
+ set(x).cross!(y)
261
+ else
262
+ raise_no_conversion y
263
+ end
264
+ else
265
+ raise_no_conversion x
266
+ end
267
+ end
268
+
269
+ # Returns a new Point3 that is a distance d from the instance along the
270
+ # line to the Point3 e. If normalized is true, the d argument specifies
271
+ # the fraction of the distance from the instance (being 0) to e (being 1).
272
+ #If normalize is false, the d argument specifies an absolute distance.
273
+ def to_along (e, d, normalize=true)
274
+ scalar = normalize ? (distance_to e) : 1
275
+ point3(e).subtract!(self).unit!.scale!(d*scalar).add(self)
276
+ end
277
+
278
+ # The 3D origin
279
+ @@origin3 = Point3.new
280
+
281
+ end
282
+
283
+ end
@@ -0,0 +1,127 @@
1
+ require 'eymiha/math3'
2
+
3
+ module Eymiha
4
+
5
+ # Point3c represents a 3D point in cylindrical coordinates:
6
+ # * c_radius is the distance from the cylindrical axis.
7
+ # * theta is the angular distance around the cylindrical axis.
8
+ # * z is the distance from the cylindrical base-plane.
9
+ # The cylindrical theta coordinate is equal to the spherical theta. The
10
+ # cylindrical z coordinate is equal to the cartesian z coordinate.
11
+ #
12
+ # From a cartesian reference, the cylindrical axis is the z axis, theta is
13
+ # measured using the right hand rule with the positive x axis representing 0,
14
+ # and the cylindrical plane is the plane z=0.
15
+ #
16
+ # Point3c instances may be converted to Point3 and Point3s instances, but
17
+ # information at the "boundaries" may be lost. Besides responding as a Point3c,
18
+ # an instance will also respond like a Point3 and Point3s as it has a full
19
+ # complement of readers for the different coordinate systems.
20
+ class Point3c
21
+
22
+ include ThreeDimensions
23
+
24
+ # cylindrical radius coordinate reader and writer.
25
+ attr_accessor :c_radius
26
+ # cylindrical z coordinate reader and writer.
27
+ attr_accessor :z
28
+ # cylindrical theta reader.
29
+ attr_reader :theta
30
+
31
+ # cylindrical theta coordinate writer, rectifying theta to a value in
32
+ # [0, 2*pi).
33
+ def theta=(theta)
34
+ @theta = theta.rectify_theta
35
+ end
36
+
37
+ # Creates and returns a Point3c instance. Sets the coordinates values using
38
+ # the set method.
39
+ def initialize(c_radius=0, theta=0, z=0)
40
+ set c_radius, theta, z
41
+ end
42
+
43
+ # Returns a string representation of the instance.
44
+ def to_s
45
+ "Point3c: c_radius #{c_radius} theta #{theta} z #{z}"
46
+ end
47
+
48
+ # Sets the coordinate values of the instance. When
49
+ # * c_radius is Numeric, the arguments are interpretted as coordinates.
50
+ # * c_radius responds like a Point3c, its cylindrical coordinates are assigned.
51
+ # * otherwise a TypeError is raised.
52
+ # The modified instance is returned.
53
+ def set(c_radius=0, theta=0, z=0)
54
+ if c_radius.kind_of? Numeric
55
+ @c_radius, @theta, @z = 1.0*c_radius, (1.0*theta).rectify_theta, 1.0*z
56
+ elsif c_radius.point3c_like?
57
+ set c_radius.c_radius, c_radius.theta, c_radius.z
58
+ else
59
+ raise_no_conversion c_radius
60
+ end
61
+ self
62
+ end
63
+
64
+ # Returns true if the coordinates of the instance are effectively equal to the
65
+ # coordinates of the given point.
66
+ def ==(point3c)
67
+ ((point3c.c_radius == c_radius) && (point3c.z == z)) &&
68
+ (((c_radius == 0) && (z == 0)) || (point3c.theta == theta))
69
+ end
70
+
71
+ # Returns true if the coordinates of the instance are approximately
72
+ # effectively equal to the coordinates of the given point, each coordinate
73
+ # less than a distance epsilon from the target.
74
+ def approximately_equals?(point3c,epsilon=Numeric.epsilon)
75
+ (@c_radius.approximately_equals?(point3c.c_radius,epsilon) &&
76
+ @z.approximately_equals?(point3c.z,epsilon)) &&
77
+ ((@c_radius.approximately_equals?(0,epsilon) &&
78
+ @z.approximately_equals?(0,epsilon)) ||
79
+ @theta.approximately_equals?(point3c.theta,epsilon))
80
+ end
81
+
82
+ alias =~ approximately_equals?
83
+
84
+ # Returns a 3D point in cartesian coordinates, filling point3 if given,
85
+ # and copied from point3c.
86
+ def point3(point3=nil,point3c=self)
87
+ (point3 ||= Point3.new).set(point3c.x,point3c.y,point3c.z)
88
+ end
89
+
90
+ # Returns a 3D point in spherical coordinates, filling point3s if given,
91
+ # and copied from point3c.
92
+ def point3s(point3s=nil,point3c=self)
93
+ (point3s ||= Point3s.new).set(point3c.s_radius,point3c.theta,point3c.phi)
94
+ end
95
+
96
+ # Returns a copy of point3c with the given cylindrical coordinates:
97
+ # * c_radius is Numeric, the arguments are copied as the coordinates.
98
+ # * c_radius responds like a Point3c, its coordinates are copied.
99
+ # * otherwise a TypeError is raised.
100
+ def point3c(c_radius=self.c_radius,theta=self.theta,z=self.z)
101
+ Point3c.new(c_radius,theta,z)
102
+ end
103
+
104
+ # Returns the cartesian x coordinate of the instance.
105
+ def x
106
+ c_radius*Math.cos(theta)
107
+ end
108
+
109
+ # Returns the cartesian y coordinate of the instance.
110
+ def y
111
+ c_radius*Math.sin(theta)
112
+ end
113
+
114
+ # Returns the spherical radius coordinate of the instance.
115
+ def s_radius
116
+ Math.sqrt((x**2)+(y**2)+(z**2))
117
+ end
118
+
119
+ # Returns the spherical phi coordinate of the instance.
120
+ def phi
121
+ m = s_radius
122
+ (m == 0)? 0 : Math.acos(z/m)
123
+ end
124
+
125
+ end
126
+
127
+ end
@@ -0,0 +1,132 @@
1
+ require 'eymiha/math3'
2
+
3
+ module Eymiha
4
+
5
+ # Point3s represents a 3D point in spherical coordinates:
6
+ # * s_radius is the distance from the origin.
7
+ # * theta is the angular distance around the cylindrical axis.
8
+ # * phi is angle that rotates the cylindrical axis to be directed at the point.
9
+ # The spherical theta coordinate is equal to the cylindrical theta.
10
+ #
11
+ # From a cartesian reference, the cylindrical axis is the z axis, theta is
12
+ # measured using the right hand rule with the positive x axis representing 0,
13
+ # and the phi is an angle measured from the positive z axis.
14
+ #
15
+ # Point3s instances may be converted to Point3 and Point3c instances, but
16
+ # information at the "boundaries" may be lost. Besides responding as a Point3s,
17
+ # an instance will also respond like a Point3 and Point3c as it has a full
18
+ # complement of readers for the different coordinate systems.
19
+ class Point3s
20
+
21
+ include ThreeDimensions
22
+
23
+ # spherical radius coordinate reader and writer.
24
+ attr_accessor :s_radius
25
+ # spherical theta coordinate reader.
26
+ attr_reader :theta
27
+ # spherical phi coordinate reader.
28
+ attr_reader :phi
29
+
30
+ # spherical coordinate theta writer, rectifying theta to a value in
31
+ # [0, 2*pi).
32
+ def theta=(theta)
33
+ @theta = theta.rectify_theta
34
+ end
35
+
36
+ # spherical coordinate theta writer, rectifying theta to a value in
37
+ # [0, pi].
38
+ def phi=(phi)
39
+ @phi = phi.rectify_phi
40
+ end
41
+
42
+ # Creates and returns a Point3s instance. Sets the coordinates values using
43
+ # the set method.
44
+ def initialize(s_radius=0, theta=0, phi=0)
45
+ set s_radius, theta, phi
46
+ end
47
+
48
+ # Returns a string representation of the instance.
49
+ def to_s
50
+ "Point3s: s_radius #{s_radius} theta #{theta} phi #{phi}"
51
+ end
52
+
53
+ # Returns a string representation of the instance.
54
+ # Sets the coordinate values of the instance. When
55
+ # * s_radius is Numeric, the arguments are interpretted as coordinates.
56
+ # * s_radius responds like a Point3c, its spherical coordinates are assigned.
57
+ # * otherwise a TypeError is raised.
58
+ # The modified instance is returned.
59
+ def set(s_radius=0, theta=0, phi=0)
60
+ if s_radius.kind_of? Numeric
61
+ @s_radius, @theta, @phi =
62
+ 1.0*s_radius, (1.0*theta).rectify_theta, (1.0*phi).rectify_phi
63
+ elsif s_radius.point3s_like?
64
+ set s_radius.s_radius, s_radius.theta, s_radius.phi
65
+ else
66
+ raise_no_conversion s_radius
67
+ end
68
+ self
69
+ end
70
+
71
+ # Returns true if the coordinates of the instance are effectively equal to the
72
+ # coordinates of the given point.
73
+ def ==(point3s)
74
+ (point3s.s_radius == s_radius) &&
75
+ ((s_radius == 0) || ((point3s.theta == theta) && (point3s.phi == phi)))
76
+ end
77
+
78
+ # Returns true if the coordinates of the instance are approximately
79
+ # effectively equal to the coordinates of the given point, each coordinate
80
+ # less than a distance epsilon from the target.
81
+ def approximately_equals?(point3s,epsilon=Numeric.epsilon)
82
+ (@s_radius.approximately_equals?(point3s.s_radius,epsilon)&&
83
+ ((@s_radius.approximately_equals?(0,epsilon) ||
84
+ (@theta.approximately_equals?(point3s.theta,epsilon)&&
85
+ @phi.approximately_equals?(point3s.phi,epsilon)))))
86
+ end
87
+
88
+ alias =~ approximately_equals?
89
+
90
+ # Returns a 3D point in cartesian coordinates, filling point3 if given,
91
+ # and copied from point3s.
92
+ def point3(point3=nil, point3s=self)
93
+ (point3 ||= Point3.new).set(point3s.x,point3s.y,point3s.z)
94
+ end
95
+
96
+ # Returns a copy of point3s with the given spherical coordinates:
97
+ # * s_radius is Numeric, the arguments are copied as the coordinates.
98
+ # * s_radius responds like a Point3c, its coordinates are copied.
99
+ # * otherwise a TypeError is raised.
100
+ def point3s(s_radius=self.s_radius,theta=self.theta,phi=self.phi)
101
+ Point3s.new(s_radius,theta,phi)
102
+ end
103
+
104
+ # Returns a 3D point in cartesian coordinates, filling point3 if given,
105
+ # and copied from point3s.
106
+ def point3c(point3c=nil,point3s=self)
107
+ (point3c ||= Point3c.new).set(point3s.c_radius,point3s.theta,point3s.z)
108
+ end
109
+
110
+ # Returns the cartesian x coordinate of the instance.
111
+ def x
112
+ s_radius*Math.cos(theta)*Math.sin(phi)
113
+ end
114
+
115
+ # Returns the cartesian y coordinate of the instance.
116
+ def y
117
+ s_radius*Math.sin(theta)*Math.sin(phi)
118
+ end
119
+
120
+ # Returns the cartesian z coordinate of the instance.
121
+ def z
122
+ s_radius*Math.cos(phi);
123
+ end
124
+
125
+ # Returns the cylindrical radius coordinate of the instance.
126
+ def c_radius
127
+ s_radius*Math.sin(phi)
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,246 @@
1
+ require 'eymiha/math3'
2
+
3
+ module Eymiha
4
+
5
+ # Quaternion instances represents Quaternions, complex numbers with one real
6
+ # part and three imaginary parts. While the math is a bit obscure, they can be
7
+ # used to manipulate 3D rotations without "gimbal lock", the problem of
8
+ # coincident viewing angle alignment problems at the boundaries, for example,
9
+ # where the difference between the x and y coordinates of the viewing vector
10
+ # are zero (ie. looking straight up or straight down.)
11
+ #
12
+ # Interestingly, Quaternions were first conceptualized by Hamilton in 1843,
13
+ # well before the widespread use of vector notation. While mostly put on the
14
+ # shelf, they still solve certain problems elegantly, and in some cases more
15
+ # quickly than matrix-based 3D calulations.
16
+ #
17
+ # The quaternion's real part is accessed through the real instance variable,
18
+ # the three complex parts as the quaternion's "axis" vector, a Point3.
19
+ class Quaternion
20
+
21
+ include ThreeDimensions
22
+
23
+ # complex axis vector part reader and writer
24
+ attr_accessor :axis
25
+ # real part reader and writer
26
+ attr_accessor :real
27
+
28
+ # Creates and returns a Quaternion instance. Sets the coordinates values
29
+ # using the set method.
30
+ def initialize(axis=origin,real=0)
31
+ set axis, real
32
+ end
33
+
34
+ # Returns a string representation of the instance.
35
+ def to_s
36
+ "Quaternion: axis: x #{axis.x} y #{axis.y} z #{axis.z} real #{real}"
37
+ end
38
+
39
+ # Sets the axis and real values of the instance. When
40
+ # * axis is a Quaternion, the arguments are interpretted as the parts of a quaternion.
41
+ # * axis reponds like a Point3, the axis and real are used to set the quaternion's values.
42
+ # * otherwise a TypeError is raised.
43
+ # The modified instance is returned.
44
+ def set(axis=origin,real=0)
45
+ if axis.kind_of? Quaternion
46
+ set axis.axis, axis.real
47
+ elsif axis.point3_like?
48
+ (@axis ||= point3).set(axis)
49
+ @real = real
50
+ else
51
+ raise_no_conversion(self.class.name,axis)
52
+ end
53
+ self
54
+ end
55
+
56
+ # Returns true if the parts of the instance are equal to the
57
+ # parts of the given quaternion.
58
+ def ==(quaternion)
59
+ (@axis == quaternion.axis)&&(@real == quaternion.real)
60
+ end
61
+
62
+ # Returns true if the parts of the instance are approximately
63
+ # equal to the parts of the given quaternion, each part
64
+ # less than a distance epsilon from the target.
65
+ def approximately_equals?(quaternion,epsilon=Numeric.epsilon)
66
+ (@axis.approximately_equals?(quaternion.axis,epsilon)&&
67
+ @real.approximately_equals?(quaternion.real,epsilon))
68
+ end
69
+
70
+ alias =~ approximately_equals?
71
+
72
+ # Returns a copy of a Quaternion with the given parts:
73
+ # * axis is a Quaternion, its parts are copied.
74
+ # * axis responds like a Point3, the arguments are copied.
75
+ # * otherwise a TypeError is raised.
76
+ def quaternion(axis=self.axis,real=self.real)
77
+ Quaternion.new(axis,real)
78
+ end
79
+
80
+ # Returns a new Quaternion-based encoding of a rotation.
81
+ def Quaternion.from_axis_angle(axis,angle)
82
+ half_angle = angle/2.0
83
+ Quaternion.new Point3.new(axis).scale(Math.sin(half_angle)),
84
+ Math.cos(half_angle)
85
+ end
86
+
87
+ # Returns a new Quaternion encoded into a rotation.
88
+ def to_axis_angle
89
+ half_angle = Math.acos(@real)
90
+ sin_half_angle = Math.sin(half_angle)
91
+ axis = (sin_half_angle.abs < 0.00000001)?
92
+ Point3.new(1,0,0) : Point3.new(@axis).scale!(1.0/sin_half_angle)
93
+ Quaternion.new(axis,2.0*half_angle)
94
+ end
95
+
96
+ # Returns a new Quaternion instance whose parts are the original
97
+ # instance's with the given amounts added:
98
+ # * axis is a Quaternion, its parts are added.
99
+ # * axis responds like a Point3, the arguments are added.
100
+ # * otherwise a TypeError is raised.
101
+ def add(axis=origin,real=0)
102
+ quaternion.add!(axis,real)
103
+ end
104
+
105
+ alias + add
106
+
107
+ # Returns the modified instance with the arguments added.
108
+ def add!(axis=origin,real=0)
109
+ if axis.kind_of? Quaternion
110
+ add!(axis.axis,axis.real)
111
+ elsif axis.point3_like?
112
+ set(@axis+axis,@real+real)
113
+ else
114
+ raise_no_conversion axis
115
+ end
116
+ self
117
+ end
118
+
119
+ # Returns a new Quaternion instance whose parts are the original
120
+ # instance's with the given amounts subtracted:
121
+ # * axis is a Quaternion, its parts are subtracted.
122
+ # * axis responds like a Point3, the arguments are subtracted.
123
+ # * otherwise a TypeError is raised.
124
+ def subtract(axis=origin,real=0)
125
+ quaternion.subtract!(axis,real)
126
+ end
127
+
128
+ alias - subtract
129
+
130
+ # Returns the modified instance with the arguments subtracted.
131
+ def subtract!(axis=origin,real=0)
132
+ if axis.kind_of? Quaternion
133
+ subtract!(axis.axis,axis.real)
134
+ elsif axis.point3_like?
135
+ set(@axis-axis,@real-real)
136
+ else
137
+ raise_no_conversion axis
138
+ end
139
+ self
140
+ end
141
+
142
+ # Returns a new Quaternion instance whose parts are the original
143
+ # instance's multiplied by the given amounts:
144
+ # * axis is a Quaternion, the instance is multiplied by the axis' parts.
145
+ # * axis responds like a Point3, the instance is multiplied by the arguments.
146
+ # * otherwise a TypeError is raised.
147
+ def multiply(axis=origin,real=1)
148
+ quaternion.multiply!(axis,real)
149
+ end
150
+
151
+ alias * multiply
152
+
153
+ # Returns the modified instance multiplied by the arguments.
154
+ def multiply!(axis=origin,real=1)
155
+ if axis.kind_of? Quaternion
156
+ multiply!(axis.axis,axis.real)
157
+ elsif axis.point3_like?
158
+ r = (@real*real)-@axis.dot(axis)
159
+ p3 = axis.scale r
160
+ qp3 = @axis.scale real
161
+ cross = @axis.cross axis
162
+ a = p3.add!(qp3).add!(cross)
163
+ set(a,r)
164
+ else
165
+ raise_no_conversion axis
166
+ end
167
+ self
168
+ end
169
+
170
+ # Returns a new Quaternion instance whose parts are the original
171
+ # instance's multiplied by the scalar:
172
+ # * scalar is a Quaternion, the instance is multiplied by the scalar's parts.
173
+ # * axis is a Numeric, the instance's parts are multiplied by the scalar.
174
+ # * otherwise a TypeError is raised.
175
+ def scale(scalar=1)
176
+ quaternion.scale!(scalar)
177
+ end
178
+
179
+ # Returns the modified instance multiplied by the scalar.
180
+ def scale!(scalar=1)
181
+ if (scalar.kind_of? Quaternion)
182
+ multiply!(scalar)
183
+ elsif (scalar.kind_of? Numeric)
184
+ set(@axis.scale(scalar),@real*scalar)
185
+ else
186
+ raise_no_conversion scalar, "Numeric"
187
+ end
188
+ end
189
+
190
+ # Returns a new Quaternion instance that is the conjugate of the original
191
+ # instance.
192
+ def conjugate
193
+ quaternion.conjugate!
194
+ end
195
+
196
+ # Returns the modified instance replaced with its conjugate.
197
+ def conjugate!
198
+ set(@axis.mirror,@real)
199
+ end
200
+
201
+ # Returns the norm of the instance.
202
+ def norm
203
+ (@real * @real) + @axis.modulus
204
+ end
205
+
206
+ # Returns the absolute value of the instance.
207
+ def abs
208
+ Math.sqrt(norm)
209
+ end
210
+
211
+ # Returns a new Quaternion instance that is the inverse of the original
212
+ # instance.
213
+ def inverse
214
+ quaternion.inverse!
215
+ end
216
+
217
+ # Returns the modified instance replaced with its inverse.
218
+ def inverse!
219
+ conjugate!
220
+ scale!(1.0/norm)
221
+ end
222
+
223
+ # Returns a new Quaternion instance whose parts are the original
224
+ # instance's divided by the given amounts:
225
+ # * axis is a Quaternion, the instance is divided by the axis' parts.
226
+ # * axis responds like a Point3, the instance is divided by the arguments.
227
+ # * otherwise a TypeError is raised.
228
+ def divide(axis=origin,real=1)
229
+ quaternion.divide!(axis,origin)
230
+ end
231
+
232
+ # Returns the modified instance divided by the arguments.
233
+ def divide!(axis=origin,real=1)
234
+ if axis.kind_of? Quaternion
235
+ divide!(axis.axis,axis.real)
236
+ elsif axis.point3_like?
237
+ multiply!(quaternion(axis,real).inverse!)
238
+ else
239
+ raise_no_conversion axis
240
+ end
241
+ self
242
+ end
243
+
244
+ end
245
+
246
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eymiha_math3
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Anderson
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-09-15 00:00:00 -04:00
12
+ date: 2008-10-20 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -44,6 +44,14 @@ files:
44
44
  - gem_package.rb
45
45
  - rakefile.rb
46
46
  - lib/eymiha
47
+ - lib/eymiha/math3
48
+ - lib/eymiha/math3/envelope3.rb
49
+ - lib/eymiha/math3/math3.rb
50
+ - lib/eymiha/math3/point3.rb
51
+ - lib/eymiha/math3/point3c.rb
52
+ - lib/eymiha/math3/point3s.rb
53
+ - lib/eymiha/math3/quaternion.rb
54
+ - lib/eymiha/math3.rb
47
55
  - lib/eymiha_math3.rb
48
56
  - test/tc_envelope3.rb
49
57
  - test/tc_point3.rb