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/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,2 @@
1
+ require 'gem_package'
2
+ require 'gem_raker'
@@ -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