ruby3mf 0.2.6 → 0.2.7

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.
@@ -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