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 +33 -0
- data/lib/envelope3.rb +96 -0
- data/lib/eymiha_math3.rb +105 -0
- data/lib/point3.rb +276 -0
- data/lib/point3c.rb +123 -0
- data/lib/point3s.rb +128 -0
- data/lib/quaternion.rb +242 -0
- data/rakefile.rb +2 -0
- data/test/tc_envelope3.rb +70 -0
- data/test/tc_point3.rb +221 -0
- data/test/tc_point3c.rb +98 -0
- data/test/tc_point3s.rb +101 -0
- data/test/tc_quaternion.rb +148 -0
- metadata +75 -0
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
|
data/lib/eymiha_math3.rb
ADDED
@@ -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
|