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/lib/point3s.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'eymiha_math3'
|
2
|
+
|
3
|
+
# Point3s represents a 3D point in spherical coordinates:
|
4
|
+
# * s_radius is the distance from the origin.
|
5
|
+
# * theta is the angular distance around the cylindrical axis.
|
6
|
+
# * phi is angle that rotates the cylindrical axis to be directed at the point.
|
7
|
+
# The spherical theta coordinate is equal to the cylindrical theta.
|
8
|
+
#
|
9
|
+
# From a cartesian reference, the cylindrical axis is the z axis, theta is
|
10
|
+
# measured using the right hand rule with the positive x axis representing 0,
|
11
|
+
# and the phi is an angle measured from the positive z axis.
|
12
|
+
#
|
13
|
+
# Point3s instances may be converted to Point3 and Point3c instances, but
|
14
|
+
# information at the "boundaries" may be lost. Besides responding as a Point3s,
|
15
|
+
# an instance will also respond like a Point3 and Point3c as it has a full
|
16
|
+
# complement of readers for the different coordinate systems.
|
17
|
+
class Point3s
|
18
|
+
|
19
|
+
understands ThreeDimensions
|
20
|
+
|
21
|
+
# spherical radius coordinate reader and writer.
|
22
|
+
attr_accessor :s_radius
|
23
|
+
# spherical theta coordinate reader.
|
24
|
+
attr_reader :theta
|
25
|
+
# spherical phi coordinate reader.
|
26
|
+
attr_reader :phi
|
27
|
+
|
28
|
+
# spherical coordinate theta writer, rectifying theta to a value in
|
29
|
+
# [0, 2*pi).
|
30
|
+
def theta=(theta)
|
31
|
+
@theta = theta.rectify_theta
|
32
|
+
end
|
33
|
+
|
34
|
+
# spherical coordinate theta writer, rectifying theta to a value in
|
35
|
+
# [0, pi].
|
36
|
+
def phi=(phi)
|
37
|
+
@phi = phi.rectify_phi
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates and returns a Point3s instance. Sets the coordinates values using
|
41
|
+
# the set method.
|
42
|
+
def initialize(s_radius=0, theta=0, phi=0)
|
43
|
+
set s_radius, theta, phi
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a string representation of the instance.
|
47
|
+
def to_s
|
48
|
+
"Point3s: s_radius #{s_radius} theta #{theta} phi #{phi}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns a string representation of the instance.
|
52
|
+
# Sets the coordinate values of the instance. When
|
53
|
+
# * s_radius is Numeric, the arguments are interpretted as coordinates.
|
54
|
+
# * s_radius responds like a Point3c, its spherical coordinates are assigned.
|
55
|
+
# * otherwise a TypeError is raised.
|
56
|
+
# The modified instance is returned.
|
57
|
+
def set(s_radius=0, theta=0, phi=0)
|
58
|
+
if s_radius.kind_of? Numeric
|
59
|
+
@s_radius, @theta, @phi =
|
60
|
+
1.0*s_radius, (1.0*theta).rectify_theta, (1.0*phi).rectify_phi
|
61
|
+
elsif s_radius.point3s_like?
|
62
|
+
set s_radius.s_radius, s_radius.theta, s_radius.phi
|
63
|
+
else
|
64
|
+
raise_no_conversion s_radius
|
65
|
+
end
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns true if the coordinates of the instance are effectively equal to the
|
70
|
+
# coordinates of the given point.
|
71
|
+
def ==(point3s)
|
72
|
+
(point3s.s_radius == s_radius) &&
|
73
|
+
((s_radius == 0) || ((point3s.theta == theta) && (point3s.phi == phi)))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns true if the coordinates of the instance are approximately
|
77
|
+
# effectively equal to the coordinates of the given point, each coordinate
|
78
|
+
# less than a distance epsilon from the target.
|
79
|
+
def approximately_equals?(point3s,epsilon=Numeric.epsilon)
|
80
|
+
(@s_radius.approximately_equals?(point3s.s_radius,epsilon)&&
|
81
|
+
((@s_radius.approximately_equals?(0,epsilon) ||
|
82
|
+
(@theta.approximately_equals?(point3s.theta,epsilon)&&
|
83
|
+
@phi.approximately_equals?(point3s.phi,epsilon)))))
|
84
|
+
end
|
85
|
+
|
86
|
+
alias =~ approximately_equals?
|
87
|
+
|
88
|
+
# Returns a 3D point in cartesian coordinates, filling point3 if given,
|
89
|
+
# and copied from point3s.
|
90
|
+
def point3(point3=nil, point3s=self)
|
91
|
+
(point3 ||= Point3.new).set(point3s.x,point3s.y,point3s.z)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a copy of point3s with the given spherical coordinates:
|
95
|
+
# * s_radius is Numeric, the arguments are copied as the coordinates.
|
96
|
+
# * s_radius responds like a Point3c, its coordinates are copied.
|
97
|
+
# * otherwise a TypeError is raised.
|
98
|
+
def point3s(s_radius=self.s_radius,theta=self.theta,phi=self.phi)
|
99
|
+
Point3s.new(s_radius,theta,phi)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns a 3D point in cartesian coordinates, filling point3 if given,
|
103
|
+
# and copied from point3s.
|
104
|
+
def point3c(point3c=nil,point3s=self)
|
105
|
+
(point3c ||= Point3c.new).set(point3s.c_radius,point3s.theta,point3s.z)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns the cartesian x coordinate of the instance.
|
109
|
+
def x
|
110
|
+
s_radius*Math.cos(theta)*Math.sin(phi)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Returns the cartesian y coordinate of the instance.
|
114
|
+
def y
|
115
|
+
s_radius*Math.sin(theta)*Math.sin(phi)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns the cartesian z coordinate of the instance.
|
119
|
+
def z
|
120
|
+
s_radius*Math.cos(phi);
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns the cylindrical radius coordinate of the instance.
|
124
|
+
def c_radius
|
125
|
+
s_radius*Math.sin(phi)
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
data/lib/quaternion.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
require 'eymiha_math3'
|
2
|
+
|
3
|
+
# Quaternion instances represents Quaternions, complex numbers with one real
|
4
|
+
# part and three imaginary parts. While the math is a bit obscure, they can be
|
5
|
+
# used to manipulate 3D rotations without "gimbal lock", the problem of
|
6
|
+
# coincident viewing angle alignment problems at the boundaries, for example,
|
7
|
+
# where the difference between the x and y coordinates of the viewing vector
|
8
|
+
# are zero (ie. looking straight up or straight down.)
|
9
|
+
#
|
10
|
+
# Interestingly, Quaternions were first conceptualized by Hamilton in 1843,
|
11
|
+
# well before the widespread use of vector notation. While mostly put on the
|
12
|
+
# shelf, they still solve certain problems elegantly, and in some cases more
|
13
|
+
# quickly than matrix-based 3D calulations.
|
14
|
+
#
|
15
|
+
# The quaternion's real part is accessed through the real instance variable,
|
16
|
+
# the three complex parts as the quaternion's "axis" vector, a Point3.
|
17
|
+
class Quaternion
|
18
|
+
|
19
|
+
understands ThreeDimensions
|
20
|
+
|
21
|
+
# complex axis vector part reader and writer
|
22
|
+
attr_accessor :axis
|
23
|
+
# real part reader and writer
|
24
|
+
attr_accessor :real
|
25
|
+
|
26
|
+
# Creates and returns a Quaternion instance. Sets the coordinates values
|
27
|
+
# using the set method.
|
28
|
+
def initialize(axis=origin,real=0)
|
29
|
+
set axis, real
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a string representation of the instance.
|
33
|
+
def to_s
|
34
|
+
"Quaternion: axis: x #{axis.x} y #{axis.y} z #{axis.z} real #{real}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sets the axis and real values of the instance. When
|
38
|
+
# * axis is a Quaternion, the arguments are interpretted as the parts of a quaternion.
|
39
|
+
# * axis reponds like a Point3, the axis and real are used to set the quaternion's values.
|
40
|
+
# * otherwise a TypeError is raised.
|
41
|
+
# The modified instance is returned.
|
42
|
+
def set(axis=origin,real=0)
|
43
|
+
if axis.kind_of? Quaternion
|
44
|
+
set axis.axis, axis.real
|
45
|
+
elsif axis.point3_like?
|
46
|
+
(@axis ||= point3).set(axis)
|
47
|
+
@real = real
|
48
|
+
else
|
49
|
+
raise_no_conversion(self.class.name,axis)
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns true if the parts of the instance are equal to the
|
55
|
+
# parts of the given quaternion.
|
56
|
+
def ==(quaternion)
|
57
|
+
(@axis == quaternion.axis)&&(@real == quaternion.real)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns true if the parts of the instance are approximately
|
61
|
+
# equal to the parts of the given quaternion, each part
|
62
|
+
# less than a distance epsilon from the target.
|
63
|
+
def approximately_equals?(quaternion,epsilon=Numeric.epsilon)
|
64
|
+
(@axis.approximately_equals?(quaternion.axis,epsilon)&&
|
65
|
+
@real.approximately_equals?(quaternion.real,epsilon))
|
66
|
+
end
|
67
|
+
|
68
|
+
alias =~ approximately_equals?
|
69
|
+
|
70
|
+
# Returns a copy of a Quaternion with the given parts:
|
71
|
+
# * axis is a Quaternion, its parts are copied.
|
72
|
+
# * axis responds like a Point3, the arguments are copied.
|
73
|
+
# * otherwise a TypeError is raised.
|
74
|
+
def quaternion(axis=self.axis,real=self.real)
|
75
|
+
Quaternion.new(axis,real)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns a new Quaternion-based encoding of a rotation.
|
79
|
+
def Quaternion.from_axis_angle(axis,angle)
|
80
|
+
half_angle = angle/2.0
|
81
|
+
Quaternion.new Point3.new(axis).scale(Math.sin(half_angle)),
|
82
|
+
Math.cos(half_angle)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a new Quaternion encoded into a rotation.
|
86
|
+
def to_axis_angle
|
87
|
+
half_angle = Math.acos(@real)
|
88
|
+
sin_half_angle = Math.sin(half_angle)
|
89
|
+
axis = (sin_half_angle.abs < 0.00000001)?
|
90
|
+
Point3.new(1,0,0) : Point3.new(@axis).scale!(1.0/sin_half_angle)
|
91
|
+
Quaternion.new(axis,2.0*half_angle)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a new Quaternion instance whose parts are the original
|
95
|
+
# instance's with the given amounts added:
|
96
|
+
# * axis is a Quaternion, its parts are added.
|
97
|
+
# * axis responds like a Point3, the arguments are added.
|
98
|
+
# * otherwise a TypeError is raised.
|
99
|
+
def add(axis=origin,real=0)
|
100
|
+
quaternion.add!(axis,real)
|
101
|
+
end
|
102
|
+
|
103
|
+
alias + add
|
104
|
+
|
105
|
+
# Returns the modified instance with the arguments added.
|
106
|
+
def add!(axis=origin,real=0)
|
107
|
+
if axis.kind_of? Quaternion
|
108
|
+
add!(axis.axis,axis.real)
|
109
|
+
elsif axis.point3_like?
|
110
|
+
set(@axis+axis,@real+real)
|
111
|
+
else
|
112
|
+
raise_no_conversion axis
|
113
|
+
end
|
114
|
+
self
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns a new Quaternion instance whose parts are the original
|
118
|
+
# instance's with the given amounts subtracted:
|
119
|
+
# * axis is a Quaternion, its parts are subtracted.
|
120
|
+
# * axis responds like a Point3, the arguments are subtracted.
|
121
|
+
# * otherwise a TypeError is raised.
|
122
|
+
def subtract(axis=origin,real=0)
|
123
|
+
quaternion.subtract!(axis,real)
|
124
|
+
end
|
125
|
+
|
126
|
+
alias - subtract
|
127
|
+
|
128
|
+
# Returns the modified instance with the arguments subtracted.
|
129
|
+
def subtract!(axis=origin,real=0)
|
130
|
+
if axis.kind_of? Quaternion
|
131
|
+
subtract!(axis.axis,axis.real)
|
132
|
+
elsif axis.point3_like?
|
133
|
+
set(@axis-axis,@real-real)
|
134
|
+
else
|
135
|
+
raise_no_conversion axis
|
136
|
+
end
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns a new Quaternion instance whose parts are the original
|
141
|
+
# instance's multiplied by the given amounts:
|
142
|
+
# * axis is a Quaternion, the instance is multiplied by the axis' parts.
|
143
|
+
# * axis responds like a Point3, the instance is multiplied by the arguments.
|
144
|
+
# * otherwise a TypeError is raised.
|
145
|
+
def multiply(axis=origin,real=1)
|
146
|
+
quaternion.multiply!(axis,real)
|
147
|
+
end
|
148
|
+
|
149
|
+
alias * multiply
|
150
|
+
|
151
|
+
# Returns the modified instance multiplied by the arguments.
|
152
|
+
def multiply!(axis=origin,real=1)
|
153
|
+
if axis.kind_of? Quaternion
|
154
|
+
multiply!(axis.axis,axis.real)
|
155
|
+
elsif axis.point3_like?
|
156
|
+
r = (@real*real)-@axis.dot(axis)
|
157
|
+
p3 = axis.scale r
|
158
|
+
qp3 = @axis.scale real
|
159
|
+
cross = @axis.cross axis
|
160
|
+
a = p3.add!(qp3).add!(cross)
|
161
|
+
set(a,r)
|
162
|
+
else
|
163
|
+
raise_no_conversion axis
|
164
|
+
end
|
165
|
+
self
|
166
|
+
end
|
167
|
+
|
168
|
+
# Returns a new Quaternion instance whose parts are the original
|
169
|
+
# instance's multiplied by the scalar:
|
170
|
+
# * scalar is a Quaternion, the instance is multiplied by the scalar's parts.
|
171
|
+
# * axis is a Numeric, the instance's parts are multiplied by the scalar.
|
172
|
+
# * otherwise a TypeError is raised.
|
173
|
+
def scale(scalar=1)
|
174
|
+
quaternion.scale!(scalar)
|
175
|
+
end
|
176
|
+
|
177
|
+
# Returns the modified instance multiplied by the scalar.
|
178
|
+
def scale!(scalar=1)
|
179
|
+
if (scalar.kind_of? Quaternion)
|
180
|
+
multiply!(scalar)
|
181
|
+
elsif (scalar.kind_of? Numeric)
|
182
|
+
set(@axis.scale(scalar),@real*scalar)
|
183
|
+
else
|
184
|
+
raise_no_conversion scalar, "Numeric"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns a new Quaternion instance that is the conjugate of the original
|
189
|
+
# instance.
|
190
|
+
def conjugate
|
191
|
+
quaternion.conjugate!
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns the modified instance replaced with its conjugate.
|
195
|
+
def conjugate!
|
196
|
+
set(@axis.mirror,@real)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Returns the norm of the instance.
|
200
|
+
def norm
|
201
|
+
(@real * @real) + @axis.modulus
|
202
|
+
end
|
203
|
+
|
204
|
+
# Returns the absolute value of the instance.
|
205
|
+
def abs
|
206
|
+
Math.sqrt(norm)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Returns a new Quaternion instance that is the inverse of the original
|
210
|
+
# instance.
|
211
|
+
def inverse
|
212
|
+
quaternion.inverse!
|
213
|
+
end
|
214
|
+
|
215
|
+
# Returns the modified instance replaced with its inverse.
|
216
|
+
def inverse!
|
217
|
+
conjugate!
|
218
|
+
scale!(1.0/norm)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns a new Quaternion instance whose parts are the original
|
222
|
+
# instance's divided by the given amounts:
|
223
|
+
# * axis is a Quaternion, the instance is divided by the axis' parts.
|
224
|
+
# * axis responds like a Point3, the instance is divided by the arguments.
|
225
|
+
# * otherwise a TypeError is raised.
|
226
|
+
def divide(axis=origin,real=1)
|
227
|
+
quaternion.divide!(axis,origin)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns the modified instance divided by the arguments.
|
231
|
+
def divide!(axis=origin,real=1)
|
232
|
+
if axis.kind_of? Quaternion
|
233
|
+
divide!(axis.axis,axis.real)
|
234
|
+
elsif axis.point3_like?
|
235
|
+
multiply!(quaternion(axis,real).inverse!)
|
236
|
+
else
|
237
|
+
raise_no_conversion axis
|
238
|
+
end
|
239
|
+
self
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'envelope3'
|
4
|
+
|
5
|
+
class TC_Envelope3 < Test::Unit::TestCase
|
6
|
+
|
7
|
+
understands ThreeDimensions
|
8
|
+
|
9
|
+
def test_empty
|
10
|
+
envelope3 = Envelope3.new
|
11
|
+
assert(envelope3.count == 0)
|
12
|
+
assert_raise(EnvelopeException) { envelope3.high }
|
13
|
+
assert_raise(EnvelopeException) { envelope3.low }
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_add
|
17
|
+
envelope3 = Envelope3.new
|
18
|
+
p3 = point3 1,2,3
|
19
|
+
envelope3.add p3
|
20
|
+
assert(envelope3.high == p3)
|
21
|
+
assert(envelope3.low == p3)
|
22
|
+
envelope3.add 2,4,6
|
23
|
+
assert(envelope3.high == point3(2,4,6))
|
24
|
+
assert(envelope3.low == point3(1,2,3))
|
25
|
+
assert(envelope3.count == 2)
|
26
|
+
envelope3.add point3(-3,5,-7)
|
27
|
+
envelope3.add point3(4.5,-3,1)
|
28
|
+
assert(envelope3.high == point3(4.5,5,6))
|
29
|
+
assert(envelope3.low == point3(-3,-3,-7))
|
30
|
+
assert(envelope3.count == 4)
|
31
|
+
envelope3.add envelope3
|
32
|
+
assert(envelope3.high == point3(4.5,5,6))
|
33
|
+
assert(envelope3.low == point3(-3,-3,-7))
|
34
|
+
assert(envelope3.count == 8)
|
35
|
+
assert_raise(EnvelopeException) { envelope3.add "bad" }
|
36
|
+
assert(envelope3.count == 8)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_contains
|
40
|
+
envelope3 = Envelope3.new(1,2,3).add(3,4,5)
|
41
|
+
assert(envelope3.contains?(1,2,3))
|
42
|
+
assert(envelope3.contains?(3,4,5))
|
43
|
+
assert(envelope3.contains?(2,3,4))
|
44
|
+
assert(!(envelope3.contains?(0,3,4)))
|
45
|
+
assert(!(envelope3.contains?(4,3,4)))
|
46
|
+
assert(!(envelope3.contains?(2,1,4)))
|
47
|
+
assert(!(envelope3.contains?(2,5,4)))
|
48
|
+
assert(!(envelope3.contains?(2,3,2)))
|
49
|
+
assert(!(envelope3.contains?(2,3,6)))
|
50
|
+
assert(envelope3.contains?(point3(1,2,3)))
|
51
|
+
assert(envelope3.contains?(point3(3,4,5)))
|
52
|
+
assert(envelope3.contains?(point3(2,3,4)))
|
53
|
+
assert(!(envelope3.contains?(point3(0,3,4))))
|
54
|
+
assert(!(envelope3.contains?(point3(4,3,4))))
|
55
|
+
assert(!(envelope3.contains?(point3(2,1,4))))
|
56
|
+
assert(!(envelope3.contains?(point3(2,5,4))))
|
57
|
+
assert(!(envelope3.contains?(point3(2,3,2))))
|
58
|
+
assert(!(envelope3.contains?(point3(2,3,6))))
|
59
|
+
assert(envelope3.contains?(envelope3))
|
60
|
+
assert(envelope3.contains?(Envelope3.new(1.5,2.5,3.5).add(2.5,3.5,4.5)))
|
61
|
+
assert(!(envelope3.contains? Envelope3.new(0.5,2.5,3.5).add(2.5,3.5,4.5)))
|
62
|
+
assert(!(envelope3.contains? Envelope3.new(1.5,1.5,3.5).add(2.5,3.5,4.5)))
|
63
|
+
assert(!(envelope3.contains? Envelope3.new(1.5,2.5,2.5).add(2.5,3.5,4.5)))
|
64
|
+
assert(!(envelope3.contains? Envelope3.new(1.5,2.5,3.5).add(3.5,3.5,4.5)))
|
65
|
+
assert(!(envelope3.contains? Envelope3.new(1.5,2.5,3.5).add(2.5,4.5,4.5)))
|
66
|
+
assert(!(envelope3.contains? Envelope3.new(1.5,2.5,3.5).add(2.5,3.5,5.5)))
|
67
|
+
assert_raise(EnvelopeException) { envelope3.contains? "bad" }
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
data/test/tc_point3.rb
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
require 'point3'
|
4
|
+
|
5
|
+
class TC_Point3 < Test::Unit::TestCase
|
6
|
+
|
7
|
+
understands ThreeDimensions
|
8
|
+
|
9
|
+
def test_initialize_and_equality
|
10
|
+
p3 = Point3.new
|
11
|
+
assert(p3 == origin)
|
12
|
+
p3 = Point3.new 1,2,3
|
13
|
+
assert((p3.x == 1)&&(p3.y == 2)&&(p3.z == 3))
|
14
|
+
p3a = Point3.new 1,2,3
|
15
|
+
assert(p3 == p3a)
|
16
|
+
p3a = Point3.new 3,2,1
|
17
|
+
assert(p3 != p3a)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_Object_point3
|
21
|
+
p3 = point3
|
22
|
+
assert(p3 == origin)
|
23
|
+
p3 = point3 1,2,3
|
24
|
+
assert((p3.x == 1)&&(p3.y == 2)&&(p3.z == 3))
|
25
|
+
p3a = Point3.new 1,2,3
|
26
|
+
assert((p3a.x == 1)&&(p3a.y == 2)&&(p3a.z == 3))
|
27
|
+
assert(p3 == p3a)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_accessors
|
31
|
+
p3 = point3
|
32
|
+
assert(p3.point3_like?)
|
33
|
+
assert((p3.x == 0)&&(p3.y == 0)&&(p3.z == 0))
|
34
|
+
p3.x = 1
|
35
|
+
p3.y = 2
|
36
|
+
p3.z = 3
|
37
|
+
assert((p3.x == 1)&&(p3.y == 2)&&(p3.z == 3))
|
38
|
+
p3.x, p3.y, p3.z = 2, 4, 6
|
39
|
+
assert((p3.x == 2)&&(p3.y == 4)&&(p3.z == 6))
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_origin
|
43
|
+
p3 = origin
|
44
|
+
assert((p3.x == 0)&&(p3.y == 0)&&(p3.z == 0))
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_set
|
48
|
+
p3 = point3
|
49
|
+
p3.set 1, 2, 3
|
50
|
+
assert((p3.x == 1)&&(p3.y == 2)&&(p3.z == 3))
|
51
|
+
p3a = point3 p3
|
52
|
+
assert((p3a.x == 1)&&(p3a.y == 2)&&(p3a.z == 3))
|
53
|
+
p3b = point3
|
54
|
+
assert_raise(TypeError) { p3b.set "bad" }
|
55
|
+
assert_raise(TypeError) { point3 "bad" }
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_copy
|
59
|
+
p3 = point3 1, 2, 3
|
60
|
+
p3a = p3.point3
|
61
|
+
assert(p3 == p3a)
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_spherical_and_cylindrical
|
65
|
+
p3 = point3 1, 2, 3
|
66
|
+
assert(p3.s_radius =~ 3.74165738677394)
|
67
|
+
assert(p3.c_radius =~ 2.23606797749979)
|
68
|
+
assert(p3.theta =~ 1.10714871779409)
|
69
|
+
assert(p3.phi =~ 0.640522312679425)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_distance_to
|
73
|
+
p3a = point3 1, 2, 3
|
74
|
+
p3b = point3 5, -3, 4
|
75
|
+
assert(p3a.distance_to(p3b) =~ 6.48074069840786)
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_modulus
|
79
|
+
p3 = point3
|
80
|
+
assert(p3.modulus == 0)
|
81
|
+
p3.set 1, 2, 3
|
82
|
+
assert(p3.modulus =~ 3.74165738677394)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_dot
|
86
|
+
p3a = point3 1, 2, 3
|
87
|
+
p3b = point3 5, -3, 4
|
88
|
+
assert(p3a.dot(p3b) =~ 11)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_mirrors
|
92
|
+
p3a = point3 1, 2, 3
|
93
|
+
p3b = point3(-1, -2, -3)
|
94
|
+
assert(p3a.mirror == p3b)
|
95
|
+
p3a.mirror!
|
96
|
+
assert(p3a == p3b)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_adds
|
100
|
+
p3a = point3 1, 2, 3
|
101
|
+
p3d = point3 p3a
|
102
|
+
assert(p3a.add == p3d)
|
103
|
+
p3a.add!
|
104
|
+
assert(p3a == p3d)
|
105
|
+
p3b = point3 5, -3, 4
|
106
|
+
p3c = point3 6, -1, 7
|
107
|
+
assert(p3a.add(p3b) == p3c)
|
108
|
+
assert(p3a.add(p3b.x,p3b.y,p3b.z) == p3c)
|
109
|
+
assert(p3a+p3b == p3c)
|
110
|
+
p3a.add! p3b
|
111
|
+
assert(p3a == p3c)
|
112
|
+
p3d.add!(p3b.x,p3b.y,p3b.z)
|
113
|
+
assert(p3d == p3c)
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_subtracts
|
117
|
+
p3a = point3 1, 2, 3
|
118
|
+
p3d = point3 p3a
|
119
|
+
assert(p3a.subtract == p3d)
|
120
|
+
p3a.subtract!
|
121
|
+
assert(p3a == p3d)
|
122
|
+
p3b = point3 5, -3, 4
|
123
|
+
p3c = point3(-4, 5, -1)
|
124
|
+
assert(p3a.subtract(p3b) == p3c)
|
125
|
+
assert(p3a.subtract(p3b.x,p3b.y,p3b.z) == p3c)
|
126
|
+
assert(p3a-p3b == p3c)
|
127
|
+
p3a.subtract! p3b
|
128
|
+
assert(p3a == p3c)
|
129
|
+
p3d.subtract!(p3b.x,p3b.y,p3b.z)
|
130
|
+
assert(p3d == p3c)
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_multiplies
|
134
|
+
p3a = point3 1, 2, 3
|
135
|
+
p3d = point3 p3a
|
136
|
+
assert(p3a.multiply == p3d)
|
137
|
+
p3a.multiply!
|
138
|
+
assert(p3a == p3d)
|
139
|
+
p3b = point3 5, -3, 4
|
140
|
+
p3c = point3 5, -6, 12
|
141
|
+
assert(p3a.multiply(p3b) == p3c)
|
142
|
+
assert(p3a.multiply(p3b.x,p3b.y,p3b.z) == p3c)
|
143
|
+
assert(p3a*p3b == p3c)
|
144
|
+
p3a.multiply! p3b
|
145
|
+
assert(p3a == p3c)
|
146
|
+
p3d.multiply!(p3b.x,p3b.y,p3b.z)
|
147
|
+
assert(p3d == p3c)
|
148
|
+
end
|
149
|
+
|
150
|
+
def test_scales
|
151
|
+
p3a = point3 1, 2, 3
|
152
|
+
p3d = point3 p3a
|
153
|
+
assert(p3a.scale == p3d)
|
154
|
+
p3a.scale!
|
155
|
+
assert(p3a == p3d)
|
156
|
+
p3b = point3 3, 6, 9
|
157
|
+
p3c = point3 3, 12, 27
|
158
|
+
assert(p3a.scale(3) == p3b)
|
159
|
+
assert(p3a.scale(p3b) == p3c)
|
160
|
+
p3a.scale! 3
|
161
|
+
assert(p3a == p3b)
|
162
|
+
p3d.scale! p3b
|
163
|
+
assert(p3d == p3c)
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_units_and_approximately_equals
|
167
|
+
p3a = point3 1, 2, 3
|
168
|
+
p3b = point3 0.267261241912424, 0.534522483824849, 0.801783725737273
|
169
|
+
assert(p3a.unit =~ p3b)
|
170
|
+
p3a.unit!
|
171
|
+
assert(p3a =~ p3b)
|
172
|
+
p3a = point3 1, 2, 3
|
173
|
+
p3b = point3 0.2672, 0.5345, 0.8017
|
174
|
+
assert(p3a.unit.approximately_equals?(p3b,0.0001))
|
175
|
+
p3a.unit!
|
176
|
+
assert(p3a.approximately_equals?(p3b,0.0001))
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_cross
|
180
|
+
p3a = point3 1, 2, 3
|
181
|
+
p3b = point3 2, 4, 6
|
182
|
+
assert(p3a.cross(p3b) == origin)
|
183
|
+
p3b.cross! p3a
|
184
|
+
assert(p3b == origin)
|
185
|
+
p3c = point3 3, 2, 1
|
186
|
+
p3d = point3(-4, 8, -4)
|
187
|
+
assert(p3a.cross(p3c) == p3d)
|
188
|
+
p3a.cross! p3c
|
189
|
+
assert(p3a == p3d)
|
190
|
+
end
|
191
|
+
|
192
|
+
def test_to_along
|
193
|
+
p3a = point3 1, 2, 3
|
194
|
+
p3b = point3(-10, 3, 4)
|
195
|
+
p3c = p3a.to_along p3b, 0
|
196
|
+
assert(p3c =~ p3a)
|
197
|
+
p3c = p3a.to_along p3b, 1
|
198
|
+
assert(p3c =~ p3b)
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_point3s
|
202
|
+
p3a = point3 1, 2, 3
|
203
|
+
p3s = p3a.point3s
|
204
|
+
assert(p3s.s_radius =~ 3.74165738677394)
|
205
|
+
assert(p3s.theta =~ 1.10714871779409)
|
206
|
+
assert(p3s.phi =~ 0.640522312679425)
|
207
|
+
p3b = p3s.point3
|
208
|
+
assert(p3a =~ p3b)
|
209
|
+
end
|
210
|
+
|
211
|
+
def test_point3c
|
212
|
+
p3a = point3 1, 2, 3
|
213
|
+
p3c = p3a.point3c
|
214
|
+
assert(p3c.c_radius =~ 2.23606797749979)
|
215
|
+
assert(p3c.theta =~ 1.10714871779409)
|
216
|
+
assert(p3c.z =~ 3)
|
217
|
+
p3b = p3c.point3
|
218
|
+
assert(p3a =~ p3b)
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|