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 +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
|