eymiha_math3 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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