ruby3mf 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +13 -13
- data/.rspec +2 -2
- data/bin/cli.rb +12 -12
- data/bin/console +14 -14
- data/bin/folder_test.sh +13 -13
- data/bin/suite_test.sh +41 -41
- data/lib/ruby3mf/3MFcoreSpec_1.1.xsd.template +188 -188
- data/lib/ruby3mf/content_types.rb +79 -77
- data/lib/ruby3mf/document.rb +242 -242
- data/lib/ruby3mf/edge_list.rb +60 -60
- data/lib/ruby3mf/errors.yml +191 -188
- data/lib/ruby3mf/log3mf.rb +135 -135
- data/lib/ruby3mf/mesh_analyzer.rb +80 -80
- data/lib/ruby3mf/mesh_normal_analyzer.rb +218 -218
- data/lib/ruby3mf/model3mf.rb +120 -117
- data/lib/ruby3mf/relationships.rb +50 -50
- data/lib/ruby3mf/schema_files.rb +26 -26
- data/lib/ruby3mf/texture3mf.rb +29 -29
- data/lib/ruby3mf/thumbnail3mf.rb +21 -21
- data/lib/ruby3mf/version.rb +3 -3
- data/lib/ruby3mf/xml.xsd +286 -286
- data/lib/ruby3mf/xml_val.rb +68 -68
- data/lib/ruby3mf.rb +26 -26
- data/ruby3mf.gemspec +32 -32
- metadata +3 -4
- data/suite.011917.out +0 -237
@@ -1,219 +1,219 @@
|
|
1
|
-
class MeshNormalAnalyzer
|
2
|
-
|
3
|
-
def initialize(mesh)
|
4
|
-
@vertices = []
|
5
|
-
@intersections = []
|
6
|
-
|
7
|
-
vertices_node = mesh.css("vertices")
|
8
|
-
vertices_node.children.each do |vertex_node|
|
9
|
-
if vertex_node.attributes.count > 0
|
10
|
-
x = vertex_node.attributes['x'].to_s.to_f
|
11
|
-
y = vertex_node.attributes['y'].to_s.to_f
|
12
|
-
z = vertex_node.attributes['z'].to_s.to_f
|
13
|
-
@vertices << [x, y, z]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
@triangles = []
|
18
|
-
triangles_node = mesh.css("triangles")
|
19
|
-
triangles_node.children.each do |triangle_node|
|
20
|
-
if triangle_node.attributes.count > 0
|
21
|
-
v1 = triangle_node.attributes['v1'].to_s.to_i
|
22
|
-
v2 = triangle_node.attributes['v2'].to_s.to_i
|
23
|
-
v3 = triangle_node.attributes['v3'].to_s.to_i
|
24
|
-
@triangles << [v1, v2, v3]
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def found_inward_triangle
|
30
|
-
# Trace a ray toward the center of the vertex points. This will hopefully
|
31
|
-
# maximize our chances of hitting the object's trianges on the first try.
|
32
|
-
center = point_cloud_center(@vertices)
|
33
|
-
|
34
|
-
@point = [0.0, 0.0, 0.0]
|
35
|
-
@direction = vector_to(@point, center)
|
36
|
-
|
37
|
-
# Make sure that we have a reasonably sized direction.
|
38
|
-
# Might end up with a zero length vector if the center is also
|
39
|
-
# at the origin.
|
40
|
-
if magnitude(@direction) < 0.1
|
41
|
-
@direction = [0.57, 0.57, 0.57]
|
42
|
-
end
|
43
|
-
|
44
|
-
# make the direction a unit vector just to make the
|
45
|
-
# debug info easier to understand
|
46
|
-
@direction = normalize(@direction)
|
47
|
-
|
48
|
-
attempts = 0
|
49
|
-
begin
|
50
|
-
# Get all of the intersections from the ray and put them in order of distance.
|
51
|
-
# The triangle we hit that's farthest from the start of the ray should always be
|
52
|
-
# a triangle that points away from us (otherwise we would hit a triangle even
|
53
|
-
# further away, assuming the mesh is closed).
|
54
|
-
#
|
55
|
-
# One special case is when the set of triangles we hit at that distance is greater
|
56
|
-
# than one. In that case we might have hit a "corner" of the model and so we don't
|
57
|
-
# know which of the two (or more) points away from us. In that case, cast a random
|
58
|
-
# ray from the center of the object and try again.
|
59
|
-
|
60
|
-
@triangles.each do |tri|
|
61
|
-
v1 = @vertices[tri[0]]
|
62
|
-
v2 = @vertices[tri[1]]
|
63
|
-
v3 = @vertices[tri[2]]
|
64
|
-
|
65
|
-
process_triangle(@point, @direction, [v1, v2, v3])
|
66
|
-
end
|
67
|
-
|
68
|
-
if @intersections.count > 0
|
69
|
-
# Sort the intersections so we can find the hits that are furthest away.
|
70
|
-
@intersections.sort! {|left, right| left[0] <=> right[0]}
|
71
|
-
|
72
|
-
max_distance = @intersections.last[0]
|
73
|
-
furthest_hits = @intersections.select{|hit| (hit[0]-max_distance).abs < 0.0001}
|
74
|
-
|
75
|
-
# Print out the hits
|
76
|
-
# furthest_hits.each {|hit| puts hit[1].to_s}
|
77
|
-
|
78
|
-
found_good_hit = furthest_hits.count == 1
|
79
|
-
end
|
80
|
-
|
81
|
-
if found_good_hit
|
82
|
-
outside_triangle = furthest_hits.last[2]
|
83
|
-
else
|
84
|
-
@intersections = []
|
85
|
-
attempts = attempts + 1
|
86
|
-
|
87
|
-
target = [Random.rand(10)/10.0, Random.rand(10)/10.0, Random.rand(10)/10.0]
|
88
|
-
@point = center
|
89
|
-
@direction = normalize(vector_to(@point, target))
|
90
|
-
end
|
91
|
-
end until found_good_hit || attempts >= 10
|
92
|
-
|
93
|
-
# return true if we hit a triangle with an inward pointing normal
|
94
|
-
# (according to counter-clockwise normal orientation)
|
95
|
-
found_good_hit && !compare_normals(outside_triangle, @direction)
|
96
|
-
end
|
97
|
-
|
98
|
-
def compare_normals(triangle, hit_direction)
|
99
|
-
oriented_normal = cross_product(
|
100
|
-
vector_to(triangle[0], triangle[1]),
|
101
|
-
vector_to(triangle[1], triangle[2]))
|
102
|
-
|
103
|
-
angle = angle_between(oriented_normal, hit_direction)
|
104
|
-
|
105
|
-
angle < Math::PI / 2.0
|
106
|
-
end
|
107
|
-
|
108
|
-
def process_triangle(point, direction, triangle)
|
109
|
-
found_intersection, t = intersect(point, direction, triangle)
|
110
|
-
|
111
|
-
if t > 0
|
112
|
-
intersection = []
|
113
|
-
intersection[0] = point[0] + t * direction[0]
|
114
|
-
intersection[1] = point[1] + t * direction[1]
|
115
|
-
intersection[2] = point[2] + t * direction[2]
|
116
|
-
|
117
|
-
@intersections << [t, intersection, triangle]
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def intersect(point, direction, triangle)
|
122
|
-
v0 = triangle[0]
|
123
|
-
v1 = triangle[1]
|
124
|
-
v2 = triangle[2]
|
125
|
-
|
126
|
-
return [false, 0] if v0.nil? || v1.nil? || v2.nil?
|
127
|
-
|
128
|
-
e1 = vector_to(v0, v1)
|
129
|
-
e2 = vector_to(v0, v2)
|
130
|
-
|
131
|
-
h = cross_product(direction, e2)
|
132
|
-
a = dot_product(e1, h)
|
133
|
-
|
134
|
-
if a.abs < 0.00001
|
135
|
-
return false, 0
|
136
|
-
end
|
137
|
-
|
138
|
-
f = 1.0/a
|
139
|
-
s = vector_to(v0, point)
|
140
|
-
u = f * dot_product(s, h)
|
141
|
-
|
142
|
-
if u < 0.0 || u > 1.0
|
143
|
-
return false, 0
|
144
|
-
end
|
145
|
-
|
146
|
-
q = cross_product(s, e1)
|
147
|
-
v = f * dot_product(direction, q)
|
148
|
-
|
149
|
-
if v < 0.0 || u + v > 1.0
|
150
|
-
return false, 0
|
151
|
-
end
|
152
|
-
|
153
|
-
t = f * dot_product(e2, q)
|
154
|
-
[t > 0, t]
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
# Various utility functions
|
159
|
-
|
160
|
-
def cross_product(a, b)
|
161
|
-
result = [0, 0, 0]
|
162
|
-
result[0] = a[1]*b[2] - a[2]*b[1]
|
163
|
-
result[1] = a[2]*b[0] - a[0]*b[2]
|
164
|
-
result[2] = a[0]*b[1] - a[1]*b[0]
|
165
|
-
|
166
|
-
result
|
167
|
-
end
|
168
|
-
|
169
|
-
def dot_product(a, b)
|
170
|
-
a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
|
171
|
-
end
|
172
|
-
|
173
|
-
def vector_to(a, b)
|
174
|
-
[b[0] - a[0], b[1] - a[1], b[2] - a[2]]
|
175
|
-
end
|
176
|
-
|
177
|
-
def magnitude(a)
|
178
|
-
Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2])
|
179
|
-
end
|
180
|
-
|
181
|
-
def equal(a, b)
|
182
|
-
(a[0] - b[0]).abs < 0.0001 && (a[1] - b[1]).abs < 0.0001 && (a[2] - b[2]).abs < 0.0001
|
183
|
-
end
|
184
|
-
|
185
|
-
def angle_between(a, b)
|
186
|
-
cos_theta = dot_product(a, b) / (magnitude(a) * magnitude(b))
|
187
|
-
Math.acos(cos_theta)
|
188
|
-
end
|
189
|
-
|
190
|
-
def normalize(a)
|
191
|
-
length = magnitude(a)
|
192
|
-
[a[0]/length, a[1]/length, a[2]/length]
|
193
|
-
end
|
194
|
-
|
195
|
-
def point_cloud_center(vertices)
|
196
|
-
if vertices.count < 1
|
197
|
-
return [0, 0, 0]
|
198
|
-
end
|
199
|
-
|
200
|
-
vertex = vertices[0]
|
201
|
-
min_x = max_x = vertex[0]
|
202
|
-
min_y = max_y = vertex[1]
|
203
|
-
min_z = max_z = vertex[2]
|
204
|
-
|
205
|
-
vertices.each do |vertex|
|
206
|
-
x = vertex[0]
|
207
|
-
y = vertex[1]
|
208
|
-
z = vertex[2]
|
209
|
-
|
210
|
-
min_x = x if x < min_x
|
211
|
-
max_x = x if x > max_x
|
212
|
-
min_y = y if y < min_y
|
213
|
-
max_y = y if y > max_y
|
214
|
-
min_z = z if z < min_z
|
215
|
-
max_z = z if z > max_z
|
216
|
-
end
|
217
|
-
|
218
|
-
[(min_x + max_x) / 2.0, (min_y + max_y) / 2.0, (min_z + max_z) / 2.0]
|
1
|
+
class MeshNormalAnalyzer
|
2
|
+
|
3
|
+
def initialize(mesh)
|
4
|
+
@vertices = []
|
5
|
+
@intersections = []
|
6
|
+
|
7
|
+
vertices_node = mesh.css("vertices")
|
8
|
+
vertices_node.children.each do |vertex_node|
|
9
|
+
if vertex_node.attributes.count > 0
|
10
|
+
x = vertex_node.attributes['x'].to_s.to_f
|
11
|
+
y = vertex_node.attributes['y'].to_s.to_f
|
12
|
+
z = vertex_node.attributes['z'].to_s.to_f
|
13
|
+
@vertices << [x, y, z]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
@triangles = []
|
18
|
+
triangles_node = mesh.css("triangles")
|
19
|
+
triangles_node.children.each do |triangle_node|
|
20
|
+
if triangle_node.attributes.count > 0
|
21
|
+
v1 = triangle_node.attributes['v1'].to_s.to_i
|
22
|
+
v2 = triangle_node.attributes['v2'].to_s.to_i
|
23
|
+
v3 = triangle_node.attributes['v3'].to_s.to_i
|
24
|
+
@triangles << [v1, v2, v3]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def found_inward_triangle
|
30
|
+
# Trace a ray toward the center of the vertex points. This will hopefully
|
31
|
+
# maximize our chances of hitting the object's trianges on the first try.
|
32
|
+
center = point_cloud_center(@vertices)
|
33
|
+
|
34
|
+
@point = [0.0, 0.0, 0.0]
|
35
|
+
@direction = vector_to(@point, center)
|
36
|
+
|
37
|
+
# Make sure that we have a reasonably sized direction.
|
38
|
+
# Might end up with a zero length vector if the center is also
|
39
|
+
# at the origin.
|
40
|
+
if magnitude(@direction) < 0.1
|
41
|
+
@direction = [0.57, 0.57, 0.57]
|
42
|
+
end
|
43
|
+
|
44
|
+
# make the direction a unit vector just to make the
|
45
|
+
# debug info easier to understand
|
46
|
+
@direction = normalize(@direction)
|
47
|
+
|
48
|
+
attempts = 0
|
49
|
+
begin
|
50
|
+
# Get all of the intersections from the ray and put them in order of distance.
|
51
|
+
# The triangle we hit that's farthest from the start of the ray should always be
|
52
|
+
# a triangle that points away from us (otherwise we would hit a triangle even
|
53
|
+
# further away, assuming the mesh is closed).
|
54
|
+
#
|
55
|
+
# One special case is when the set of triangles we hit at that distance is greater
|
56
|
+
# than one. In that case we might have hit a "corner" of the model and so we don't
|
57
|
+
# know which of the two (or more) points away from us. In that case, cast a random
|
58
|
+
# ray from the center of the object and try again.
|
59
|
+
|
60
|
+
@triangles.each do |tri|
|
61
|
+
v1 = @vertices[tri[0]]
|
62
|
+
v2 = @vertices[tri[1]]
|
63
|
+
v3 = @vertices[tri[2]]
|
64
|
+
|
65
|
+
process_triangle(@point, @direction, [v1, v2, v3])
|
66
|
+
end
|
67
|
+
|
68
|
+
if @intersections.count > 0
|
69
|
+
# Sort the intersections so we can find the hits that are furthest away.
|
70
|
+
@intersections.sort! {|left, right| left[0] <=> right[0]}
|
71
|
+
|
72
|
+
max_distance = @intersections.last[0]
|
73
|
+
furthest_hits = @intersections.select{|hit| (hit[0]-max_distance).abs < 0.0001}
|
74
|
+
|
75
|
+
# Print out the hits
|
76
|
+
# furthest_hits.each {|hit| puts hit[1].to_s}
|
77
|
+
|
78
|
+
found_good_hit = furthest_hits.count == 1
|
79
|
+
end
|
80
|
+
|
81
|
+
if found_good_hit
|
82
|
+
outside_triangle = furthest_hits.last[2]
|
83
|
+
else
|
84
|
+
@intersections = []
|
85
|
+
attempts = attempts + 1
|
86
|
+
|
87
|
+
target = [Random.rand(10)/10.0, Random.rand(10)/10.0, Random.rand(10)/10.0]
|
88
|
+
@point = center
|
89
|
+
@direction = normalize(vector_to(@point, target))
|
90
|
+
end
|
91
|
+
end until found_good_hit || attempts >= 10
|
92
|
+
|
93
|
+
# return true if we hit a triangle with an inward pointing normal
|
94
|
+
# (according to counter-clockwise normal orientation)
|
95
|
+
found_good_hit && !compare_normals(outside_triangle, @direction)
|
96
|
+
end
|
97
|
+
|
98
|
+
def compare_normals(triangle, hit_direction)
|
99
|
+
oriented_normal = cross_product(
|
100
|
+
vector_to(triangle[0], triangle[1]),
|
101
|
+
vector_to(triangle[1], triangle[2]))
|
102
|
+
|
103
|
+
angle = angle_between(oriented_normal, hit_direction)
|
104
|
+
|
105
|
+
angle < Math::PI / 2.0
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_triangle(point, direction, triangle)
|
109
|
+
found_intersection, t = intersect(point, direction, triangle)
|
110
|
+
|
111
|
+
if t > 0
|
112
|
+
intersection = []
|
113
|
+
intersection[0] = point[0] + t * direction[0]
|
114
|
+
intersection[1] = point[1] + t * direction[1]
|
115
|
+
intersection[2] = point[2] + t * direction[2]
|
116
|
+
|
117
|
+
@intersections << [t, intersection, triangle]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def intersect(point, direction, triangle)
|
122
|
+
v0 = triangle[0]
|
123
|
+
v1 = triangle[1]
|
124
|
+
v2 = triangle[2]
|
125
|
+
|
126
|
+
return [false, 0] if v0.nil? || v1.nil? || v2.nil?
|
127
|
+
|
128
|
+
e1 = vector_to(v0, v1)
|
129
|
+
e2 = vector_to(v0, v2)
|
130
|
+
|
131
|
+
h = cross_product(direction, e2)
|
132
|
+
a = dot_product(e1, h)
|
133
|
+
|
134
|
+
if a.abs < 0.00001
|
135
|
+
return false, 0
|
136
|
+
end
|
137
|
+
|
138
|
+
f = 1.0/a
|
139
|
+
s = vector_to(v0, point)
|
140
|
+
u = f * dot_product(s, h)
|
141
|
+
|
142
|
+
if u < 0.0 || u > 1.0
|
143
|
+
return false, 0
|
144
|
+
end
|
145
|
+
|
146
|
+
q = cross_product(s, e1)
|
147
|
+
v = f * dot_product(direction, q)
|
148
|
+
|
149
|
+
if v < 0.0 || u + v > 1.0
|
150
|
+
return false, 0
|
151
|
+
end
|
152
|
+
|
153
|
+
t = f * dot_product(e2, q)
|
154
|
+
[t > 0, t]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Various utility functions
|
159
|
+
|
160
|
+
def cross_product(a, b)
|
161
|
+
result = [0, 0, 0]
|
162
|
+
result[0] = a[1]*b[2] - a[2]*b[1]
|
163
|
+
result[1] = a[2]*b[0] - a[0]*b[2]
|
164
|
+
result[2] = a[0]*b[1] - a[1]*b[0]
|
165
|
+
|
166
|
+
result
|
167
|
+
end
|
168
|
+
|
169
|
+
def dot_product(a, b)
|
170
|
+
a[0]*b[0] + a[1]*b[1] + a[2]*b[2]
|
171
|
+
end
|
172
|
+
|
173
|
+
def vector_to(a, b)
|
174
|
+
[b[0] - a[0], b[1] - a[1], b[2] - a[2]]
|
175
|
+
end
|
176
|
+
|
177
|
+
def magnitude(a)
|
178
|
+
Math.sqrt(a[0]*a[0] + a[1]*a[1] + a[2]*a[2])
|
179
|
+
end
|
180
|
+
|
181
|
+
def equal(a, b)
|
182
|
+
(a[0] - b[0]).abs < 0.0001 && (a[1] - b[1]).abs < 0.0001 && (a[2] - b[2]).abs < 0.0001
|
183
|
+
end
|
184
|
+
|
185
|
+
def angle_between(a, b)
|
186
|
+
cos_theta = dot_product(a, b) / (magnitude(a) * magnitude(b))
|
187
|
+
Math.acos(cos_theta)
|
188
|
+
end
|
189
|
+
|
190
|
+
def normalize(a)
|
191
|
+
length = magnitude(a)
|
192
|
+
[a[0]/length, a[1]/length, a[2]/length]
|
193
|
+
end
|
194
|
+
|
195
|
+
def point_cloud_center(vertices)
|
196
|
+
if vertices.count < 1
|
197
|
+
return [0, 0, 0]
|
198
|
+
end
|
199
|
+
|
200
|
+
vertex = vertices[0]
|
201
|
+
min_x = max_x = vertex[0]
|
202
|
+
min_y = max_y = vertex[1]
|
203
|
+
min_z = max_z = vertex[2]
|
204
|
+
|
205
|
+
vertices.each do |vertex|
|
206
|
+
x = vertex[0]
|
207
|
+
y = vertex[1]
|
208
|
+
z = vertex[2]
|
209
|
+
|
210
|
+
min_x = x if x < min_x
|
211
|
+
max_x = x if x > max_x
|
212
|
+
min_y = y if y < min_y
|
213
|
+
max_y = y if y > max_y
|
214
|
+
min_z = z if z < min_z
|
215
|
+
max_z = z if z > max_z
|
216
|
+
end
|
217
|
+
|
218
|
+
[(min_x + max_x) / 2.0, (min_y + max_y) / 2.0, (min_z + max_z) / 2.0]
|
219
219
|
end
|