geom 0.0.1
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/.gitignore +18 -0
- data/Gemfile +8 -0
- data/Rakefile +2 -0
- data/geom.gemspec +17 -0
- data/lib/geom/line.rb +118 -0
- data/lib/geom/plane.rb +71 -0
- data/lib/geom/point.rb +148 -0
- data/lib/geom/rectangular_coordinate_system.rb +77 -0
- data/lib/geom/tolerance.rb +3 -0
- data/lib/geom/transformation.rb +93 -0
- data/lib/geom/vector.rb +173 -0
- data/lib/geom/version.rb +3 -0
- data/lib/geom.rb +13 -0
- data/spec/geom/line_spec.rb +125 -0
- data/spec/geom/plane_spec.rb +55 -0
- data/spec/geom/point_spec.rb +213 -0
- data/spec/geom/rectangular_coordinate_system_spec.rb +48 -0
- data/spec/geom/transformation_spec.rb +69 -0
- data/spec/geom/vector_spec.rb +193 -0
- data/spec/spec_helper.rb +7 -0
- metadata +82 -0
data/lib/geom/vector.rb
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
module Geom
|
2
|
+
# Vector defined by coordinates x, y, z
|
3
|
+
class Vector
|
4
|
+
attr_accessor :x, :y, :z
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
if args.size == 2
|
8
|
+
@x = args[1].x.to_f - args[0].x.to_f
|
9
|
+
@y = args[1].y.to_f - args[0].y.to_f
|
10
|
+
@z = args[1].z.to_f - args[0].z.to_f
|
11
|
+
else
|
12
|
+
@x, @y, @z = args.flatten
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def -(vector)
|
17
|
+
Vector.new(@x - vector.x, @y - vector.y, @z - vector.z)
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(vector)
|
21
|
+
Vector.new(@x + vector.x, @y + vector.y, @z + vector.z)
|
22
|
+
end
|
23
|
+
|
24
|
+
def *(scale)
|
25
|
+
Vector.new(@x * scale, @y * scale, @z * scale)
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(vector)
|
29
|
+
(@x - vector.x).abs < TOLERANCE &&
|
30
|
+
(@y - vector.y).abs < TOLERANCE &&
|
31
|
+
(@z - vector.z).abs < TOLERANCE
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :eql?, :==
|
35
|
+
|
36
|
+
def hash
|
37
|
+
(@x.to_int ^ @y.to_int ^ @z.to_int)
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method :scale, :*
|
41
|
+
|
42
|
+
def **(power)
|
43
|
+
Vector.new(@x ** power, @y ** power, @z ** power)
|
44
|
+
end
|
45
|
+
|
46
|
+
def /(scale)
|
47
|
+
self * (1.0/scale)
|
48
|
+
end
|
49
|
+
|
50
|
+
def dot(vector)
|
51
|
+
@x * vector.x + @y * vector.y + @z * vector.z
|
52
|
+
end
|
53
|
+
|
54
|
+
def cross(vector)
|
55
|
+
Vector.new(@y * vector.z - @z * vector.y,
|
56
|
+
@z * vector.x - @x * vector.z,
|
57
|
+
@x * vector.y - @y * vector.x)
|
58
|
+
end
|
59
|
+
|
60
|
+
def unitize
|
61
|
+
self / self.length
|
62
|
+
end
|
63
|
+
|
64
|
+
def reverse
|
65
|
+
Vector.new(-@x, -@y, -@z)
|
66
|
+
end
|
67
|
+
|
68
|
+
def length
|
69
|
+
Math.sqrt(self.dot(self))
|
70
|
+
end
|
71
|
+
|
72
|
+
def rotate(ref_vector, angle)
|
73
|
+
r = [[0,0,0],[0,0,0],[0,0,0]]
|
74
|
+
c = Math.cos(angle)
|
75
|
+
s = Math.sin(angle)
|
76
|
+
|
77
|
+
unit_ref_vector = ref_vector.unitize
|
78
|
+
|
79
|
+
ax = unit_ref_vector.x
|
80
|
+
ay = unit_ref_vector.y
|
81
|
+
az = unit_ref_vector.z
|
82
|
+
|
83
|
+
r[0][0] = (c + ((1 - c) * (ax * ax)))
|
84
|
+
r[0][1] = (((1 - c) * (ax * ay)) - (s * az))
|
85
|
+
r[0][2] = (((1 - c) * (ax * az)) + (s * ay))
|
86
|
+
r[1][0] = (((1 - c) * (ax * ay)) + (s * az))
|
87
|
+
r[1][1] = (c + ((1 - c) * (ay * ay)))
|
88
|
+
r[1][2] = (((1 - c) * (ay * az)) - (s * ax))
|
89
|
+
r[2][0] = (((1 - c) * (ax * az)) - (s * ay))
|
90
|
+
r[2][1] = (((1 - c) * (ay * az)) + (s * ax))
|
91
|
+
r[2][2] = (c + ((1 - c) * (az * az)))
|
92
|
+
|
93
|
+
vx = ((r[0][0] * @x) + ((r[0][1] * @y) + (r[0][2] * @z)))
|
94
|
+
vy = ((r[1][0] * @x) + ((r[1][1] * @y) + (r[1][2] * @z)))
|
95
|
+
vz = ((r[2][0] * @x) + ((r[2][1] * @y) + (r[2][2] * @z)))
|
96
|
+
|
97
|
+
Vector.new(vx, vy, vz)
|
98
|
+
end
|
99
|
+
|
100
|
+
def angle_between(vector)
|
101
|
+
# One of the vectors is a zero vector
|
102
|
+
return nil if ((vector.length == 0) || (self.length == 0))
|
103
|
+
|
104
|
+
dot = self.dot(vector)
|
105
|
+
val = dot / (self.length * vector.length)
|
106
|
+
|
107
|
+
if (val > 1.0)
|
108
|
+
# Would result in NaN
|
109
|
+
0.0
|
110
|
+
else
|
111
|
+
Math.acos(val)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def transform(rectangular_coordinate_system)
|
117
|
+
m = Transformation.new(rectangular_coordinate_system).matrix
|
118
|
+
tx = m[0,0] * @x + m[0,1] * @y + m[0,2] * @z + m[0,3] * 1.0
|
119
|
+
ty = m[1,0] * @x + m[1,1] * @y + m[1,2] * @z + m[1,3] * 1.0
|
120
|
+
tz = m[2,0] * @x + m[2,1] * @y + m[2,2] * @z + m[2,3] * 1.0
|
121
|
+
Vector.new(tx, ty, tz)
|
122
|
+
end
|
123
|
+
|
124
|
+
# If dot product > 0, angle is acute and vectors are the same direction
|
125
|
+
# If dot product < 0, angle is obtuse and vectors are in opposite direction
|
126
|
+
# If dot product = 0, vectors are orthogonal, including if one is zero vector (taken as same direction)
|
127
|
+
def same_direction?(vector)
|
128
|
+
dotp = self.dot(vector);
|
129
|
+
dotp > 0
|
130
|
+
end
|
131
|
+
|
132
|
+
def parallel?(vector)
|
133
|
+
angle = self.angle_between(vector)
|
134
|
+
((angle - Math::PI).abs < TOLERANCE) || (angle.abs < TOLERANCE)
|
135
|
+
end
|
136
|
+
|
137
|
+
def zero?
|
138
|
+
self.length <= 0.0
|
139
|
+
end
|
140
|
+
|
141
|
+
def unitity?
|
142
|
+
self.x == 1.0 && self.x == 1.0 && self.z == 1.0
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.average(vectors)
|
146
|
+
num = vectors.size.to_f
|
147
|
+
Vector.sum(vectors).scale(1/num)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.sum(vectors)
|
151
|
+
tx, ty, tz = 0, 0, 0
|
152
|
+
vectors.each do |vector|
|
153
|
+
tx += vector.x
|
154
|
+
ty += vector.y
|
155
|
+
tz += vector.z
|
156
|
+
end
|
157
|
+
Vector.new(tx, ty, tz)
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_point
|
161
|
+
Point.new(@x,@y,@z)
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_s
|
165
|
+
"Vector(%.3f,%.3f,%.3f)" % [@x, @y, @z]
|
166
|
+
end
|
167
|
+
|
168
|
+
def to_a
|
169
|
+
[@x, @y, @z]
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
data/lib/geom/version.rb
ADDED
data/lib/geom.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'geom/version'
|
2
|
+
require 'geom/tolerance'
|
3
|
+
require 'matrix'
|
4
|
+
require 'geom/point'
|
5
|
+
require 'geom/vector'
|
6
|
+
require 'geom/plane'
|
7
|
+
require 'geom/line'
|
8
|
+
require 'geom/transformation'
|
9
|
+
require 'geom/rectangular_coordinate_system'
|
10
|
+
|
11
|
+
module Geom
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
require 'geom/line'
|
3
|
+
|
4
|
+
module Geom
|
5
|
+
describe Line do
|
6
|
+
describe "Construction" do
|
7
|
+
before do
|
8
|
+
@valid_attributes = [1, 2, 3, 4, 5, 6]
|
9
|
+
@attributes = [:x0, :xa, :y0, :ya, :z0, :za]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should create a valid instance from an array of parameters" do
|
13
|
+
line = Line.new(@valid_attributes)
|
14
|
+
@attributes.each_with_index{|value, index| line.send(value).should == @valid_attributes[index] }
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create a valid instance from two points" do
|
18
|
+
line = Line.new(Point.new(1, 3, 5), Point.new(3, 7, 11))
|
19
|
+
@attributes.each_with_index{|value, index| line.send(value).should == @valid_attributes[index] }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "Equality" do
|
24
|
+
it "should determine equality with another line" do
|
25
|
+
line_1 = Line.new(Point.new(0, 0, 0), Point.new(2, 2, 2))
|
26
|
+
line_2 = Line.new(Point.new(0, 0, 0), Point.new(2, 2, 2))
|
27
|
+
line_1.should == line_2
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "Return types" do
|
32
|
+
it "should return a summary string" do
|
33
|
+
Line.new(1,2,3,1,0,0).to_s.should == "Line(1.000,2.000,3.000,1.000,0.000,0.000)"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return a hash code" do
|
37
|
+
Line.new(1,2.88,3,1,-45.111,101).hash.should == -73
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "Intersection" do
|
42
|
+
it "should determine intersection with another line" do
|
43
|
+
line_1 = Line.new(Point.new(0, 0, 0), Point.new(10, 10, 0))
|
44
|
+
line_2 = Line.new(Point.new(8, 0, 0), Point.new(8, 100, 0))
|
45
|
+
line_1.intersection_with_line(line_2).should == Point.new(8, 8, 0)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should raise exception when lines are parallel" do
|
49
|
+
line_1 = Line.new(1, 1, 3, -1, 0, 2)
|
50
|
+
line_2 = Line.new(1, 2, 3, -2, 0, 4)
|
51
|
+
lambda {line_1.intersection_with_line(line_2)}.should raise_error(ArgumentError, "Lines are parallel")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should raise exception when lines are skew" do
|
55
|
+
line_1 = Line.new(1, 1, 3, -1, 0, 2)
|
56
|
+
line_2 = Line.new(1, 2, 3, -2.5, 0.3, 4)
|
57
|
+
lambda {line_1.intersection_with_line(line_2)}.should raise_error(ArgumentError, "Lines do not intersect")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should calculation intersection with a plane" do
|
61
|
+
plane = Plane.new(Point.new(0, 0, 0), Vector.new(0, 0, 1))
|
62
|
+
line = Line.new(Point.new(2, 2, 10), Point.new(2, 2, 20))
|
63
|
+
line.intersection_with_plane(plane).should == Point.new(2, 2, 0)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "Parameters" do
|
68
|
+
it "should calculate closest approach parameter" do
|
69
|
+
line_1 = Line.new(Point.new(0, 0, 0), Point.new(1, 1, 0))
|
70
|
+
line_2 = Line.new(Point.new(0.5, -0.5, 0), Point.new(0.5, -0.1, 0))
|
71
|
+
line_1.closest_approach_parameter(line_2).should be_within(0.001).of(0.5)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should calculate point at parameter" do
|
75
|
+
line = Line.new(1, 1, 3, -1, 0, 2)
|
76
|
+
line.point_at_parameter(5).should == Point.new(6, -2, 10)
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "should calculate parameter" do
|
80
|
+
before do
|
81
|
+
@start_point = Point.new(-1, -1, -1)
|
82
|
+
@end_point = Point.new(1, 1, 1)
|
83
|
+
@mid_point = Point.new(0, 0, 0)
|
84
|
+
@line = Line.new(@start_point, @end_point)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "at start point" do
|
88
|
+
@line.parameter_at_point(@start_point).should == 0
|
89
|
+
end
|
90
|
+
|
91
|
+
it "at end point" do
|
92
|
+
@line.parameter_at_point(@end_point).should == 1
|
93
|
+
end
|
94
|
+
|
95
|
+
it "at mid point" do
|
96
|
+
@line.parameter_at_point(@mid_point).should == 0.5
|
97
|
+
end
|
98
|
+
|
99
|
+
it "at point beyond end point" do
|
100
|
+
@line.parameter_at_point(Point.new(10,10,10)).should == 5.5
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "should determine if a line is on a plane" do
|
106
|
+
before do
|
107
|
+
@plane = Plane.new(0, 0, 1, 0)
|
108
|
+
end
|
109
|
+
it "when a line is on a plane" do
|
110
|
+
line_1 = Line.new(Point.new(1, 1, 0), Point.new(2, -3, 0))
|
111
|
+
line_1.on_plane?(@plane).should be_true
|
112
|
+
end
|
113
|
+
|
114
|
+
it "when a line is parallel to a plane" do
|
115
|
+
line_2 = Line.new(Point.new(1, 1, 1), Point.new(2, -3, 1))
|
116
|
+
line_2.on_plane?(@plane).should be_false
|
117
|
+
end
|
118
|
+
it "when a line intersects plane" do
|
119
|
+
line_3 = Line.new(Point.new(1, 1, 0), Point.new(2, -3, 1))
|
120
|
+
line_3.on_plane?(@plane).should be_false
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
require 'geom/plane'
|
3
|
+
|
4
|
+
module Geom
|
5
|
+
describe Plane do
|
6
|
+
before do
|
7
|
+
@valid_attributes = [-10, 10, 0, 4]
|
8
|
+
@tol = 0.001
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "Construction" do
|
12
|
+
it "should create a valid instance from an array of coordinates" do
|
13
|
+
plane = Plane.new(@valid_attributes)
|
14
|
+
plane.a.should be_within(@tol).of(-0.707)
|
15
|
+
plane.b.should be_within(@tol).of(0.707)
|
16
|
+
plane.c.should be_within(@tol).of(0.0)
|
17
|
+
plane.d.should be_within(@tol).of(0.283)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should create a valid instance from three numbers" do
|
21
|
+
plane = Plane.new(Point.new(0, 3, 0), Vector.new(0, 3, 0))
|
22
|
+
plane.a.should == 0
|
23
|
+
plane.b.should == 1
|
24
|
+
plane.c.should == 0
|
25
|
+
plane.d.should == 3
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "Equality" do
|
30
|
+
it "should determine if another plane is equal" do
|
31
|
+
plane_1 = Plane.new(Point.new(0, 3, 0), Vector.new(0, 3, 0))
|
32
|
+
plane_2 = Plane.new(Point.new(0, 3, 0), Vector.new(0, 3, 0))
|
33
|
+
plane_1.should == plane_2
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should calculate the plane normal vector" do
|
37
|
+
test_plane = Plane.new(@valid_attributes)
|
38
|
+
normal = test_plane.normal
|
39
|
+
normal.x.should be_within(@tol).of(-0.707)
|
40
|
+
normal.y.should be_within(@tol).of(0.707)
|
41
|
+
normal.z.should be_within(@tol).of(0)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "Return types" do
|
46
|
+
it "should return a summary string" do
|
47
|
+
Plane.new(1,2,3,1).to_s.should == "Plane(0.267,0.535,0.802,0.267)"
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return a hash code" do
|
51
|
+
Plane.new(1,2.88,2,-45.111).hash.should == -12
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
require 'geom/point'
|
3
|
+
|
4
|
+
module Geom
|
5
|
+
describe Point do
|
6
|
+
before do
|
7
|
+
@valid_attributes = [1.1, -2, 10]
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "Construction" do
|
11
|
+
it "should create a valid instance from an array of coordinates" do
|
12
|
+
point = Point.new(@valid_attributes)
|
13
|
+
point.x.should == @valid_attributes[0]
|
14
|
+
point.y.should == @valid_attributes[1]
|
15
|
+
point.z.should == @valid_attributes[2]
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should create a valid instance from three numbers" do
|
19
|
+
point = Point.new(@valid_attributes[0], @valid_attributes[1], @valid_attributes[2])
|
20
|
+
point.x.should == @valid_attributes[0]
|
21
|
+
point.y.should == @valid_attributes[1]
|
22
|
+
point.z.should == @valid_attributes[2]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "Arithmetic" do
|
27
|
+
before do
|
28
|
+
@first_point = Point.new(0,0,0)
|
29
|
+
@second_point = Point.new(1,2,3)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should allow addition with another point" do
|
33
|
+
result = @first_point + @second_point
|
34
|
+
result.should == Point.new(1,2,3)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should allow subtraction with another point" do
|
38
|
+
result = @first_point - @second_point
|
39
|
+
result.should == Point.new(-1,-2,-3)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "Return Types" do
|
44
|
+
it "should return as point" do
|
45
|
+
Point.new(@valid_attributes).to_vector.should == Vector.new(@valid_attributes)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return as array" do
|
49
|
+
Point.new(@valid_attributes).to_a.should == @valid_attributes
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return a summary string" do
|
53
|
+
Point.new(@valid_attributes).to_s.should == "Point(1.100,-2.000,10.000)"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return a hash code" do
|
57
|
+
Point.new(1,2.88,-45.111).hash.should == -48
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "should calculate minimum distance" do
|
62
|
+
it "to another point" do
|
63
|
+
Point.new(0,0,0).distance_to_point(Point.new(1,3,2)).should == Math.sqrt(1*1 + 3*3 + 2*2)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "to a plane when the point is remote from the plane" do
|
67
|
+
plane_1 = Plane.new(3, 2, 6, 6)
|
68
|
+
point_1 = Point.new(1, 1, 3)
|
69
|
+
point_1.distance_to_plane(plane_1).should be_within(0.001).of(-2.429)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "to a plane when the point is on the plane" do
|
73
|
+
plane_2 = Plane.new(2, 3, 4, 20)
|
74
|
+
point_2 = Point.new(1, 2, 3)
|
75
|
+
point_2.distance_to_plane(plane_2).should be_within(0.001).of(0.0)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should calculated the average of an array of points" do
|
80
|
+
points = [Point.new(0,0,0), Point.new(1,1,1), Point.new(10,-10,2)]
|
81
|
+
average_point = Point.new(11/3.0, -9/3.0, 3/3.0)
|
82
|
+
Point.average(points).should == average_point
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "should determine coincidence" do
|
86
|
+
before do
|
87
|
+
@p1 = Point.new(1,-1,0)
|
88
|
+
@p2 = Point.new(1,-1,0)
|
89
|
+
@p3 = Point.new(1.1,-1,0)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "with another identical point" do
|
93
|
+
@p1.coincident?(@p2).should be_true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "with a non-identical point" do
|
97
|
+
@p1.coincident?(@p3).should be_false
|
98
|
+
end
|
99
|
+
|
100
|
+
it "from an array of points and remove any coincident points" do
|
101
|
+
Point.remove_coincident([Point.new(1,-1,0), Point.new(1,-1,0), Point.new(1,-1,0), Point.new(1.1,-1,0)]).size.should == 2
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "should determine if a point is between two points" do
|
106
|
+
before do
|
107
|
+
@p1 = Point.new(0,-1,0)
|
108
|
+
@p2 = Point.new(4,-1,0)
|
109
|
+
@p3 = Point.new(9,-1,0)
|
110
|
+
end
|
111
|
+
it "when the point is bounded" do
|
112
|
+
@p2.between?(@p1, @p3).should be_true
|
113
|
+
end
|
114
|
+
|
115
|
+
it "when the point is not bounded" do
|
116
|
+
@p1.between?(@p2, @p3).should be_false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "should determine if point is on plane" do
|
121
|
+
before do
|
122
|
+
@plane = Plane.new(0, 0, 1, 1)
|
123
|
+
@point_1 = Point.new(0, 0, 1)
|
124
|
+
@point_2 = Point.new(0, 0, 2)
|
125
|
+
@point_3 = Point.new(0, 0, 1.0000000001)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "when the point is on the plane" do
|
129
|
+
@point_1.on_plane?(@plane).should be_true
|
130
|
+
end
|
131
|
+
|
132
|
+
it "when the point is not on the plane" do
|
133
|
+
@point_2.on_plane?(@plane).should be_false
|
134
|
+
end
|
135
|
+
|
136
|
+
it "when the point is close but not on the plane" do
|
137
|
+
@point_3.on_plane?(@plane).should be_false
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "Projection" do
|
142
|
+
it "should be projected normal (dropped) onto plane" do
|
143
|
+
z_plane = Plane.new(0, 0, 1, 0)
|
144
|
+
oblique_plane = Plane.new(-1, 1, 0, 0)
|
145
|
+
oblique_offset_plane = Plane.new(0, 0, 1, 10)
|
146
|
+
|
147
|
+
point_1 = Point.new(1, 1, 1)
|
148
|
+
point_2 = Point.new(0, 1, 0)
|
149
|
+
|
150
|
+
point_1.project_onto_plane(z_plane).should == Point.new(1, 1, 0)
|
151
|
+
point_2.project_onto_plane(oblique_plane).should == Point.new(0.5, 0.5, 0)
|
152
|
+
point_1.project_onto_plane(oblique_offset_plane).should == Point.new(1, 1, 10)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should be projected along a vector onto plane" do
|
156
|
+
plane = Plane.new(Point.new(1,1,3), Point.new(0,0,3), Point.new(-1,1,3))
|
157
|
+
direction = Vector.new(1,1,4)
|
158
|
+
start_point = Point.new(5,5,0)
|
159
|
+
end_point = start_point.project_onto_plane_along_vector(plane, direction)
|
160
|
+
|
161
|
+
end_point.x.should be_within(0.001).of(5.75)
|
162
|
+
end_point.y.should be_within(0.001).of(5.75)
|
163
|
+
end_point.z.should be_within(0.001).of(3)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should be projected normal (dropped) onto line" do
|
167
|
+
line = Line.new(1, 1, 3, -1, 0, 2)
|
168
|
+
point_1 = Point.new(1, 1, 5)
|
169
|
+
point_1.project_onto_line(line).should == Point.new(3, 1, 4)
|
170
|
+
|
171
|
+
point_2 = Point.new(1, 3, 0)
|
172
|
+
point_2.project_onto_line(line).should == Point.new(1, 3, 0)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should be projected along a vector onto line" do
|
176
|
+
line = Line.new(Point.new(0, 0, 0), Point.new(2, 0, 0))
|
177
|
+
point = Point.new(0, 1, 0)
|
178
|
+
direction = Vector.new(1, -1, 0)
|
179
|
+
point.project_onto_line_along_vector(line, direction).should == Point.new(1, 0, 0)
|
180
|
+
point.should == Point.new(0, 1, 0)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe "Translation" do
|
185
|
+
it "should translate along a vector" do
|
186
|
+
p = Point.new(1,0,0)
|
187
|
+
v = Vector.new(1,0,0)
|
188
|
+
p.translate(v).should == Point.new(2,0,0)
|
189
|
+
p.should == Point.new(1,0,0)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should translate along a vector a specified distance" do
|
193
|
+
p = Point.new(0,0,1)
|
194
|
+
v = Vector.new(0,1,0)
|
195
|
+
p.translate(v, 10).should == Point.new(0,10,1)
|
196
|
+
p.should == Point.new(0,0,1)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "Transformation:" do
|
201
|
+
it "should transform into a coordinate system" do
|
202
|
+
p1 = Point.new(2,2,0)
|
203
|
+
p2 = Point.new(5,5,0)
|
204
|
+
vx = Vector.new(1,0,0)
|
205
|
+
vy = Vector.new(0,1,0)
|
206
|
+
vz = Vector.new(0,0,1)
|
207
|
+
rcs = RectangularCoordinateSystem.new_from_xvector_and_xyplane(p2, vy, vz)
|
208
|
+
p1.transform(rcs).should == Point.new(-3,3,0)
|
209
|
+
p1.should == Point.new(2,2,0)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
require 'geom/rectangular_coordinate_system'
|
3
|
+
|
4
|
+
module Geom
|
5
|
+
describe RectangularCoordinateSystem do
|
6
|
+
|
7
|
+
describe "Construction" do
|
8
|
+
before do
|
9
|
+
@origin = Point.new(0,0,0)
|
10
|
+
@ivector = Vector.new(1,0,0)
|
11
|
+
@jvector = Vector.new(0,1,0)
|
12
|
+
@kvector = Vector.new(0,0,1)
|
13
|
+
@rcs = RectangularCoordinateSystem.new
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should construct a RCS using x-vector and xy-plane normal vector" do
|
17
|
+
RectangularCoordinateSystem.new_from_xvector_and_xyplane(@origin, @ivector, @kvector).should == @rcs
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should construct a RCS using y-vector and yz-plane normal vector" do
|
21
|
+
RectangularCoordinateSystem.new_from_yvector_and_yzplane(@origin, @jvector, @ivector).should == @rcs
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should construct a RCS using z-vector and zx-plane normal vector" do
|
25
|
+
RectangularCoordinateSystem.new_from_zvector_and_zxplane(@origin, @kvector, @jvector).should == @rcs
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "Return types" do
|
30
|
+
it "should return a summary string" do
|
31
|
+
RectangularCoordinateSystem.new.to_s.should == "RCS[Point(0.000,0.000,0.000) X-Vector(1.000,0.000,0.000) Y-Vector(0.000,1.000,0.000) Z-Vector(0.000,0.000,1.000)]"
|
32
|
+
end
|
33
|
+
it "should print matrix formatted string" do
|
34
|
+
RectangularCoordinateSystem.new.to_s(true).should be
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should calculate a 4x4 transformation matrix" do
|
38
|
+
RectangularCoordinateSystem.new.transformation_matrix.should == Matrix[
|
39
|
+
[1.0, 0.0, 0.0, 0.0],
|
40
|
+
[0.0, 1.0, 0.0, 0.0],
|
41
|
+
[0.0, 0.0, 1.0, 0.0],
|
42
|
+
[0.0, 0.0, 0.0, 1.0]
|
43
|
+
]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require_relative '../spec_helper.rb'
|
2
|
+
require 'geom/transformation'
|
3
|
+
|
4
|
+
module Geom
|
5
|
+
describe Transformation do
|
6
|
+
before do
|
7
|
+
@p1 = Point.new(0,0,0)
|
8
|
+
@p2 = Point.new(10,1,1)
|
9
|
+
@vx = Vector.new(1,0,0)
|
10
|
+
@vy = Vector.new(0,1,0)
|
11
|
+
@vz = Vector.new(0,0,1)
|
12
|
+
@rcs_1 = RectangularCoordinateSystem.new_from_xvector_and_xyplane(@p1, @vx, @vz)
|
13
|
+
@rcs_2 = RectangularCoordinateSystem.new_from_xvector_and_xyplane(@p2, @vx, @vz)
|
14
|
+
@rcs_3 = RectangularCoordinateSystem.new_from_xvector_and_xyplane(@p1, @vy, @vz)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "Construction" do
|
18
|
+
it "should create a valid instance from a coordinate systems" do
|
19
|
+
transform = Transformation.new(RectangularCoordinateSystem.new)
|
20
|
+
transform.identity?.should be_true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "Type" do
|
25
|
+
it "should determine if transformation involves translation" do
|
26
|
+
transform = Transformation.new(@rcs_2)
|
27
|
+
transform.translation?.should be_true
|
28
|
+
transform.rotation?.should be_false
|
29
|
+
transform.scaling?.should be_false
|
30
|
+
transform.type.should == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should determine if transformation involved rotation" do
|
34
|
+
transform = Transformation.new(@rcs_3)
|
35
|
+
transform.translation?.should be_false
|
36
|
+
transform.rotation?.should be_true
|
37
|
+
transform.scaling?.should be_false
|
38
|
+
transform.type.should == 2
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "Return Types" do
|
43
|
+
it "should print matrix formatted string" do
|
44
|
+
Transformation.new(@rcs_1).to_s(true).should be
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should create a description" do
|
48
|
+
Transformation.new(@rcs_2).type_description.should == "Type 1 Translation"
|
49
|
+
Transformation.new(@rcs_3).type_description.should == "Type 2 Rotation"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should calculate translation vector" do
|
53
|
+
Transformation.new(@rcs_2).translation_vector.should == Vector.new(10.0, 1.0, 1.0)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "Rotation Matrix" do
|
58
|
+
it "should extract sub-matrix" do
|
59
|
+
transform = Transformation.new(@rcs_2)
|
60
|
+
transform.rotation_submatrix.should == Matrix.diagonal(1,1,1)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should determine if diagonal when only non-zeros on the diagonal" do
|
64
|
+
transform = Transformation.new(@rcs_2)
|
65
|
+
transform.rotation_submatrix_diagonal?.should be_true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|