Lagrange
Loading...
Searching...
No Matches
select_facets_in_frustum.h
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14#include <array>
15#include <atomic>
16
17// clang-format off
18#include <lagrange/utils/warnoff.h>
19#include <tbb/blocked_range.h>
20#include <tbb/enumerable_thread_specific.h>
21#include <tbb/parallel_for.h>
22#include <lagrange/utils/warnon.h>
23// clang-format on
24
25#include <lagrange/Mesh.h>
26#include <lagrange/MeshTrait.h>
27#include <lagrange/common.h>
28#include <lagrange/legacy/inline.h>
29#include <lagrange/utils/tbb.h>
30
31namespace lagrange {
32LAGRANGE_LEGACY_INLINE
33namespace legacy {
34
50template <typename MeshType, typename Point3D>
51bool select_facets_in_frustum(
52 MeshType& mesh,
53 const Eigen::PlainObjectBase<Point3D>& n0,
54 const Eigen::PlainObjectBase<Point3D>& p0,
55 const Eigen::PlainObjectBase<Point3D>& n1,
56 const Eigen::PlainObjectBase<Point3D>& p1,
57 const Eigen::PlainObjectBase<Point3D>& n2,
58 const Eigen::PlainObjectBase<Point3D>& p2,
59 const Eigen::PlainObjectBase<Point3D>& n3,
60 const Eigen::PlainObjectBase<Point3D>& p3,
61 bool greedy = false)
62{
63 static_assert(MeshTrait<MeshType>::is_mesh(), "Input is not a mesh.");
64 using AttributeArray = typename MeshType::AttributeArray;
65 using Scalar = typename MeshType::Scalar;
66 using Index = typename MeshType::Index;
67
68 const auto num_facets = mesh.get_num_facets();
69 const auto& vertices = mesh.get_vertices();
70 const auto& facets = mesh.get_facets();
71
72 // Per-thread buffers to store intermediate results.
73 struct LocalBuffers
74 {
75 Point3D v0, v1, v2; // Triangle vertices.
76 Point3D q0, q1, q2, q3; // Intermediate tet vertices.
77 std::array<double, 2> r0, r1, r2; // Triangle projections.
78 };
79 tbb::enumerable_thread_specific<LocalBuffers>
80 temp_vars; // we can safely remove this and just declare these variables in the lambdas (as
81 // long as there's no memory allocation on the heap, this is not necessary)
82
83
84 auto edge_overlap_with_negative_octant = [](const Point3D& q0, const Point3D& q1) -> bool {
85 Scalar t_min = 0, t_max = 1;
86 const Point3D e = q0 - q1;
87 if (e[0] > 0) {
88 t_max = std::min(t_max, -q1[0] / e[0]);
89 } else if (e[0] < 0) {
90 t_min = std::max(t_min, -q1[0] / e[0]);
91 } else {
92 if (q1[0] >= 0) return false;
93 }
94
95 if (e[1] > 0) {
96 t_max = std::min(t_max, -q1[1] / e[1]);
97 } else if (e[1] < 0) {
98 t_min = std::max(t_min, -q1[1] / e[1]);
99 } else {
100 if (q1[1] >= 0) return false;
101 }
102
103 if (e[2] > 0) {
104 t_max = std::min(t_max, -q1[2] / e[2]);
105 } else if (e[2] < 0) {
106 t_min = std::max(t_min, -q1[2] / e[2]);
107 } else {
108 if (q1[2] >= 0) return false;
109 }
110
111 return t_max >= t_min;
112 };
113
114 auto compute_plane =
115 [](const Point3D& q0, const Point3D& q1, const Point3D& q2) -> std::pair<Point3D, Scalar> {
116 const Point3D n = {
117 q0[2] * (q2[1] - q1[1]) + q1[2] * (q0[1] - q2[1]) + q2[2] * (q1[1] - q0[1]),
118 q0[0] * (q2[2] - q1[2]) + q1[0] * (q0[2] - q2[2]) + q2[0] * (q1[2] - q0[2]),
119 q0[1] * (q2[0] - q1[0]) + q1[1] * (q0[0] - q2[0]) + q2[1] * (q1[0] - q0[0])};
120 const Scalar c = (n.dot(q0) + n.dot(q1) + n.dot(q2)) / 3;
121 return {n, c};
122 };
123
124 // Compute the orientation of 2D triangle (v0, v1, O), where O = (0, 0).
125 auto orient2D_inexact = [](const std::array<double, 2>& v0,
126 const std::array<double, 2>& v1) -> int {
127 const auto r = v0[0] * v1[1] - v0[1] * v1[0];
128 if (r > 0) return 1;
129 if (r < 0) return -1;
130 return 0;
131 };
132
133 auto triangle_intersects_negative_axis = [&](const Point3D& q0,
134 const Point3D& q1,
135 const Point3D& q2,
136 const Point3D& n,
137 const Scalar c,
138 const int axis) -> bool {
139 auto& r0 = temp_vars.local().r0;
140 auto& r1 = temp_vars.local().r1;
141 auto& r2 = temp_vars.local().r2;
142
143 r0[0] = q0[(axis + 1) % 3];
144 r0[1] = q0[(axis + 2) % 3];
145 r1[0] = q1[(axis + 1) % 3];
146 r1[1] = q1[(axis + 2) % 3];
147 r2[0] = q2[(axis + 1) % 3];
148 r2[1] = q2[(axis + 2) % 3];
149
150 auto o01 = orient2D_inexact(r0, r1);
151 auto o12 = orient2D_inexact(r1, r2);
152 auto o20 = orient2D_inexact(r2, r0);
153 if (o01 == o12 && o01 == o20) {
154 if (o01 == 0) {
155 // Triangle projection is degenerate.
156 // Note that the case where axis is coplanar with the triangle
157 // is treated as no intersection (which is debatale).
158 return false;
159 } else {
160 // Triangle projection contains the origin.
161 // Check axis intercept.
162 return (c < 0 && n[axis] > 0) || (c > 0 && n[axis] < 0);
163 }
164 } else {
165 // Note that we treat the case where the axis intersect triangle at
166 // its boundary as no intersection.
167 return false;
168 }
169 };
170
171 auto triangle_intersects_negative_axes =
172 [&](const Point3D& q0, const Point3D& q1, const Point3D& q2) -> bool {
173 const auto r = compute_plane(q0, q1, q2);
174 if (triangle_intersects_negative_axis(q0, q1, q2, r.first, r.second, 0)) return true;
175 if (triangle_intersects_negative_axis(q0, q1, q2, r.first, r.second, 1)) return true;
176 if (triangle_intersects_negative_axis(q0, q1, q2, r.first, r.second, 2)) return true;
177 return false;
178 };
179
180 auto tet_overlap_with_negative_octant =
181 [&](const Point3D& q0, const Point3D& q1, const Point3D& q2, const Point3D& q3) -> bool {
182 // Check 1: Check if any tet vertices is in negative octant.
183 if ((q0.array() < 0).all()) return true;
184 if ((q1.array() < 0).all()) return true;
185 if ((q2.array() < 0).all()) return true;
186 if ((q3.array() < 0).all()) return true;
187
188 // Check 2: Check if any tet edges cross the negative octant.
189 if (edge_overlap_with_negative_octant(q0, q1)) return true;
190 if (edge_overlap_with_negative_octant(q0, q2)) return true;
191 if (edge_overlap_with_negative_octant(q0, q3)) return true;
192 if (edge_overlap_with_negative_octant(q1, q2)) return true;
193 if (edge_overlap_with_negative_octant(q1, q3)) return true;
194 if (edge_overlap_with_negative_octant(q2, q3)) return true;
195
196 // Check 3: Check if -X, -Y or -Z axis intersect the tet.
197 if (triangle_intersects_negative_axes(q0, q1, q2)) return true;
198 if (triangle_intersects_negative_axes(q1, q2, q3)) return true;
199 if (triangle_intersects_negative_axes(q2, q3, q0)) return true;
200 if (triangle_intersects_negative_axes(q3, q0, q1)) return true;
201
202 // All check failed iff tet does not intersect the negative octant.
203 return false;
204 };
205
206 AttributeArray attr;
207 if (!greedy) {
208 attr.resize(num_facets, 1);
209 attr.setZero();
210 }
211 std::atomic_bool r{false};
212
213 tbb::parallel_for(
214 tbb::blocked_range<Index>(0, num_facets),
215 [&](const tbb::blocked_range<Index>& tbb_range) {
216 for (auto fi = tbb_range.begin(); fi != tbb_range.end(); fi++) {
217 if (tbb_utils::is_cancelled()) break;
218
219 // Triangle (v0, v1, v2) intersect the cone defined by 4 planes
220 // iff the tetrahedron (q0, q1, q2, q3) does not intersect the negative octant.
221 // This can be proved by Farkas' lemma.
222
223 auto& v0 = temp_vars.local().v0;
224 auto& v1 = temp_vars.local().v1;
225 auto& v2 = temp_vars.local().v2;
226
227 auto& q0 = temp_vars.local().q0;
228 auto& q1 = temp_vars.local().q1;
229 auto& q2 = temp_vars.local().q2;
230 auto& q3 = temp_vars.local().q3;
231
232 v0 = vertices.row(facets(fi, 0));
233 v1 = vertices.row(facets(fi, 1));
234 v2 = vertices.row(facets(fi, 2));
235
236 q0 = {(v0 - p0).dot(n0), (v1 - p0).dot(n0), (v2 - p0).dot(n0)};
237 q1 = {(v0 - p1).dot(n1), (v1 - p1).dot(n1), (v2 - p1).dot(n1)};
238 q2 = {(v0 - p2).dot(n2), (v1 - p2).dot(n2), (v2 - p2).dot(n2)};
239 q3 = {(v0 - p3).dot(n3), (v1 - p3).dot(n3), (v2 - p3).dot(n3)};
240
241 const auto ri = !tet_overlap_with_negative_octant(q0, q1, q2, q3);
242
243 if (ri) r = true;
244
245 if (!greedy) {
246 attr(fi, 0) = ri;
247 }
248
249 if (greedy && ri) {
250 tbb_utils::cancel_group_execution();
251 break;
252 }
253 }
254 });
255
256 if (!greedy) {
257 mesh.add_facet_attribute("is_selected");
258 mesh.import_facet_attribute("is_selected", attr);
259 }
260 return r.load();
261}
262
263} // namespace legacy
264} // namespace lagrange
Index get_num_facets() const
Retrieves the number of facets.
Definition SurfaceMesh.h:678
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
Main namespace for Lagrange.