eymiha_math3 0.1.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.
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