ruby3mf 0.2.6 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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