Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43ad5d4b97 | ||
|
|
95fd44bbad | ||
|
|
86b93459a6 | ||
|
|
1bfaeba67b | ||
|
|
185559e373 | ||
|
|
a5659b38e8 | ||
|
|
4dcca4f4cf | ||
|
|
d9005aaf8c | ||
|
|
bdc06aa8f9 | ||
|
|
285824cf58 | ||
|
|
2918c80198 | ||
|
|
8d21869426 | ||
|
|
a387959c93 | ||
|
|
a9ff01b36a | ||
|
|
813151775b | ||
|
|
3b448247d0 | ||
|
|
e20de53e11 | ||
|
|
5aaf063f87 | ||
|
|
49d2ff7fac | ||
|
|
da30bd04cc | ||
|
|
624a18574a | ||
|
|
6f0fc0e6e7 | ||
|
|
4a17e43560 | ||
|
|
8fdba02189 | ||
|
|
2fe4b2f08f | ||
|
|
3b53ca0093 | ||
|
|
324986134e | ||
|
|
e4ee3b8362 | ||
|
|
7cfb2b58e1 | ||
|
|
5aa60a2c29 | ||
|
|
c9ac27102d | ||
|
|
20206f696f | ||
|
|
0671152b75 | ||
|
|
2509048c45 | ||
|
|
02f8a98ac9 | ||
|
|
7e6ebba263 | ||
|
|
96a1a972c6 | ||
|
|
e43d741882 | ||
|
|
36bdef78c5 | ||
|
|
b6b2075c06 | ||
|
|
78a24378dd | ||
|
|
4e4b09fd2f | ||
|
|
6ef47093b6 | ||
|
|
da39ca8ef4 | ||
|
|
fdf70af435 | ||
|
|
ca0389d276 | ||
|
|
1b750729e8 | ||
|
|
29761eaa9a | ||
|
|
df8f2890f1 | ||
|
|
7afeb57d5d | ||
|
|
d10ad36e1f | ||
|
|
750566e77b | ||
|
|
a265a0e4d0 | ||
|
|
f20ba0af07 | ||
|
|
26652fc080 | ||
|
|
dc6625dc45 | ||
|
|
1fe49f5fff | ||
|
|
dbf732914a | ||
|
|
99783e89b6 | ||
|
|
2cced67313 | ||
|
|
09b0760a0d | ||
|
|
ca06b99b68 | ||
|
|
1d0699a0f2 | ||
|
|
b775ea9c1d | ||
|
|
c951995a7c | ||
|
|
7b5f5198ac | ||
|
|
ec8be4dc7f | ||
|
|
0dd8a62358 | ||
|
|
0a1a59147c | ||
|
|
c40ef0a244 | ||
|
|
705897a21d | ||
|
|
e5aa5ed6cb | ||
|
|
ddb7c61234 | ||
|
|
c8772b24af | ||
|
|
05de8fb214 | ||
|
|
299d8a7533 | ||
|
|
4906e40cf9 | ||
|
|
c760669905 | ||
|
|
ccd8e14af8 | ||
|
|
1dc12d09d9 | ||
|
|
f47898d038 | ||
|
|
c4d35b7faa | ||
|
|
7c6569277c | ||
|
|
7c3f00d38e | ||
|
|
d2a6e0b821 | ||
|
|
0163af0684 | ||
|
|
129f2ce3f0 | ||
|
|
797539df2a | ||
|
|
c2015d8548 | ||
|
|
eb00217017 | ||
|
|
a326edde2e | ||
|
|
0e4a54d223 | ||
|
|
a64fce1cab | ||
|
|
4b50396d2a | ||
|
|
ce95c4b427 | ||
|
|
b764b824a3 | ||
|
|
87ca7a7f80 | ||
|
|
9651eb0601 | ||
|
|
b81cf5303b | ||
|
|
20e68cc8ad | ||
|
|
a7a4175636 | ||
|
|
7f3091de7b | ||
|
|
a97c4f0d6e | ||
|
|
5a2ce89967 | ||
|
|
50009e543f | ||
|
|
941d8a61da | ||
|
|
5bcf5ab9c3 | ||
|
|
5c646a2007 | ||
|
|
a3b9639a27 | ||
|
|
c30ad5b0fb | ||
|
|
9992cda8f2 | ||
|
|
e28afd0185 | ||
|
|
510eb27926 | ||
|
|
a5cd853e45 | ||
|
|
fab9b4fe90 | ||
|
|
bcfe64e959 | ||
|
|
8369bb7ef2 | ||
|
|
38f18e1acf | ||
|
|
5c6b86db6a | ||
|
|
5fa9c8cb6b | ||
|
|
28d0941e3d | ||
|
|
180dae1bad | ||
|
|
bc1da81a02 | ||
|
|
c47c0fcf0c | ||
|
|
ba1deee51b | ||
|
|
18297705b1 | ||
|
|
3d43ed4a01 | ||
|
|
0832835dd2 | ||
|
|
8b7ec4660e | ||
|
|
5f8b6442e5 | ||
|
|
c8fe72112c | ||
|
|
1fdb871c24 | ||
|
|
139d94a231 | ||
|
|
428ed6bf80 | ||
|
|
9c8ef43526 | ||
|
|
b07c88e870 | ||
|
|
690911f337 | ||
|
|
15b92dbb16 | ||
|
|
4d82a3130d | ||
|
|
73e18a0083 | ||
|
|
34df58327e | ||
|
|
20faab5d64 | ||
|
|
0f893e1923 | ||
|
|
aec310d6c7 | ||
|
|
183bc2ef89 | ||
|
|
9b1763fb0d | ||
|
|
f13c8c031f | ||
|
|
64aa3efe26 | ||
|
|
d6b6a7af8b | ||
|
|
43332ce62e | ||
|
|
cb32db1da4 | ||
|
|
36b876d111 | ||
|
|
75be69c6a1 | ||
|
|
93276909a7 | ||
|
|
7a0882a220 | ||
|
|
ae0e8ea47b | ||
|
|
c15d780d96 | ||
|
|
e4b3facbe9 | ||
|
|
5c80cc49e0 | ||
|
|
75c4301477 | ||
|
|
428a9cdac4 | ||
|
|
4af5726d4f | ||
|
|
d93271312f | ||
|
|
07e26730c1 | ||
|
|
6d97c5bbb5 | ||
|
|
34b4d2d60e | ||
|
|
62f1b14233 | ||
|
|
3f99e5a390 | ||
|
|
4cf8291866 | ||
|
|
86b970ea35 | ||
|
|
b5376cc87c | ||
|
|
a9a239a5ad | ||
|
|
f3166082f0 | ||
|
|
2e66cb2dd8 | ||
|
|
f1a16f34ce | ||
|
|
fde87ca24e | ||
|
|
8be6fae2ff | ||
|
|
3b23ae1cf1 | ||
|
|
942956127e | ||
|
|
0f22dd800c | ||
|
|
f153244934 | ||
|
|
9083367e5e | ||
|
|
7ba91791ef | ||
|
|
19f4a3b12e | ||
|
|
70944341d7 | ||
|
|
e2db4288d8 | ||
|
|
3ffd1568a8 | ||
|
|
af1e161565 | ||
|
|
0570937347 | ||
|
|
e8b2994111 | ||
|
|
b2d927fcbe | ||
|
|
3579b9757a | ||
|
|
8d5141e37b | ||
|
|
55fb4bb908 | ||
|
|
090b219a5a | ||
|
|
97d972177d | ||
|
|
246c2ed6c0 | ||
|
|
fb8c715e83 | ||
|
|
c24f37370f | ||
|
|
6ed2a69a06 | ||
|
|
21b32b0f69 | ||
|
|
c13ae304f4 | ||
|
|
5ce858cb7a | ||
|
|
3ce0e865c0 | ||
|
|
80df3d1f98 | ||
|
|
e40e65b12c | ||
|
|
339a9f40f0 | ||
|
|
f10919d8a7 | ||
|
|
620488965a | ||
|
|
8c301eabd1 | ||
|
|
9744ca6b17 | ||
|
|
99ecf3beef | ||
|
|
ce64ad5bcc | ||
|
|
9afcf3f474 | ||
|
|
adbf34a7cc | ||
|
|
0ca7c82d4b | ||
|
|
cfadf83b17 | ||
|
|
4f2e121ada | ||
|
|
8d88deec1f | ||
|
|
d24ca11f30 | ||
|
|
b435e292b2 | ||
|
|
8fb43b8a81 | ||
|
|
6aa3a3fe99 | ||
|
|
df3597fbb1 | ||
|
|
a1cdc7efbf | ||
|
|
c45bdd9e12 | ||
|
|
e89653e374 | ||
|
|
54b41ac268 | ||
|
|
ab4f5fff4d | ||
|
|
da414e23e1 | ||
|
|
0f7fba5f8d | ||
|
|
7d95ea711f | ||
|
|
2abeb1d370 | ||
|
|
452a574d6a | ||
|
|
dfc2a45d96 | ||
|
|
d3f4a8af77 | ||
|
|
9c3cf13748 | ||
|
|
dfd46f7807 | ||
|
|
4e088e4221 | ||
|
|
c7ca85e6af | ||
|
|
a9263bcf0f | ||
|
|
c73d5d0107 | ||
|
|
5262ae183b | ||
|
|
5fd66ec1f2 | ||
|
|
194d277da7 | ||
|
|
b731d2c493 | ||
|
|
278fe1027d | ||
|
|
679085a233 | ||
|
|
7f302f2a36 | ||
|
|
f900cff7ee | ||
|
|
e7c1634d69 | ||
|
|
e02cbda1ba | ||
|
|
ceea1de1a9 | ||
|
|
90ebcfb82f | ||
|
|
b1f96d992b | ||
|
|
4698a1dbfa | ||
|
|
82b1ad1ce7 | ||
|
|
a73140d202 | ||
|
|
3633954d6d | ||
|
|
61e0efc9b6 | ||
|
|
dd7b4de256 | ||
|
|
d179622873 | ||
|
|
5d3a20f778 | ||
|
|
6e48ea9ddd | ||
|
|
488f538040 | ||
|
|
17cc9f3a5a | ||
|
|
bf2c508826 | ||
|
|
f2e68b1a2a | ||
|
|
5e873e5081 | ||
|
|
3beed70eb8 | ||
|
|
73258d4264 | ||
|
|
8ae7482918 | ||
|
|
917ce5554c | ||
|
|
58ab0d5496 | ||
|
|
13b0699349 | ||
|
|
acd3a9800d | ||
|
|
03b36c1b61 | ||
|
|
b714d88185 | ||
|
|
eff3fea479 | ||
|
|
7358e90b54 | ||
|
|
3c2b0de9d2 | ||
|
|
a384f838fb | ||
|
|
1053594e9a | ||
|
|
72cde263cc | ||
|
|
b670a19aba | ||
|
|
2d9fa5c033 | ||
|
|
8be0f0ae85 | ||
|
|
00ffa4bd80 | ||
|
|
e5e0905d03 | ||
|
|
10d13793b0 | ||
|
|
70d5180438 | ||
|
|
50fe626694 | ||
|
|
ca403c92ed | ||
|
|
bab959474f | ||
|
|
27b522c155 | ||
|
|
875231c335 | ||
|
|
89810b06ca | ||
|
|
90e512b5d6 | ||
|
|
19fe382208 | ||
|
|
994c56b05f | ||
|
|
db76164ea8 | ||
|
|
58c5ed3681 | ||
|
|
05bd5c7470 | ||
|
|
2276f4ef8c | ||
|
|
188e72eb32 | ||
|
|
03306043ea | ||
|
|
28038fe0ea | ||
|
|
5b5cb8c538 | ||
|
|
1c20d18156 | ||
|
|
97920fbc90 | ||
|
|
37fb7d928a | ||
|
|
2056b359bd | ||
|
|
b4cf00292a | ||
|
|
6748dd37ab | ||
|
|
ef35c1dca1 | ||
|
|
8cdedd8752 | ||
|
|
e4ba0b166c | ||
|
|
586ac06c7e | ||
|
|
0d745fca35 | ||
|
|
5270e67abe | ||
|
|
14051845a3 | ||
|
|
d6fa937212 | ||
|
|
37aee260c1 | ||
|
|
56d64428ae | ||
|
|
0f6db7016d | ||
|
|
cbffc1171b |
+32
-17
@@ -231,7 +231,7 @@ set(CMAKE_FIND_USE_PACKAGE_REGISTRY FALSE)
|
||||
# Prepare to manage and build BRL-CAD's external dependencies. Specifies a
|
||||
# specific commit hash to use in the 'bext' repository.
|
||||
include(BRLCAD_ExternalDeps)
|
||||
brlcad_bext_init(12115e6c5922e2db27a9b88e00dd714af5315f69)
|
||||
brlcad_bext_init(0682795bcfe34644c17e5a4bdd0225c76f6bdc98)
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
# Define various utilities.
|
||||
@@ -678,8 +678,13 @@ endif(MSVC)
|
||||
# before use so platforms without GNU/LLVM-style linker options are skipped.
|
||||
brlcad_check_linker_flag(C "LINKER:--no-undefined" BRLCAD_LINKER_NO_UNDEFINED_C_FLAG_FOUND)
|
||||
brlcad_check_linker_flag(CXX "LINKER:--no-undefined" BRLCAD_LINKER_NO_UNDEFINED_CXX_FLAG_FOUND)
|
||||
if(BRLCAD_LINKER_NO_UNDEFINED_C_FLAG_FOUND AND BRLCAD_LINKER_NO_UNDEFINED_CXX_FLAG_FOUND)
|
||||
if(BRLCAD_LINKER_NO_UNDEFINED_C_FLAG_FOUND AND BRLCAD_LINKER_NO_UNDEFINED_CXX_FLAG_FOUND
|
||||
AND NOT BRLCAD_ENABLE_ADDRESS_SANITIZER)
|
||||
set(BRLCAD_LINKER_NO_UNDEFINED_FLAG "LINKER:--no-undefined" CACHE STRING "Linker flag requiring resolved shared library symbols" FORCE)
|
||||
else()
|
||||
# Clang links the AddressSanitizer runtime into executables, leaving its
|
||||
# shared-library instrumentation symbols unresolved until then.
|
||||
set(BRLCAD_LINKER_NO_UNDEFINED_FLAG "" CACHE STRING "Linker flag requiring resolved shared library symbols" FORCE)
|
||||
endif()
|
||||
brlcad_check_linker_flags(C BRLCAD_LINKER_WHOLE_ARCHIVE_GROUP_C_FLAG_FOUND "LINKER:--whole-archive" "LINKER:--no-whole-archive")
|
||||
brlcad_check_linker_flags(CXX BRLCAD_LINKER_WHOLE_ARCHIVE_GROUP_CXX_FLAG_FOUND "LINKER:--whole-archive" "LINKER:--no-whole-archive")
|
||||
@@ -1642,21 +1647,31 @@ if(NOT MSVC)
|
||||
endif(BRLCAD_WARNINGS OR BRLCAD_ENABLE_STRICT)
|
||||
|
||||
if(BRLCAD_ENABLE_COVERAGE)
|
||||
# TODO: These seem to GCC specific flags - should probably set up for clang
|
||||
# as well if that's our compiler...
|
||||
# https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
|
||||
# If we need different settings for different tools, should
|
||||
# encapsulate each tool's setup in a function
|
||||
check_c_flag(ftest-coverage)
|
||||
check_cxx_flag(ftest-coverage)
|
||||
if(FTEST_COVERAGE_C_FLAG_FOUND)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
else(FTEST_COVERAGE_C_FLAG_FOUND)
|
||||
message(SEND_ERROR "Building with coverage is not supported by BRL-CAD on this platform.")
|
||||
endif(FTEST_COVERAGE_C_FLAG_FOUND)
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "Clang" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
check_c_flag(fprofile-instr-generate)
|
||||
check_cxx_flag(fprofile-instr-generate)
|
||||
check_c_flag(fcoverage-mapping)
|
||||
check_cxx_flag(fcoverage-mapping)
|
||||
if(NOT FPROFILE_INSTR_GENERATE_C_FLAG_FOUND OR
|
||||
NOT FPROFILE_INSTR_GENERATE_CXX_FLAG_FOUND OR
|
||||
NOT FCOVERAGE_MAPPING_C_FLAG_FOUND OR
|
||||
NOT FCOVERAGE_MAPPING_CXX_FLAG_FOUND)
|
||||
message(FATAL_ERROR "Clang source-based coverage is not supported by this toolchain.")
|
||||
endif()
|
||||
add_compile_options(-fprofile-instr-generate -fcoverage-mapping)
|
||||
add_link_options(-fprofile-instr-generate)
|
||||
else()
|
||||
check_c_flag(ftest-coverage)
|
||||
check_cxx_flag(ftest-coverage)
|
||||
if(FTEST_COVERAGE_C_FLAG_FOUND AND FTEST_COVERAGE_CXX_FLAG_FOUND)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov -ftest-coverage -fprofile-arcs -fprofile-update=atomic")
|
||||
else()
|
||||
message(FATAL_ERROR "GCC coverage is not supported by this toolchain.")
|
||||
endif()
|
||||
endif()
|
||||
endif(BRLCAD_ENABLE_COVERAGE)
|
||||
|
||||
if(BRLCAD_ENABLE_STRICT)
|
||||
|
||||
@@ -13,6 +13,8 @@ changes made. See document footer for additional details.
|
||||
--- 20XX-XX-XX Release 7.46.0 ---
|
||||
----------------------------------------------------------------------
|
||||
|
||||
* improved pixscale & bwscale scaling and image piping - Sean Morrison
|
||||
* restored pixdiff behavior on .pix, added -b for .bw - Sean Morrison
|
||||
* updated MGED and Archer for Tcl 8.6.18 and Itcl/Itk 4 - Cliff Yapp
|
||||
* fixed writes to fileless geometry databases - Cliff Yapp
|
||||
* fixed nastran-g handling of large property IDs - Rupesh, Cliff Yapp
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
BRL-CAD
|
||||
Release 7.44.2
|
||||
http://brlcad.org/
|
||||
BRL-CAD
|
||||
Release 7.44.2
|
||||
http://brlcad.org/
|
||||
|
||||
BRL-CAD is a powerful cross-platform open source combinatorial
|
||||
solid modeling system that includes an interactive 3D solid geometry
|
||||
|
||||
+543
@@ -0,0 +1,543 @@
|
||||
BRL-CAD B-Rep Tessellation Improvement Plan
|
||||
===========================================
|
||||
|
||||
Date: 2026-07-17
|
||||
|
||||
|
||||
1. Objective
|
||||
------------
|
||||
|
||||
Produce deterministic, watertight, orientable manifold meshes from valid closed
|
||||
NURBS B-Reps, with explicit and measurable geometric quality guarantees. The
|
||||
conversion-quality path must either return a certified mesh or fail with a
|
||||
specific diagnostic. It must never silently emit a mesh marked solid when the
|
||||
result has not passed all required checks.
|
||||
|
||||
The existing fast tessellator should remain available for shaded display. It
|
||||
should not be evolved into, or presented as, a conversion-quality tessellator:
|
||||
its face-local vertex indexing means coincident samples on adjacent faces are
|
||||
not topologically shared.
|
||||
|
||||
|
||||
2. Central Design Decision
|
||||
--------------------------
|
||||
|
||||
Do not triangulate every face directly in its native rectangular NURBS UV
|
||||
domain.
|
||||
|
||||
Instead, introduce an explicit chart layer:
|
||||
|
||||
B-Rep topology
|
||||
-> one or more topology-correct charts per face
|
||||
-> constrained triangulation in chart coordinates
|
||||
-> evaluation through chart -> native UV -> 3D surface
|
||||
-> global assembly using explicit topology IDs
|
||||
-> manifold and geometric certification
|
||||
|
||||
The chart domain must have the topology of the represented surface region.
|
||||
Periodic seams and singularities are chart topology, not floating-point
|
||||
coincidences to repair after triangulation.
|
||||
|
||||
For a cone, the first prototype should use a triangular or wedge chart. The
|
||||
base is one chart boundary, the two sides are periodic copies of the seam, and
|
||||
the apex is one chart vertex. Chart interior points map back to the original
|
||||
rectangular native UV domain for surface evaluation. No boundary edge handed
|
||||
to detria should collapse to one 3D point.
|
||||
|
||||
A triangle is not a universal replacement for a quad:
|
||||
|
||||
* Cone: triangular/wedge chart, or several angular wedges.
|
||||
* Sphere: triangular pole caps plus one or more regular middle charts.
|
||||
* Cylinder: regular periodic chart with explicit seam identification.
|
||||
* Torus: a multi-chart atlas or explicit identifications in both periodic
|
||||
directions.
|
||||
* General trimmed faces: decomposition into certified disk-like charts, with
|
||||
trims split where they cross chart boundaries.
|
||||
|
||||
Do not change the underlying NURBS representation merely to tessellate it.
|
||||
Charts are adapters to native UV.
|
||||
|
||||
|
||||
3. Findings Driving This Plan
|
||||
-----------------------------
|
||||
|
||||
3.1 Current singularity handling has the wrong topology
|
||||
|
||||
The quality path subdivides singular trim segments in 2D even when all samples
|
||||
map to the same 3D pole. Detria triangulates the resulting 2D strip, after
|
||||
which BRL-CAD merges vertices by ON_3dPoint pointer identity and discards
|
||||
degenerate triangles. Holes, duplicate triangles, and bad edge incidence are
|
||||
then repaired heuristically. A valid manifold cannot be made reliable with
|
||||
this ordering.
|
||||
|
||||
Relevant implementation:
|
||||
|
||||
* src/libbrep/cdt/cdt.cpp, especially singular-trim splitting around line 487.
|
||||
* src/libbrep/cdt/edge.cpp, shared B-Rep edge discretization around line 1022.
|
||||
* src/libbrep/cdt/mesh.cpp, face triangulation and repair.
|
||||
|
||||
3.2 Projection repair is handling invalid charts, not only difficult geometry
|
||||
|
||||
On failing NIST cases, local LSCM/mean-value and best-fit-plane projections
|
||||
produce mixed triangle orientations or self-intersecting constrained polygons.
|
||||
Detria then correctly rejects intersecting constraints. Retrying a different
|
||||
projection does not address the source topology problem.
|
||||
|
||||
The implementation called LSCM is a positive-weight spring/mean-value-style
|
||||
parameterization with circular or elliptical boundary placement. It does not
|
||||
validate all conditions needed for injectivity, it does not adequately check
|
||||
solver convergence, and its current result checks do not prove the absence of
|
||||
fold-overs.
|
||||
|
||||
3.3 UV aspect normalization changes Delaunay decisions
|
||||
|
||||
The quality path anisotropically scales high-aspect UV domains toward a square.
|
||||
Nonuniform scaling does not preserve Delaunay triangulations. It changes
|
||||
incircle tests and selected diagonals and is therefore a metric choice, not
|
||||
numerical conditioning.
|
||||
|
||||
Use translation and uniform, preferably power-of-two, scaling only to condition
|
||||
coordinate range. Obtain good 3D triangles using the surface metric, adaptive
|
||||
sampling, and constrained local optimization.
|
||||
|
||||
3.4 Several sources of nondeterminism and platform sensitivity exist
|
||||
|
||||
* Surface jitter is assigned while iterating pointer-ordered containers, so
|
||||
allocation order changes which patch receives which perturbation.
|
||||
* Other work queues and sets use pointer values as ordering keys.
|
||||
* The detria translation unit is not currently compiled with all strict
|
||||
floating-point options required by its robust predicates.
|
||||
* An experimental facetize caller does not fully initialize its tolerance
|
||||
structure.
|
||||
* The vendored detria predates upstream changes to collinear-prefix handling
|
||||
and expansion arithmetic.
|
||||
|
||||
3.5 Present final validation is necessary but not sufficient
|
||||
|
||||
bg_trimesh_solid2 verifies indexed degeneracy and two oppositely directed uses
|
||||
of every edge. It does not establish geometric nondegeneracy, valid vertex
|
||||
links, lack of self-intersections, surface error, normal error, connectedness,
|
||||
outward orientation, or acceptable triangle quality.
|
||||
|
||||
|
||||
4. Target Data Model
|
||||
--------------------
|
||||
|
||||
Replace implicit topology based on ON_3dPoint pointers with explicit stable
|
||||
identifiers. At minimum, distinguish:
|
||||
|
||||
* TopoVertexID: a vertex in global B-Rep/output topology.
|
||||
* BrepEdgeID: a B-Rep edge.
|
||||
* EdgeSampleID: one sample in the canonical ordered discretization of a B-Rep
|
||||
edge.
|
||||
* FaceChartID: one chart associated with a B-Rep face.
|
||||
* ChartVertexID: a vertex in a chart; several chart vertices may intentionally
|
||||
map to the same TopoVertexID at a seam or singularity.
|
||||
* MeshVertexID, HalfedgeID, and TriangleID in the assembled output mesh.
|
||||
|
||||
Coordinates are attributes of these entities, not their identity.
|
||||
|
||||
Each B-Rep edge must be sampled once through a global edge manager. Both
|
||||
adjacent face charts reference exactly the same EdgeSampleID sequence, with
|
||||
opposite traversal direction. If refinement inserts an edge sample, both
|
||||
adjacent charts must be invalidated or updated together.
|
||||
|
||||
The assembled output should use a halfedge structure or an equivalent data
|
||||
structure that makes edge incidence and vertex-link validation explicit.
|
||||
|
||||
|
||||
5. Work Plan
|
||||
------------
|
||||
|
||||
Phase 0: Establish a reproducible baseline
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
0.1 Build an end-to-end test driver around ON_Brep_CDT_Tessellate and
|
||||
ON_Brep_CDT_Mesh. It must be able to:
|
||||
|
||||
* select individual solids and faces;
|
||||
* set all tessellation tolerances explicitly;
|
||||
* write the chart constraints, sampled points, and output mesh on failure;
|
||||
* run validation independently of frontends such as facetize;
|
||||
* produce stable textual summaries suitable for CTest comparison.
|
||||
|
||||
0.2 Register the ten cone fixtures and the primary solids in all NIST database
|
||||
files as regression tests. Add smaller extracted regressions for every failure
|
||||
so normal testing does not depend only on large databases.
|
||||
|
||||
0.3 Record, for every test:
|
||||
|
||||
* result status and failure stage;
|
||||
* vertex, edge, and triangle counts;
|
||||
* open, overused, and misoriented edge counts;
|
||||
* invalid vertex-link count;
|
||||
* degenerate and intersecting triangle counts;
|
||||
* maximum surface and edge approximation errors;
|
||||
* minimum angle and relevant aspect measures;
|
||||
* runtime and refinement counts.
|
||||
|
||||
Exit criterion:
|
||||
|
||||
* Every baseline case has a repeatable result and a deterministic diagnostic
|
||||
artifact when it fails.
|
||||
|
||||
|
||||
Phase 1: Correctness, determinism, and fail-closed behavior
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
1.1 Fix known local defects before changing the architecture:
|
||||
|
||||
* In edge.cpp, correct the condition that tests trim1 twice rather than testing
|
||||
trim1 and trim2.
|
||||
* In mesh.cpp duplicate-triangle handling, compute the second direction from
|
||||
the second triangle rather than calling tnorm(orig) twice.
|
||||
* In cdt.cpp mesh export, index fmeshes using active_faces[fi].
|
||||
* Update the closest-distance accumulator in the closest-point helper.
|
||||
* Preserve orientation when comparing normals; do not erase it with an
|
||||
unconditional absolute dot product.
|
||||
* Make status initialization, update, and reuse semantics explicit.
|
||||
* Invalidate and rebuild all tolerance-dependent data when tolerances change.
|
||||
* Fully initialize bg_tess_tol in every caller.
|
||||
* Require every frontend to accept only the defined success status.
|
||||
* Never mark a BoT solid after a non-solid or failed return.
|
||||
* Fail before tessellation when required closed-manifold B-Rep topology is
|
||||
invalid.
|
||||
* Destroy all state-owned diagnostic allocations.
|
||||
|
||||
1.2 Eliminate pointer-address ordering from all algorithms. Sort and queue by
|
||||
stable B-Rep, chart, edge, sample, and triangle IDs.
|
||||
|
||||
1.3 Remove random UV jitter. Robust predicates must handle collinearity.
|
||||
Where a geometric policy is needed for exactly coincident or on-constraint
|
||||
points, resolve it topologically and report the decision.
|
||||
|
||||
1.4 Introduce explicit result codes and structured diagnostics. Suggested
|
||||
categories:
|
||||
|
||||
* invalid input B-Rep topology;
|
||||
* invalid tolerance configuration;
|
||||
* chart construction failure;
|
||||
* invalid planar straight-line graph;
|
||||
* detria failure;
|
||||
* adaptive refinement limit;
|
||||
* manifold assembly failure;
|
||||
* geometric validation failure;
|
||||
* success.
|
||||
|
||||
Exit criteria:
|
||||
|
||||
* Repeated runs produce identical constraints and topology.
|
||||
* Frontends never export or label failed results as solids.
|
||||
* Known local defects have focused regression tests.
|
||||
|
||||
|
||||
Phase 2: Harden detria and the libbg wrapper
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Detria should remain the constrained Delaunay triangulation engine. Its role
|
||||
should be tightly bounded: triangulate an already valid planar straight-line
|
||||
graph in a topology-correct chart.
|
||||
|
||||
2.1 src/libbg/detria.hpp has been udpated to the current reviewed upstream version.
|
||||
Important changes:
|
||||
|
||||
* long collinear and nearly collinear initial point sequences;
|
||||
* guarded expansion-array advancement in robust arithmetic.
|
||||
|
||||
Upstream references:
|
||||
|
||||
* https://github.com/Kimbatt/detria
|
||||
* https://github.com/Kimbatt/detria/blob/master/DOCS.md
|
||||
* https://github.com/Kimbatt/detria/commit/610840e27320199fa4d9de40d8565ddafadceb8d
|
||||
* https://github.com/Kimbatt/detria/commit/2eaf7f8
|
||||
|
||||
2.2 Compile the translation unit instantiating detria with strict
|
||||
floating-point semantics:
|
||||
|
||||
* GCC/Clang: no fast-math, -ffp-contract=off, -frounding-math, and initially no
|
||||
LTO for that translation unit.
|
||||
* MSVC: /fp:strict, or at minimum never /fp:fast.
|
||||
|
||||
Test LTO separately on each supported compiler before allowing it for this
|
||||
translation unit.
|
||||
|
||||
2.3 Add wrapper preflight:
|
||||
|
||||
* validate all indices before dereferencing them;
|
||||
* reject nonfinite coordinates;
|
||||
* remove zero-length constraint segments;
|
||||
* detect exact coordinate duplicates;
|
||||
* coalesce duplicates only when they have the same semantic topology;
|
||||
* promote a Steiner point lying on a constraint into the constraint sequence,
|
||||
or discard it according to a documented policy;
|
||||
* detect nonincident crossing constraints and return an invalid-chart error;
|
||||
* condition coordinate range only with translation and uniform power-of-two
|
||||
scaling;
|
||||
* preserve semantic IDs through index compaction.
|
||||
|
||||
2.4 Add wrapper postconditions:
|
||||
|
||||
* every required constraint appears exactly as required in the output;
|
||||
* every output index is valid;
|
||||
* triangle orientations follow the requested convention;
|
||||
* no indexed or geometric zero-area triangle is present;
|
||||
* coverage and area agree with the chart polygon within a defined tolerance;
|
||||
* no unexpected boundary or nonmanifold edge is present.
|
||||
|
||||
2.5 Add adversarial detria tests covering:
|
||||
|
||||
* short and very long collinear prefixes;
|
||||
* nearly collinear points across a range of ULP separations;
|
||||
* cocircular and nearly cocircular points;
|
||||
* duplicate coordinate identities;
|
||||
* Steiner points exactly and nearly on constraints;
|
||||
* crossing constraints;
|
||||
* holes and nested invalid loops;
|
||||
* huge and tiny coordinate exponents;
|
||||
* input-order permutations;
|
||||
* translations, rotations, and uniform power-of-two scaling.
|
||||
|
||||
Do not require a particular diagonal for cocircular polygons unless BRL-CAD
|
||||
defines a deterministic tie-break. Validate Delaunay, constraint, and manifold
|
||||
properties instead.
|
||||
|
||||
Exit criteria:
|
||||
|
||||
* Detria tests pass under Debug, optimized Release, supported sanitizers, and
|
||||
all primary compilers.
|
||||
* Invalid inputs are classified before or at the wrapper boundary and never
|
||||
enter higher-level mesh repair as mysterious missing triangles.
|
||||
|
||||
|
||||
Phase 3: Introduce topology-correct charts
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
3.1 Add a FaceChart abstraction containing:
|
||||
|
||||
* a chart domain and its boundary loops;
|
||||
* mappings from chart vertices and edges to global topology IDs;
|
||||
* chart -> native UV evaluation;
|
||||
* native UV -> chart mapping where available;
|
||||
* singular and periodic boundary metadata;
|
||||
* trim fragments assigned to the chart;
|
||||
* validity checks for simple boundaries and a nonfolded interior.
|
||||
|
||||
3.2 Implement classification of each face and surface:
|
||||
|
||||
* regular nonperiodic;
|
||||
* periodic in U or V;
|
||||
* periodic in both directions;
|
||||
* one collapsed boundary;
|
||||
* two collapsed boundaries;
|
||||
* irregular trim topology requiring decomposition.
|
||||
|
||||
Use B-Rep topology and surface properties, not only coordinate equality, to
|
||||
identify seams and singularities.
|
||||
|
||||
3.3 Implement the cone prototype:
|
||||
|
||||
* construct one triangular/wedge chart for the lateral face;
|
||||
* represent the apex once in the chart;
|
||||
* represent the two periodic seam sides separately in chart space but map them
|
||||
to the same ordered global topology IDs;
|
||||
* map chart points to native cone UV for exact surface evaluation;
|
||||
* remove singular-trim oversplitting for this chart type;
|
||||
* ensure detria never receives a collapsed boundary segment;
|
||||
* assemble and certify all cone fixtures across scale and orientation changes.
|
||||
|
||||
3.4 Expand to:
|
||||
|
||||
* spherical pole caps;
|
||||
* cylinders and other single-periodic regular surfaces;
|
||||
* tori and doubly periodic surfaces;
|
||||
* trimmed singular surfaces;
|
||||
* faces requiring multiple charts.
|
||||
|
||||
3.5 Split trim curves at chart boundaries. Each trim fragment must be
|
||||
represented by one ordered chart constraint sequence with explicit global IDs.
|
||||
Do not merge chart seam copies by coordinate comparison.
|
||||
|
||||
Exit criteria:
|
||||
|
||||
* No chart supplied to detria has a collapsed constraint edge.
|
||||
* Cone and sphere poles are represented by explicit topology rather than
|
||||
post-triangulation vertex collapse.
|
||||
* Adjacent faces share the exact same boundary sample IDs.
|
||||
* The cone fixture suite passes full manifold and geometry certification.
|
||||
|
||||
|
||||
Phase 4: Geometric adaptive refinement and triangle quality
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
4.1 Use detria for the initial constrained triangulation of a valid chart.
|
||||
|
||||
4.2 Measure error against the original B-Rep geometry, including:
|
||||
|
||||
* curve chord error for constrained edge segments;
|
||||
* surface chord error at triangle edge midpoints and centroids;
|
||||
* normal deviation;
|
||||
* variation of the first fundamental form/Jacobian over a triangle;
|
||||
* minimum 3D angle;
|
||||
* 3D aspect and size relative to local feature scale.
|
||||
|
||||
4.3 Refine face interiors by inserting chart-space Steiner points selected from
|
||||
the worst geometric error. Re-run constrained triangulation or perform a
|
||||
locally certified insertion.
|
||||
|
||||
4.4 Refine shared B-Rep edges only through the global edge manager. A new
|
||||
sample must be inserted into the ordered edge sequence used by both adjacent
|
||||
charts before either face can be finalized.
|
||||
|
||||
4.5 Replace anisotropic global UV normalization. If a quality objective is
|
||||
anisotropic, derive it from the surface metric and use local sampling or edge
|
||||
flips. Numerical conditioning remains a uniform transformation only.
|
||||
|
||||
4.6 After the required geometric tolerance is met, improve quality with
|
||||
constraint-preserving operations such as:
|
||||
|
||||
* metric-aware edge flips;
|
||||
* additional Steiner points;
|
||||
* surface-constrained vertex relocation where topology permits it.
|
||||
|
||||
Every operation must be transactional and retain the error and manifold
|
||||
guarantees.
|
||||
|
||||
Exit criteria:
|
||||
|
||||
* Results meet explicit chord and normal tolerances.
|
||||
* Quality is stable under different native UV aspect ratios for equivalent 3D
|
||||
geometry.
|
||||
* Shared-edge refinement cannot create a T-junction.
|
||||
|
||||
|
||||
Phase 5: Replace heuristic repair with certified assembly
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
5.1 Assemble all chart triangles using explicit global topology IDs into a
|
||||
halfedge mesh.
|
||||
|
||||
5.2 Make patch replacement transactional. A replacement is committed only if:
|
||||
|
||||
* its boundary exactly matches the removed patch boundary IDs;
|
||||
* every new triangle is valid and correctly oriented;
|
||||
* its vertex links and edge incidence are valid;
|
||||
* it introduces no geometric intersections;
|
||||
* it meets surface and quality tolerances.
|
||||
|
||||
5.3 Retire projection repair paths once the corresponding chart types are
|
||||
implemented. Retain a parameterization fallback only for a patch already
|
||||
proved to be a topological disk. Require positive orientation for every mapped
|
||||
triangle, a solver residual check, and a simple nonintersecting boundary before
|
||||
calling detria.
|
||||
|
||||
5.4 Eliminate arbitrary orientation tie-breaks and repair loops whose progress
|
||||
is based only on a changed triangle set. Bound every refinement and repair
|
||||
operation with a clear failure code and diagnostic artifact.
|
||||
|
||||
Exit criteria:
|
||||
|
||||
* Standard successful tessellations do not rely on post-hoc hole or orientation
|
||||
repair.
|
||||
* Any remaining repair operation preserves its boundary and is locally
|
||||
certified before commit.
|
||||
|
||||
|
||||
Phase 6: Full certification and conversion gate
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Before returning conversion-quality success, verify:
|
||||
|
||||
Topology:
|
||||
|
||||
* every mesh edge has exactly two oppositely oriented incident faces;
|
||||
* every vertex link is one cycle for a closed manifold component;
|
||||
* no T-junctions or duplicate directed halfedges exist;
|
||||
* component count and Euler characteristic agree with the intended B-Rep;
|
||||
* all required B-Rep edge sample sequences appear in the output.
|
||||
|
||||
Geometry:
|
||||
|
||||
* every triangle has scale-aware nonzero area;
|
||||
* nonadjacent triangles do not intersect;
|
||||
* edge and surface approximation errors meet the requested tolerances;
|
||||
* normal deviations meet the requested tolerance;
|
||||
* orientation is consistent and outward where the B-Rep supplies enough
|
||||
information to determine it.
|
||||
|
||||
Quality:
|
||||
|
||||
* report minimum angle and aspect statistics;
|
||||
* enforce defined minimum quality requirements, or return a distinct result
|
||||
stating that topology and tolerance passed but quality did not;
|
||||
* prevent unbounded refinement by reporting the limiting feature or tolerance.
|
||||
|
||||
Only after these checks may a caller mark the result as BOT_SOLID.
|
||||
|
||||
|
||||
6. Test Matrix
|
||||
-------------
|
||||
|
||||
Run the complete regression set under:
|
||||
|
||||
* GCC and Clang on Linux;
|
||||
* MSVC on Windows;
|
||||
* x86-64 and ARM64 where available;
|
||||
* Debug and optimized Release;
|
||||
* LTO enabled and disabled;
|
||||
* ASan and UBSan, plus MSan for detria and parameterization tests where
|
||||
practical;
|
||||
* repeated executions with allocator perturbation.
|
||||
|
||||
Apply geometric transformations and topology-preserving reorderings:
|
||||
|
||||
* model scales from approximately 1e-12 through 1e12 where OpenNURBS and
|
||||
BRL-CAD tolerances permit meaningful representation;
|
||||
* large translations;
|
||||
* arbitrary rotations;
|
||||
* uniform power-of-two scale changes;
|
||||
* reversed or permuted face, loop, edge, and input point ordering;
|
||||
* small ULP perturbations around collinear, cocircular, seam, and pole cases.
|
||||
|
||||
The property result should be invariant even where the exact valid Delaunay
|
||||
diagonal is not unique.
|
||||
|
||||
|
||||
7. Initial Acceptance Targets
|
||||
-----------------------------
|
||||
|
||||
The first useful architecture milestone is complete when:
|
||||
|
||||
1. All ten existing cone fixtures tessellate using the new cone chart, without
|
||||
singular-boundary oversplitting or post-CDT pole collapse.
|
||||
2. All ten outputs pass full topology, vertex-link, self-intersection, and
|
||||
geometric error validation.
|
||||
3. Results are invariant under input reordering, repeated execution, supported
|
||||
compilers, and representative scale/rotation transforms.
|
||||
4. The same global edge sample sequence is demonstrably used by both faces
|
||||
adjacent to every nonsingular B-Rep edge.
|
||||
5. Detria receives only preflighted simple constraints and runs with strict
|
||||
floating-point semantics.
|
||||
6. Failure produces a bounded, classified result and a reproducible chart
|
||||
artifact rather than entering an open-ended repair loop.
|
||||
|
||||
After the cone milestone, require the same gates for spheres, periodic regular
|
||||
surfaces, and then the complete NIST set.
|
||||
|
||||
|
||||
8. Priority Order
|
||||
-----------------
|
||||
|
||||
1. Reproducible end-to-end tests and diagnostic artifacts.
|
||||
2. Local correctness bugs, initialized state, deterministic ordering, and
|
||||
fail-closed frontends.
|
||||
3. Detria update, strict compilation, wrapper preflight, and adversarial tests.
|
||||
4. Explicit topology IDs and the cone triangular-chart prototype.
|
||||
5. Sphere and periodic-surface chart support.
|
||||
6. Metric-driven adaptive refinement and triangle-quality improvement.
|
||||
7. Certified halfedge assembly and removal of heuristic projection repairs.
|
||||
8. Full NIST and cross-platform certification.
|
||||
|
||||
Do not spend substantial effort tuning best-fit projection, UV jitter, or
|
||||
post-triangulation singularity repair before the chart and explicit-topology
|
||||
layers exist. Those mechanisms operate after the information needed for a
|
||||
reliable solution has already been lost.
|
||||
@@ -14,6 +14,31 @@ foreach(im ${NIST_SAMPLE_MODELS})
|
||||
g_target(${im} "BRL-CAD Geometry Models/Sample")
|
||||
endforeach(im ${NIST_SAMPLE_MODELS})
|
||||
|
||||
# NIST 6 face 51 contains an unrelated seam excursion at a spherical pole.
|
||||
# It must retain the native rigorous result without invoking mesh repair.
|
||||
if(BUILD_TESTING AND TARGET brep-audit AND TARGET NIST_MBE_PMI_6.g)
|
||||
brlcad_add_test(
|
||||
NAME brep_cdt_nist6_native
|
||||
COMMAND
|
||||
brep-audit
|
||||
--mode
|
||||
quality
|
||||
--valid-solids-only
|
||||
--memory-limit-mib
|
||||
4096
|
||||
"${CMAKE_BINARY_DIR}/${DATA_DIR}/db/nist/NIST_MBE_PMI_6.g"
|
||||
Document_item.s
|
||||
)
|
||||
set_tests_properties(
|
||||
brep_cdt_nist6_native
|
||||
PROPERTIES
|
||||
REQUIRED_FILES "${CMAKE_BINARY_DIR}/${DATA_DIR}/db/nist/NIST_MBE_PMI_6.g"
|
||||
TIMEOUT 90
|
||||
)
|
||||
add_dependencies(check NIST_MBE_PMI_6.g)
|
||||
add_dependencies(unit NIST_MBE_PMI_6.g)
|
||||
endif()
|
||||
|
||||
cmakefiles(
|
||||
${NIST_SAMPLE_MODELS}
|
||||
CMakeLists.txt
|
||||
|
||||
@@ -4,38 +4,54 @@
|
||||
:manmanual: BRL-CAD
|
||||
|
||||
== NAME
|
||||
pixdiff - highlight samples which differ between two pix or bw files
|
||||
pixdiff - highlight differences between two pix or bw images
|
||||
|
||||
== SYNOPSIS
|
||||
|
||||
[source]
|
||||
----
|
||||
pixdiff _file1.pix_ _file2.pix_ > _out.pix_
|
||||
pixdiff _file1.bw_ _file2.bw_ > _out.bw_
|
||||
pixdiff -b _file1.bw_ _file2.bw_ > _out.bw_
|
||||
----
|
||||
|
||||
|
||||
[[description]]
|
||||
== DESCRIPTION
|
||||
|
||||
*pixdiff* compares two raw *pix*(5) or *bw*(5) images byte by byte and
|
||||
writes a difference image of the same size to standard output. Bytes that
|
||||
differ by +/-1 are written at intensity 192, while larger differences are
|
||||
written at full intensity 255. Matching bytes are written at half their
|
||||
original intensity, providing visual context for locating differences.
|
||||
By default, *pixdiff* compares raw *pix*(5) images as RGB pixels and writes
|
||||
a PIX difference image to standard output. When all three channels of a
|
||||
pixel match, it writes a dim grayscale version of that pixel to all three
|
||||
output channels. When any channel differs, each matching channel is written
|
||||
as zero, each channel differing by one is written as 192, and each channel
|
||||
differing by more than one is written as 255.
|
||||
|
||||
Because the comparison does not interpret pixel structure, no format option
|
||||
is needed and *pixdiff* is independent of image resolution. The two inputs
|
||||
must be the same size in bytes; otherwise the program reports the mismatch
|
||||
and exits. Either input file may be given as
|
||||
With *-b*, *pixdiff* compares raw *bw*(5) images one byte at a time and
|
||||
writes a BW difference image. Differing samples use the same 192 and 255
|
||||
intensities; matching samples are written at half their original intensity.
|
||||
|
||||
No image dimensions are needed. The two inputs must be the same size in
|
||||
bytes, and PIX input must contain complete three-byte pixels. Otherwise,
|
||||
*pixdiff* reports an error. Either input file may be given as
|
||||
*-* to read from standard input. Output must be redirected; *pixdiff*
|
||||
refuses to write a raw image stream to a terminal.
|
||||
|
||||
Upon completion, the count of bytes that matched, that were off by 1, and
|
||||
that were off by many is reported on standard error.
|
||||
Upon completion, *pixdiff* reports matching, off-by-one, and larger-difference
|
||||
channel or sample counts on standard error.
|
||||
|
||||
Also see *bwdiff*(1), which can also operate on *pix*(5) files and can
|
||||
produce other kinds of difference output.
|
||||
*bwdiff -n -b* produces a similar BW highlight image, but writes 255 rather
|
||||
than 192 for off-by-one differences. The default *bwdiff*(1) output instead
|
||||
shows signed differences around a neutral gray.
|
||||
|
||||
|
||||
[[options]]
|
||||
== OPTIONS
|
||||
|
||||
*-b*::
|
||||
Treat both inputs and the output as one-byte-per-pixel BW images. Without
|
||||
this option, use three-byte-per-pixel PIX images.
|
||||
|
||||
*-h*::
|
||||
Show usage information.
|
||||
|
||||
|
||||
[[examples]]
|
||||
@@ -59,10 +75,10 @@ on standard error give a quick quantitative sense of how much changed.
|
||||
====
|
||||
[source,bash]
|
||||
----
|
||||
pixdiff depth_a.bw depth_b.bw > depth_diff.bw
|
||||
pixdiff -b depth_a.bw depth_b.bw > depth_diff.bw
|
||||
----
|
||||
Writes a grayscale highlight image using the same byte comparison and
|
||||
statistics as the color example; no format option is needed.
|
||||
Writes a grayscale highlight image using byte-wise comparison. Matching
|
||||
samples retain half their input intensity as context.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ Link with *-lanalyze*.
|
||||
The public interface is organized across the headers included by
|
||||
_analyze.h_, including _analyze/diff.h_, _analyze/density.h_,
|
||||
_analyze/grid.h_, _analyze/heal.h_, _analyze/info.h_, _analyze/pnts.h_,
|
||||
_analyze/polygonize.h_, _analyze/nirt.h_, _analyze/voxelize.h_, and
|
||||
_analyze/contour.h_, _analyze/polygonize.h_, _analyze/nirt.h_,
|
||||
_analyze/voxelize.h_, and
|
||||
_analyze/worker.h_.
|
||||
|
||||
== DESCRIPTION
|
||||
@@ -32,7 +33,7 @@ to gather the information they need.
|
||||
Facilities provided by the library include model differencing, material
|
||||
density and mass property analysis, ray-based gridding and sampling,
|
||||
geometry healing, model information queries, point-cloud generation and
|
||||
processing, polygonization and voxelization of geometry, and the
|
||||
processing, manifold dual contouring and voxelization of geometry, and the
|
||||
interactive ray-tracing engine used by the *nirt*(1) command. A parallel
|
||||
worker interface supports distributing analysis work across multiple
|
||||
threads.
|
||||
|
||||
@@ -200,6 +200,56 @@ ____
|
||||
|
||||
The LIBRT_DEBUG environment variable may be set to the hexadecimal bit vector as described by the DEBUG_* flags listed in the raytrace.h header. Similarly, many LIBRT applications provide a "-x #" command-line option which will take precedence over the environment variable.
|
||||
|
||||
The RT_DEBUG_RTCMP flag (0x10000000) (set with LIBRT_DEBUG=10000000)
|
||||
sets up rtcmp capture, but recording starts disabled. Set
|
||||
LIBRT_RTCMP_FILE before enabling the flag, then call
|
||||
rt_rtcmp_capture_set_enabled(1) from the application or a debugger to
|
||||
record selected rays. Call rt_rtcmp_capture_set_enabled(0) to stop
|
||||
recording. Enabling returns -1 if the debug flag is unset or the output
|
||||
writer failed to initialize; disabling returns 0 even before setup. A
|
||||
ray must begin while recording is enabled, and leaving recording
|
||||
disabled when it finishes suppresses its record. The debug flag must be
|
||||
set before concurrent ray shooting; use the setter to change recording
|
||||
state while rays are running.
|
||||
|
||||
Captured hits append one high-precision rtcmp JSON record per ray with
|
||||
final partitions. Misses produce no record. librt never clears the
|
||||
output file; the caller must clear or rotate it before a new comparison
|
||||
as needed. Separate processes must not write to the same file
|
||||
concurrently. Pending records are flushed within two seconds under
|
||||
normal operation and during normal process shutdown; call
|
||||
rt_rtcmp_capture_flush() to wait for pending records when using a
|
||||
debugger. Disabling recording does not discard records already queued. A
|
||||
process terminated abnormally may lose pending records. rtcmp comparison
|
||||
files should use the same hit-only capture mode on both sides; its
|
||||
generation mode also includes misses. The output can be expanded to also
|
||||
include primitive segments if LIBRT_RTCMP_PRIMITIVES is set to 1 before
|
||||
capture is set up.
|
||||
|
||||
For example, run a single-threaded ray tracing application in a debugger
|
||||
and stop at `rt_shootray` for the suspect ray. Capture just that call,
|
||||
then compare it with a capture of the same ray from a reference build:
|
||||
|
||||
[source,shell]
|
||||
----
|
||||
$ LIBRT_DEBUG=10000000 LIBRT_RTCMP_FILE=trial.json \
|
||||
gdb --args ./ray_application model.g
|
||||
(gdb) break rt_shootray
|
||||
(gdb) run
|
||||
(gdb) call rt_rtcmp_capture_set_enabled(1)
|
||||
(gdb) finish
|
||||
(gdb) call rt_rtcmp_capture_set_enabled(0)
|
||||
(gdb) call rt_rtcmp_capture_flush()
|
||||
(gdb) quit
|
||||
$ rtcmp -c reference.json trial.json
|
||||
----
|
||||
|
||||
Continue to the suspect ray before enabling capture if the breakpoint
|
||||
first stops on another ray. Produce `reference.json` with the same ray
|
||||
and the same capture mode; set LIBRT_RTCMP_PRIMITIVES=1 for both runs
|
||||
when primitive segments are needed to locate a difference before Boolean
|
||||
evaluation.
|
||||
|
||||
The LIBRT_V4FLIP environment variable may be set to a true or false value to respectively force or disable endianness interpretation of v4 geometry database files. The default behavior is to automatically detect whether flipping the endian interpretation will help LIBRT read a binary-incompatible v4 geometry database file. Setting LIBRT_V4FLIP will override automatic detection.
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ facetize - Creates __new_object__ using __old_object__ as a guide, creating an e
|
||||
|
||||
[source]
|
||||
----
|
||||
facetize [-h] [-v] [-q] [-n] [-r] [-s suffix] [-p prefix] [--in-place] [--max-time #] [--max-pnts #] [--resume] [--methods m1,m2,...] [--method-opts METHOD opt1=val opt2=val...] [--no-empty] [--log-file filename] [--nmg-booleval] [--disable-fixup] [--perturb] [--no-perturb] [--perturb-sa-tol #] [--perturb-vol-tol #] [--tolerate-failures] [-B -t threshold] {old_object new_object}
|
||||
facetize [-h] [-v] [-q] [-n] [-r] [-j jobs] [-s suffix] [-p prefix] [--in-place] [--max-time #] [--max-pnts #] [--resume] [--methods m1,m2,...] [--method-opts METHOD opt1=val opt2=val...] [--no-empty] [--log-file filename] [--nmg-booleval] [--disable-fixup] [--perturb] [--no-perturb] [--perturb-sa-tol #] [--perturb-vol-tol #] [--tolerate-failures] [-B -t threshold] {old_object new_object}
|
||||
----
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ By default, the Manifold BoT path can make use of a coplanarity-avoidance pertur
|
||||
|
||||
When the perturbation step is active, per-region *facetize* validates the initial Boolean output against the original CSG geometry using a Crofton stochastic raytrace. The default tolerance thresholds that trigger a perturb retry are 10% for both surface area and volume; these can be adjusted with *--perturb-sa-tol* and *--perturb-vol-tol*. At the end of a per-region facetization run *facetize* prints a concise validation summary listing how many regions passed, triggered a perturb retry, or encountered one of the following noteworthy conditions:
|
||||
|
||||
_Unbounded halfspace (validation unavailable)_: Crofton surface-area and volume comparisons require a finite sampling domain. A standalone halfspace, or another CSG result without finite bounds, is therefore not sampled and is counted as having validation unavailable. A bounded object intersected with or subtracted by a halfspace can still be validated because its finite operand supplies the sampling bounds.
|
||||
|
||||
_Sub-mm / few-hit bypass (accepted with note)_: When the Crofton sampler finds only a very small number of ray-surface intersections (below an internal threshold of 50 crossings), the sampling noise alone would exceed 14% relative error, making any SA comparison meaningless. Geometry in this category is typically sub-millimetre in cross-section (e.g., a TGC with a radius under 0.2 mm) but the stochastic sampler has confirmed that some geometry is present. The BoT conversion is accepted and the region is counted separately in the summary. Users should verify that such extreme geometry reflects actual modeling intent rather than an accidental near-degenerate primitive.
|
||||
|
||||
_Zero-hit replacement_: When the Crofton sampler fires rays but records zero intersections with the CSG geometry while the corresponding BoT is non-empty, *facetize* concludes that the Boolean evaluation most likely produces empty geometry (for example, a subtractor that fully engulfs the base object). In this case the non-empty BoT is discarded and replaced with an empty BoT to match the expected raytrace behavior. If this replacement appears unexpected for a given region, the *lint* command can be used to inspect the result.
|
||||
@@ -61,13 +63,21 @@ Different tolerance settings will create finer or coarser meshes. See the *tol*
|
||||
|
||||
Primitive tessellation is performed in a child process against a private working copy of the database in the BRL-CAD cache. The original database is not modified as individual primitive meshes are generated. Only after the conversion succeeds are the requested results transferred from the working database. This separation also permits an interrupted conversion to retain its intermediate state for use with *--resume*.
|
||||
|
||||
The parent command sends primitive names to a persistent tessellation process one at a time. The child reports a result for each name, so a timeout, conversion failure, or child-process crash that occurs before a database write can be associated directly with the affected primitive. The child is restarted when necessary and processing continues with the next name; the parent does not subdivide and retry the whole group to discover which object failed. Unless output is suppressed with *--quiet*, long tessellation batches also report their current object and completed-object count at approximately five-second intervals.
|
||||
The parent command sends primitive names to a small pool of persistent tessellation processes. Each child reports a result for every name, so a timeout, conversion failure, or child-process crash can be associated directly with the affected primitive. A child is restarted when necessary and processing continues with the next name; the parent does not subdivide and retry the whole group to discover which object failed. Unless output is suppressed with *--quiet*, long tessellation batches also report their completed-object count and active-worker state at approximately five-second intervals.
|
||||
|
||||
Database updates use a separate write handshake. After preparing a mesh in memory, the child announces that it is ready to write and reports the approximate mesh size, then waits. The parent acknowledges that it is waiting for the write before the child may update the working database. The child explicitly reports when the update is complete. Consequently, the normal per-object tessellation limit, including *--max-time* and method-specific limits, applies to mesh preparation and does not interrupt an acknowledged database write.
|
||||
By default, *facetize* uses at most two worker processes and only enables the second when both CPU and memory information indicate it is safe. It reserves the larger of one GiB or one quarter of total physical memory for the rest of the system. Starting another job also requires at least 512 MiB beyond that reserve, or the largest worker resident-memory measurement seen so far if that is greater. If the platform cannot report the required memory information, *facetize* falls back to one worker. The available CPU count is divided among worker processes and is also imposed as the thread limit inside each worker, preventing nested algorithms from independently using all CPUs. *--jobs* changes the maximum process count; it does not bypass the memory admission checks.
|
||||
|
||||
Per-region BoT conversion uses this worker policy for both independent CSG validation and initial Manifold Boolean evaluation. When more than one worker is available, persistent validation processes each open their own read-only handle to the original database and compute Crofton surface-area and volume references for different regions. Persistent Boolean processes then evaluate different region trees using independent read-only handles to one shared snapshot of the tessellated working database. That snapshot also serves as the rollback checkpoint, so increasing *--jobs* does not create additional full database copies. This process isolation avoids sharing mutable raytrace preparation or database state between concurrent evaluations. Each result remains associated with the requested region name. The parent commits completed BoTs through one writer and continues to perform hierarchy changes, validation decisions, and any perturb retries serially. A one-worker run retains the established in-process order and avoids the snapshot phase.
|
||||
|
||||
Validation and Boolean workers report their completed-region count and active-worker state approximately every five seconds. When *--max-time* is specified, it also bounds each parallel CSG reference or Boolean request; a timed-out process is terminated, and the failure is associated with that region. Failure or timeout of supplementary CSG validation does not cause unrelated regions to be retried.
|
||||
|
||||
Database updates use a separate write handshake. After preparing a mesh in memory, a child announces that it is ready to write and reports the approximate mesh size and its current resident memory, then waits. The parent grants write permission, the child acknowledges when it is starting the write, and the child explicitly reports when the update is complete. Consequently, the normal per-object tessellation limit, including *--max-time* and method-specific limits, applies to mesh preparation and does not interrupt an acknowledged write.
|
||||
|
||||
With multiple workers, primitive tessellation children write to small staging databases rather than modifying the shared working database. Region Boolean children read one immutable cache-local snapshot while completed results are committed to the working database. The snapshot doubles as the recovery checkpoint and is opened independently by each worker; it is not duplicated per worker. For very large files, snapshot preparation reports the amount copied and elapsed time at five-second intervals. A region result is first completed in memory and enters the write handshake only when it is ready to replace the prior staged result. After a child reports a completed staging write, the parent submits that result to a single persistent writer process. The writer announces that it is ready, waits for the parent's permission, acknowledges that it is starting the write, transfers the result through one persistent working-database handle, and reports completion. This permits tessellation and initial region Boolean evaluation to proceed in parallel without concurrent database allocation updates or repeated full directory scans, while allowing the parent to supervise the canonical database write itself. With one worker, the established direct path avoids the extra staging I/O and database snapshot.
|
||||
|
||||
Before the first primitive conversion, the parent measures write throughput on the same cache filesystem as the working database. It writes a temporary file in 1 MiB chunks until approximately one second has elapsed or 128 MiB has been written, then removes the file immediately. Successful mesh writes of at least 1 MiB subsequently refine this profile. For each database update, the parent allows ten times the projected write time, with a minimum of five seconds and a maximum of five minutes. This measured allowance protects legitimate long writes on slow storage while still detecting a genuinely stalled update.
|
||||
|
||||
If a child fails before write permission is granted, the working database is known to be unchanged and no rollback is needed. A failure, crash, or write-deadline expiration after permission is granted but before completion is ambiguous: the working database may contain a partial update. In that case *facetize* restores its cache-local checkpoint and replays only the primitive conversions already known to have succeeded. The original database remains untouched during this recovery.
|
||||
If a child fails before acknowledging that its write has started, the working database is known to be unchanged and no rollback is needed. In parallel mode, a failure during a child write affects only that worker's disposable staging database, so the failed object is reported and the worker can be restarted without replaying unrelated objects. A serial child failure during a direct write, or a writer-process failure after acknowledging write start, is ambiguous because the working database may contain a partial update. In those cases *facetize* restores its cache-local checkpoint and replays only the conversions not already tied to a specific failure. The original database remains untouched during this recovery.
|
||||
|
||||
|
||||
[[facetize_options]]
|
||||
@@ -94,6 +104,10 @@ Instructs *facetize* to produce NMG solids rather than BoT solids.
|
||||
Rather than converting all specified inputs to one top level object, walk specified trees and individually convert regions to facetized objects. This mode is useful when wanting to display models in shaded mode while preserving color and region ID properties. If no regions are present a normal conversion is performed instead.
|
||||
|
||||
|
||||
*-j, --jobs #*::
|
||||
Set the maximum number of facetize worker processes. A value of 1 forces serial processing. The default value of 0 selects a conservative automatic limit based on available CPUs and memory. Memory checks may delay or reduce parallel work even when a larger value is requested. In per-region BoT mode the same limit controls parallel CSG reference sampling and initial Manifold Boolean evaluation.
|
||||
|
||||
|
||||
*-s, --suffix*::
|
||||
User specified suffix to use avoiding naming conflicts (naming conflict avoidance is needed when creating objects in *-r* mode.)
|
||||
|
||||
@@ -119,7 +133,7 @@ Resume an incomplete facetization process (will utilize cached data from a previ
|
||||
|
||||
|
||||
*--methods*::
|
||||
Accepts a comma separated list of one or more facetization methods to use. The default is to try all algorithms available as needed, but if a user knows they want to try a specific methodology they can call it out using this option.
|
||||
Accepts a comma separated list of one or more facetization methods to use. The default tries NMG tessellation first, Manifold Dual Contouring (MDC) second, and SPSR as a final fallback. Other listed methods may be selected explicitly if a user wants to try a specific methodology.
|
||||
|
||||
|
||||
*--method-opts*::
|
||||
@@ -231,21 +245,15 @@ mged>**facetize --tolerate-failures large_model large_model.partial.bot**
|
||||
|
||||
====
|
||||
|
||||
.Increase Decimation in CM Mode
|
||||
.Refine a Manifold Dual Contouring conversion
|
||||
[example]
|
||||
====
|
||||
mged>**facetize --methods CM --method-opts feature_size=0.06 d_feature_size=2.0 ellipse.r ell.bot**
|
||||
This selects MDC directly and requests a finer cell size with a larger ray
|
||||
budget than the default:
|
||||
|
||||
The default settings on this object in CM mode will produce a little over 8000 faces in the final bot, but the addition of the *d_feature_size* option with a >1.5 value has dramatically reduced the count to 104.
|
||||
mged>**facetize --methods MDC --method-opts "MDC feature_size=0.1 max_rays=400000" ellipse.r ell.bot**
|
||||
|
||||
====
|
||||
|
||||
.Decrease Decimation in CM Mode
|
||||
[example]
|
||||
====
|
||||
mged>**facetize --methods CM --method-opts feature_size=0.06 d_feature_size=0.01 ellipse.r ell.bot**
|
||||
|
||||
If the intent is preserve a more detailed mesh, reducing *decimation-feature-size* will keep more of the original triangles - in the above example, almost 250 thousand of the original 6.9 million faces were retained. NOTE: use lower values here cautiously, as extremely large meshes can be very difficult for many tools to deal with.
|
||||
Smaller feature sizes increase mesh detail, ray count, memory use, and runtime.
|
||||
|
||||
====
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ Check for references to objects or files that are missing (not present in the da
|
||||
|
||||
|
||||
*-I*, *--invalid-shape* [check [check ...]]::
|
||||
Check for shapes in the database that do not satisfy one or more properties required for them to be valid definitions, where correcting the problem requires a change to the object's definition. (As of this writing the only implemented check is for BoT object solidity, but many more are possible and planned.) (Objects invalid due to missing objects are potentially valid without the necessity of changing the object's own definition - e.g. the missing object/file can be added. That category of error can be searched for with the *-M* check.)
|
||||
Check for shapes in the database that do not satisfy one or more properties required for them to be valid definitions, where correcting the problem requires a change to the object's definition. Individual checks may be selected with _type:check_ names such as *brep:opennurbs*. Objects invalid because they refer to missing objects or files are reported by *-M* instead.
|
||||
|
||||
|
||||
*-F*, *--filter*::
|
||||
@@ -81,6 +81,21 @@ With *--perturb*, the reference BoT is the actual output that *facetize* would p
|
||||
Fractional tolerance for raytrace validation comparisons (default: *0.10* = 10%). A value of 0.05 requires the two independent estimates to agree within 5%. Lower values are stricter. Only meaningful when *--raytrace* is also specified.
|
||||
|
||||
|
||||
[[brep_checks]]
|
||||
== B-REP CHECKS
|
||||
|
||||
The B-Rep invalid-shape checks include OpenNURBS validity, excessive stored tolerances, disagreement between edge curves and their surface trim paths, collapsed singular boundaries, and sampled trim-loop crossings. Their names are listed by *lint -h*. The full JSON report identifies the affected face, loop, trim, and edge where applicable.
|
||||
|
||||
The *brep:fast_shading* check runs the display-quality face tessellator and reports individual faces it cannot triangulate. Genuine failures include the tessellation result, processing stage, and diagnostic message. Faces not processed because a resource limit was reached are reported separately as inconclusive. Degenerate or tolerance-empty faces are considered successfully handled. Their counts, and the count of successful approximations, accompany any reported issue but are not classified as failures.
|
||||
|
||||
Fast shading is intentionally an explicit-only check because it is more expensive than the structural sampling checks. It has a 30-second per-object time budget, checked between face and refinement jobs, in addition to the display tessellator's default memory, point, and triangle budgets. It also attempts invalid B-Reps because the display path is designed to recover partial geometry from them. For example:
|
||||
|
||||
[source]
|
||||
----
|
||||
lint -I brep:fast_shading -j shading.json object
|
||||
----
|
||||
|
||||
|
||||
[[raytrace_mode]]
|
||||
== RAYTRACE MODE
|
||||
|
||||
|
||||
+13
-16
@@ -679,23 +679,21 @@ Convert CSG geometry to a 3D voxel grid by:
|
||||
fill fraction
|
||||
* Level-of-detail parameter controls sub-voxel sampling
|
||||
|
||||
==== 7.5 Implicit Surface Polygonizer (Marching Cubes / Tetrahedral)
|
||||
==== 7.5 Manifold Dual Contouring
|
||||
|
||||
An implicit surface polygonizer (Bloomenthal tetrahedral marching) for
|
||||
converting any inside/outside function to a triangle mesh.
|
||||
`contour.cpp` evaluates CSG geometry directly with `librt` and constructs
|
||||
closed, oriented triangle meshes:
|
||||
|
||||
`polygonizer.c` (based on Jules Bloomenthal’s "`An Implicit Surface
|
||||
Polygonizer,`" Graphics Gems IV, 1994) converts any implicit function
|
||||
(inside/outside test) to a triangle mesh:
|
||||
|
||||
* Maintains a hash table of evaluated lattice corners and edges
|
||||
* Expanding front: starts at a seed point and spreads outward, testing
|
||||
adjacent cube faces
|
||||
* Each transitive cube is optionally decomposed into 6 tetrahedra for
|
||||
sub-cube accuracy
|
||||
* Edge-surface crossing is found by bisection (`converge`) then the
|
||||
vertex normal is estimated from finite differences
|
||||
* Used in `libanalyze` for converting CSG regions to faceted form
|
||||
* Recursively refines an implicit octree using cached axis-aligned rays along
|
||||
node box edges and interior quarter- and center-lines
|
||||
* Reuses complete ray partitions at the conforming finest grid and retains
|
||||
evaluated boundary points with analytic surface normals
|
||||
* Resolves ambiguous faces consistently and permits multiple vertices in a
|
||||
cell to preserve local topology
|
||||
* Places vertices with bounded quadratic-error minimization
|
||||
* Validates the resulting indexed mesh before returning it
|
||||
* Backs the legacy `analyze_polygonize` interface without retaining the old
|
||||
seed-driven marching-tetrahedra implementation
|
||||
|
||||
==== 7.6 Geometry Differencing (raydiff)
|
||||
|
||||
@@ -1712,7 +1710,6 @@ import
|
||||
runtime
|
||||
* *MT19937*: Mersenne Twister PRNG
|
||||
* *pbrt-v3* (derived): HLBVH construction algorithm
|
||||
* *Bloomenthal polygonizer*: implicit surface polygonization
|
||||
* *Screened Poisson Reconstruction* (Michael Kazhdan): surface
|
||||
reconstruction from point clouds
|
||||
* *Ball Pivoting Algorithm*: surface reconstruction
|
||||
|
||||
@@ -55,7 +55,6 @@ brlcad_manage_files(pbrt.txt ${DOC_DIR}/legal/embedded TRIGGER
|
||||
brlcad_manage_files(picohash.txt ${DOC_DIR}/legal/embedded TRIGGER gist)
|
||||
brlcad_manage_files(pixcii.txt ${DOC_DIR}/legal/embedded TRIGGER libicv)
|
||||
brlcad_manage_files(point_in_polygon.txt ${DOC_DIR}/legal/embedded TRIGGER libbg)
|
||||
brlcad_manage_files(polygonizer.txt ${DOC_DIR}/legal/embedded TRIGGER libanalyze)
|
||||
brlcad_manage_files(predicates.txt ${DOC_DIR}/legal/embedded TRIGGER libbg)
|
||||
brlcad_manage_files(profont.txt ${DOC_DIR}/legal/embedded TRIGGER libbu)
|
||||
brlcad_manage_files(pstdint.txt ${DOC_DIR}/legal/embedded TRIGGER libbu)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
https://www.bloomenthal.com/JBloom/code/polygonizer.c
|
||||
|
||||
Authored by Jules Bloomenthal, Xerox PARC.
|
||||
Copyright (c) Xerox Corporation, 1991. All rights reserved.
|
||||
Permission is granted to reproduce, use and distribute this code for
|
||||
any and all purposes, provided that this notice appears in all copies.
|
||||
|
||||
file:/src/libanalyze/polygonizer.c
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include "analyze/defines.h"
|
||||
#include "analyze/contour.h"
|
||||
#include "analyze/debug.h"
|
||||
#include "analyze/diff.h"
|
||||
#include "analyze/density.h"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
set(
|
||||
analyze_headers
|
||||
contour.h
|
||||
debug.h
|
||||
defines.h
|
||||
density.h
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/* C O N T O U R . H
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
/** @addtogroup libanalyze
|
||||
* @{ */
|
||||
/** @file analyze/contour.h
|
||||
*
|
||||
* Ray-driven manifold dual contouring of evaluated BRL-CAD geometry.
|
||||
*/
|
||||
|
||||
#ifndef ANALYZE_CONTOUR_H
|
||||
#define ANALYZE_CONTOUR_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "vmath.h"
|
||||
#include "analyze/defines.h"
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
#define ANALYZE_MDC_DEFAULT_MAX_RAYS 0
|
||||
#define ANALYZE_MDC_DEFAULT_MINIMUM_FREE_MEM 150000000
|
||||
#define ANALYZE_MDC_DEFAULT_MIN_DEPTH 2
|
||||
#define ANALYZE_MDC_DEFAULT_MAX_DEPTH 10
|
||||
#define ANALYZE_MDC_MAX_DEPTH 20
|
||||
#define ANALYZE_MDC_DEFAULT_MAX_TIME 600
|
||||
|
||||
struct db_i;
|
||||
/** Resource and accuracy controls for analyze_mdc(). */
|
||||
struct analyze_mdc_params {
|
||||
/** Requested maximum finest-cell edge length. Zero selects an
|
||||
* implementation default based on the model bounds. */
|
||||
fastf_t feature_size;
|
||||
|
||||
/** Optional user ceiling for the automatically derived ray budget.
|
||||
* Zero applies only geometry, depth, time, and memory limits. */
|
||||
size_t max_rays;
|
||||
|
||||
/** Abort before available memory drops below this many bytes. */
|
||||
size_t minimum_free_mem;
|
||||
|
||||
/** Minimum and maximum power-of-two grid depths. */
|
||||
int min_depth;
|
||||
int max_depth;
|
||||
|
||||
/** Maximum wall-clock processing time in seconds. Zero is unlimited. */
|
||||
int max_time;
|
||||
|
||||
/** Emit progress diagnostics when non-zero. */
|
||||
int verbosity;
|
||||
};
|
||||
|
||||
#define ANALYZE_MDC_PARAMS_DEFAULT { \
|
||||
0.0, \
|
||||
ANALYZE_MDC_DEFAULT_MAX_RAYS, \
|
||||
ANALYZE_MDC_DEFAULT_MINIMUM_FREE_MEM, \
|
||||
ANALYZE_MDC_DEFAULT_MIN_DEPTH, \
|
||||
ANALYZE_MDC_DEFAULT_MAX_DEPTH, \
|
||||
ANALYZE_MDC_DEFAULT_MAX_TIME, \
|
||||
0 \
|
||||
}
|
||||
|
||||
enum analyze_mdc_status {
|
||||
ANALYZE_MDC_OK = 0,
|
||||
ANALYZE_MDC_INVALID_INPUT = 1,
|
||||
ANALYZE_MDC_PREP_FAILED = 2,
|
||||
ANALYZE_MDC_NO_SURFACE = 3,
|
||||
ANALYZE_MDC_RAY_LIMIT = 4,
|
||||
ANALYZE_MDC_TIMEOUT = 5,
|
||||
ANALYZE_MDC_MEMORY_LIMIT = 6,
|
||||
ANALYZE_MDC_AMBIGUOUS = 7,
|
||||
ANALYZE_MDC_NOT_MANIFOLD = 8
|
||||
};
|
||||
|
||||
/**
|
||||
* Evaluate @p object with librt and construct an indexed triangle mesh.
|
||||
*
|
||||
* On success, the caller owns @p faces and @p vertices and must release them
|
||||
* with bu_free(). Failure leaves all outputs empty.
|
||||
*/
|
||||
ANALYZE_EXPORT extern enum analyze_mdc_status
|
||||
analyze_mdc(int **faces, size_t *num_faces, point_t **vertices,
|
||||
size_t *num_vertices, const char *object, struct db_i *dbip,
|
||||
const struct analyze_mdc_params *params);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* ANALYZE_CONTOUR_H */
|
||||
|
||||
/** @} */
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -43,9 +43,29 @@ struct analyze_polygonize_params {
|
||||
size_t minimum_free_mem;
|
||||
int verbosity;
|
||||
};
|
||||
#define ANALYZE_POLYGONIZE_PARAMS_DEFAULT { 0, 0, 30, 150000000, 0 }
|
||||
#define ANALYZE_POLYGONIZE_DEFAULT_MAX_CYCLE_TIME 30
|
||||
#define ANALYZE_POLYGONIZE_DEFAULT_MINIMUM_FREE_MEM 150000000
|
||||
#define ANALYZE_POLYGONIZE_PARAMS_DEFAULT { 0, 0, \
|
||||
ANALYZE_POLYGONIZE_DEFAULT_MAX_CYCLE_TIME, \
|
||||
ANALYZE_POLYGONIZE_DEFAULT_MINIMUM_FREE_MEM, 0 }
|
||||
|
||||
/**
|
||||
* Legacy polygonization interface, implemented by analyze_mdc().
|
||||
*
|
||||
* @p size is the requested maximum cell edge length. @p p_s is retained for
|
||||
* source and binary compatibility but is no longer used as a starting seed.
|
||||
* The caller owns successful output arrays and must release them with
|
||||
* bu_free(). Failure leaves all outputs empty.
|
||||
*
|
||||
* @return 0 on success
|
||||
* @return -1 on invalid input or polygonization failure
|
||||
* @return 2 when a time limit is reached
|
||||
* @return 3 when the minimum-free-memory limit is reached
|
||||
*/
|
||||
ANALYZE_EXPORT extern int
|
||||
analyze_polygonize(int **faces, int *num_faces, point_t **vertices, int *num_vertices, fastf_t size, point_t p_s, const char *obj, struct db_i *dbip, struct analyze_polygonize_params *p);
|
||||
analyze_polygonize(int **faces, int *num_faces, point_t **vertices,
|
||||
int *num_vertices, fastf_t size, point_t p_s, const char *obj,
|
||||
struct db_i *dbip, struct analyze_polygonize_params *p);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "bg/clip.h"
|
||||
#include "bg/lseg.h"
|
||||
#include "bg/obr.h"
|
||||
#include "bg/pca.h"
|
||||
#include "bg/plane.h"
|
||||
#include "bg/polygon.h"
|
||||
#include "bg/sat.h"
|
||||
|
||||
+4
-4
@@ -50,12 +50,12 @@ BG_EXPORT extern int bg_lseg_clip(fastf_t *xp1, fastf_t *yp1, fastf_t *xp2, fast
|
||||
* The RPP has faces parallel to the coordinate planes and is defined
|
||||
* by a minimum point and a maximum point.
|
||||
*
|
||||
* FIXME: the function name implies this takes a point,dir for a,b but
|
||||
* it actually takes a line segment going from points a to b!
|
||||
* Despite its historical name, this function clips the finite line segment
|
||||
* from a to b.
|
||||
*
|
||||
* Returns -
|
||||
* 0 if ray does not hit RPP,
|
||||
* !0 if ray hits RPP.
|
||||
* 0 if the segment does not hit the RPP,
|
||||
* !0 if the segment hits the RPP.
|
||||
*
|
||||
* Implicit Return -
|
||||
* if !0 was returned, "a" and "b" have been clipped to the RPP.
|
||||
|
||||
+6
-3
@@ -126,9 +126,12 @@ BG_EXPORT extern int bg_3d_coplanar_obr(point_t *center,
|
||||
* \endverbatim
|
||||
*
|
||||
*
|
||||
* @param[out] pnts eight points of oriented bounding box
|
||||
* @param points_3d array of 3D points
|
||||
* @param pnt_cnt number of points in pnts array
|
||||
* @param[out] pnts eight non-NULL, caller-provided point_t storage
|
||||
* locations for the oriented bounding box vertices
|
||||
* @param[in] points_3d flat array containing 3 * pnt_cnt coordinate values
|
||||
* @param[in] pnt_cnt number of points in points_3d
|
||||
*
|
||||
* @return 0 on success and -1 for invalid input or when no box can be fit
|
||||
*/
|
||||
BG_EXPORT extern int bg_3d_obb(point_t **pnts,
|
||||
const fastf_t *points_3d,
|
||||
|
||||
+4
-4
@@ -21,7 +21,7 @@
|
||||
/*----------------------------------------------------------------------*/
|
||||
/** @addtogroup bg_pca
|
||||
*
|
||||
* Principle Component Analysis
|
||||
* Principal Component Analysis
|
||||
*
|
||||
* Calculates an XYZ coordinate system such that it aligns with the largest
|
||||
* variations in the supplied data. Intuitively, it "aligns" with the
|
||||
@@ -50,7 +50,7 @@
|
||||
* point_t p;
|
||||
* for (size_t i = 0; i < pntcnt; i++) {
|
||||
* MAT4X3PNT(p, RT, pnts[i]);
|
||||
* VMOVE(pnts[i], v);
|
||||
* VMOVE(pnts[i], p);
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
@@ -69,7 +69,7 @@ __BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Perform a Principle Component Analysis on a set of points.
|
||||
* Perform a Principal Component Analysis on a set of points.
|
||||
*
|
||||
* Outputs are a center point and XYZ vectors for the coordinate system.
|
||||
*
|
||||
@@ -88,7 +88,7 @@ BG_EXPORT extern int bg_pca(point_t *c, vect_t *xaxis, vect_t *yaxis, vect_t *za
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* BG_PLANE_H */
|
||||
#endif /* BG_PCA_H */
|
||||
/** @} */
|
||||
/*
|
||||
* Local Variables:
|
||||
|
||||
+7
-4
@@ -1171,9 +1171,10 @@ BG_EXPORT extern int bg_isect_planes(point_t pt,
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Given an origin and a normal, create a plane_t.
|
||||
* Given an origin and a normal, create a plane_t with a unit normal.
|
||||
* The input point and normal are not modified. A zero-length normal fails.
|
||||
*/
|
||||
BG_EXPORT extern int bg_plane_pt_nrml(plane_t *p, point_t pt, vect_t nrml);
|
||||
BG_EXPORT extern int bg_plane_pt_nrml(plane_t *p, const point_t pt, const vect_t nrml);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
@@ -1188,13 +1189,15 @@ BG_EXPORT extern int bg_fit_plane(point_t *c, vect_t *n, size_t npnts, point_t *
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Find the closest U,V point on the plane p to 3d point pt.
|
||||
* Find the closest U,V point on the plane p to 3d point pt. The plane normal
|
||||
* need not be unit length.
|
||||
*/
|
||||
BG_EXPORT extern int bg_plane_closest_pt(fastf_t *u, fastf_t *v, plane_t *p, point_t *pt);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Return the 3D point on the plane at parametric coordinates u, v.
|
||||
* Return the 3D point on the plane at parametric coordinates u, v. The plane
|
||||
* normal need not be unit length.
|
||||
*/
|
||||
BG_EXPORT extern int bg_plane_pt_at(point_t *pt, plane_t *p, fastf_t u, fastf_t v);
|
||||
|
||||
|
||||
+64
-10
@@ -199,6 +199,68 @@ bg_nested_poly_triangulate(int **faces, int *num_faces, point2d_t **out_pts, int
|
||||
const int *steiner, const size_t steiner_npts,
|
||||
const point2d_t *pts, const size_t npts, triangulation_t type);
|
||||
|
||||
enum bg_triangulation_reason {
|
||||
BG_TRIANGULATION_OK = 0,
|
||||
BG_TRIANGULATION_INVALID_INPUT = 1,
|
||||
BG_TRIANGULATION_INVALID_PSLG = 2,
|
||||
BG_TRIANGULATION_CROSSING_CONSTRAINTS = 3,
|
||||
BG_TRIANGULATION_INVALID_NESTING = 4,
|
||||
BG_TRIANGULATION_DETRIA_FAILED = 5,
|
||||
BG_TRIANGULATION_POSTCONDITION_FAILED = 6
|
||||
};
|
||||
|
||||
struct bg_triangulation_report {
|
||||
enum bg_triangulation_reason reason;
|
||||
int input_index;
|
||||
char message[128];
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Sanitize and triangulate a 2D polygon with holes.
|
||||
*
|
||||
* This variant returns a newly allocated point array because topology repair
|
||||
* may merge points, split intersections, or reorder contours. It first
|
||||
* removes duplicate and boundary-coincident unconstrained points. If needed,
|
||||
* it then uses integer Clipper operations to reconstruct valid contour
|
||||
* nesting before constrained Delaunay triangulation. Optional noncrossing
|
||||
* interior constraints contain two input point indices per edge. Constraints
|
||||
* removed with canceled contour regions are omitted; every surviving
|
||||
* constraint is certified in its cleaned component.
|
||||
*
|
||||
* The caller must free both faces and out_pts with bu_free.
|
||||
*
|
||||
* @param[out] report optional structured success or failure detail
|
||||
*
|
||||
* @return 0 if triangulation is successful
|
||||
* @return 1 if triangulation is unsuccessful
|
||||
*/
|
||||
BG_EXPORT extern int
|
||||
bg_nested_poly_triangulate_clean(int **faces, int *num_faces,
|
||||
point2d_t **out_pts, int *num_outpts,
|
||||
const int *poly, const size_t poly_npts,
|
||||
const int **holes_array, const size_t *holes_npts, const size_t nholes,
|
||||
const int *steiner, const size_t steiner_npts,
|
||||
const int *constraints, const size_t constraint_cnt,
|
||||
const point2d_t *pts, const size_t npts,
|
||||
struct bg_triangulation_report *report);
|
||||
|
||||
/**
|
||||
* Strict constrained-Delaunay entry point. In addition to the validation
|
||||
* performed by bg_nested_poly_triangulate, this function reports the failure
|
||||
* class and certifies the returned constraints, orientation, incidence, and
|
||||
* chart coverage. out_pts aliases pts on success and must not be freed.
|
||||
*/
|
||||
BG_EXPORT extern int
|
||||
bg_nested_poly_triangulate_strict(int **faces, int *num_faces,
|
||||
point2d_t **out_pts, int *num_outpts,
|
||||
const int *poly, const size_t poly_npts,
|
||||
const int **holes_array, const size_t *holes_npts, const size_t nholes,
|
||||
const int *steiner, const size_t steiner_npts,
|
||||
const int *constraints, const size_t constraint_cnt,
|
||||
const point2d_t *pts, const size_t npts,
|
||||
struct bg_triangulation_report *report);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Triangulate a 2D polygon without holes.
|
||||
@@ -253,15 +315,6 @@ BG_EXPORT extern int
|
||||
bg_polygon_triangulate(int **faces, int *num_faces, point_t **out_pts, int *num_outpts,
|
||||
struct bg_polygon *p, triangulation_t type);
|
||||
|
||||
|
||||
/* Test function - do not use */
|
||||
BG_EXPORT extern int
|
||||
bg_poly2tri_test(int **faces, int *num_faces, point2d_t **out_pts, int *num_outpts,
|
||||
const int *poly, const size_t poly_pnts,
|
||||
const int **holes_array, const size_t *holes_npts, const size_t nholes,
|
||||
const int *steiner, const size_t steiner_npts,
|
||||
const point2d_t *pts);
|
||||
|
||||
/*********************************************************
|
||||
Operations on 3D point types - these are assumed to be
|
||||
polygons embedded in 3D planes in space
|
||||
@@ -293,6 +346,7 @@ BG_EXPORT extern int bg_3d_polygon_area(fastf_t *area, size_t npts, const point_
|
||||
* @param[in] npts Number of point_ts, stored in pts
|
||||
* @param[in] pts all points of the polygon, sorted counter-clockwise.
|
||||
* The array mustn't contain duplicated points or non-coplanar points.
|
||||
* A zero-area polygon fails.
|
||||
*
|
||||
* @return 0 if calculation was successful
|
||||
* @return 1 if calculation failed, e.g. because one in-parameter is a NULL-pointer
|
||||
@@ -307,7 +361,7 @@ BG_EXPORT extern int bg_3d_polygon_centroid(point_t *cent, size_t npts, const po
|
||||
*@param[in] npts Number of points, pts contains
|
||||
*@param pts Array of point_ts, building a convex polygon. Duplicated points
|
||||
*aren't allowed. The points in the array will be sorted counter-clockwise.
|
||||
*@param[in] cmp Plane equation of the polygon
|
||||
*@param[in] cmp Plane equation of the polygon. Its normal must be nonzero.
|
||||
*
|
||||
*@return 0 if calculation was successful
|
||||
*@return 1 if calculation failed, e.g. because pts is a NULL-pointer
|
||||
|
||||
+20
-8
@@ -39,7 +39,9 @@
|
||||
__BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Test for an intersection between a line and an Axis-Aligned Bounding Box (AABB).
|
||||
* Test for an intersection between an infinite line and an Axis-Aligned
|
||||
* Bounding Box (AABB). ldir need not be unit length, but must be nonzero.
|
||||
* aabb_extent supplies the positive half extents along the coordinate axes.
|
||||
*
|
||||
* Returns 1 if they intersect, 0 otherwise.
|
||||
*/
|
||||
@@ -50,7 +52,10 @@ bg_sat_line_aabb(
|
||||
);
|
||||
|
||||
/**
|
||||
* Test for an intersection between a line and an Oriented Bounding Box (OBB).
|
||||
* Test for an intersection between an infinite line and an Oriented Bounding
|
||||
* Box (OBB). ldir need not be unit length, but must be nonzero. Each OBB
|
||||
* extent vector points from the center to a face center; nonzero vectors must
|
||||
* be mutually perpendicular, and a zero vector represents a degenerate axis.
|
||||
*
|
||||
* Returns 1 if they intersect, 0 otherwise.
|
||||
*/
|
||||
@@ -61,7 +66,9 @@ bg_sat_line_obb(
|
||||
);
|
||||
|
||||
/**
|
||||
* Test for an intersection between a triangle and an Axis-Aligned Bounding Box (AABB).
|
||||
* Test for an intersection between a triangle and an Axis-Aligned Bounding
|
||||
* Box (AABB). extent supplies the positive half extents along the coordinate
|
||||
* axes.
|
||||
*
|
||||
* Returns 1 if they intersect, 0 otherwise.
|
||||
*/
|
||||
@@ -72,7 +79,10 @@ bg_sat_tri_aabb(
|
||||
);
|
||||
|
||||
/**
|
||||
* Test for an intersection between a triangle and an Oriented Bounding Box (OBB).
|
||||
* Test for an intersection between a triangle and an Oriented Bounding Box
|
||||
* (OBB). Each extent vector points from the center to a face center; nonzero
|
||||
* vectors must be mutually perpendicular, and a zero vector represents a
|
||||
* degenerate axis.
|
||||
*
|
||||
* Returns 1 if they intersect, 0 otherwise.
|
||||
*/
|
||||
@@ -85,8 +95,9 @@ bg_sat_tri_obb(
|
||||
/**
|
||||
* Test for an intersection between an Axis-Aligned Bounding Box (AABB) and an
|
||||
* Oriented Bounding Box (OBB). The latter is defined by a center point and
|
||||
* three perpendicular vectors from the center to the centers of the various
|
||||
* faces.
|
||||
* three extent vectors from the center to the centers of the various faces.
|
||||
* Nonzero extent vectors must be mutually perpendicular; a zero vector
|
||||
* represents a degenerate axis.
|
||||
*
|
||||
* Returns 1 if they intersect, 0 otherwise.
|
||||
*/
|
||||
@@ -98,8 +109,9 @@ bg_sat_aabb_obb(
|
||||
|
||||
/**
|
||||
* Test for an intersection between two Oriented Bounding Boxes (OBBs). The
|
||||
* boxes are defined by a center point and three perpendicular vectors from the
|
||||
* center to the centers of the various faces.
|
||||
* boxes are defined by a center point and three extent vectors from the center
|
||||
* to the centers of the various faces. Nonzero extent vectors must be
|
||||
* mutually perpendicular; a zero vector represents a degenerate axis.
|
||||
*
|
||||
* Returns 1 if they intersect, 0 otherwise.
|
||||
*/
|
||||
|
||||
+129
-7
@@ -67,6 +67,7 @@
|
||||
#define BG_SPSR_H
|
||||
|
||||
#include "common.h"
|
||||
#include <stdint.h>
|
||||
#include "vmath.h"
|
||||
#include "bg/defines.h"
|
||||
|
||||
@@ -85,8 +86,8 @@ struct bg_3d_spsr_opts {
|
||||
int full_depth; /**< full depth */
|
||||
int base_depth; /**< coarse MG solver depth */
|
||||
int baseVcycles; /**< coarse MG solver v-cycles */
|
||||
int max_memory_GB; /**< maximum memory (in GB) */
|
||||
size_t threads; /**< number of threads to use (default is max available) */
|
||||
int max_memory_GB; /**< reserved; currently not enforced */
|
||||
size_t threads; /**< one selects serial; otherwise uses the configured pool */
|
||||
fastf_t samples_per_node; /**< minimum number of samples per node */
|
||||
fastf_t scale; /**< scale factor */
|
||||
fastf_t width; /**< voxel width */
|
||||
@@ -109,7 +110,7 @@ struct bg_3d_spsr_opts {
|
||||
#define BG_3D_SPSR_BOUNDARY_DIRICHLET 3
|
||||
|
||||
#define BG_3D_SPSR_DEFAULT_DEGREE 1 /* DEFAULT_FEM_DEGREE */
|
||||
#define BG_3D_SPSR_DEFAULT_DEPTH 11
|
||||
#define BG_3D_SPSR_DEFAULT_DEPTH 8
|
||||
#define BG_3D_SPSR_DEFAULT_KERNELDEPTH 0
|
||||
#define BG_3D_SPSR_DEFAULT_ITERATIONS 0
|
||||
#define BG_3D_SPSR_DEFAULT_FULL_DEPTH 0
|
||||
@@ -117,13 +118,13 @@ struct bg_3d_spsr_opts {
|
||||
#define BG_3D_SPSR_DEFAULT_BASEVCYCLES 0
|
||||
#define BG_3D_SPSR_DEFAULT_MAX_MEM 0
|
||||
#define BG_3D_SPSR_DEFAULT_THREADS 0
|
||||
#define BG_3D_SPSR_DEFAULT_SAMPLES_PER_NODE 1.1
|
||||
#define BG_3D_SPSR_DEFAULT_SCALE 1.0
|
||||
#define BG_3D_SPSR_DEFAULT_SAMPLES_PER_NODE 1.5
|
||||
#define BG_3D_SPSR_DEFAULT_SCALE 1.1
|
||||
#define BG_3D_SPSR_DEFAULT_WIDTH 0.0
|
||||
#define BG_3D_SPSR_DEFAULT_CONFIDENCE 0.0
|
||||
#define BG_3D_SPSR_DEFAULT_CONFIDENCE_BIAS 0.0
|
||||
#define BG_3D_SPSR_DEFAULT_CGSOLVER_ACCURACY 1.0e-3
|
||||
#define BG_3D_SPSR_DEFAULT_POINT_WEIGHT 8.0 /* DefaultPointWeightMultiplier * Degree */
|
||||
#define BG_3D_SPSR_DEFAULT_POINT_WEIGHT 2.0
|
||||
#define BG_3D_SPSR_DEFAULT_NONMANIFOLD 0
|
||||
#define BG_3D_SPSR_DEFAULT_LINEARFIT 0
|
||||
#define BG_3D_SPSR_DEFAULT_EXACT 1
|
||||
@@ -167,10 +168,131 @@ struct bg_3d_spsr_opts {
|
||||
* @return 0 if successful, else error
|
||||
*
|
||||
*/
|
||||
BG_EXPORT int bg_3d_spsr(int **faces, int *num_faces, point_t **vertices, int *num_vertices,
|
||||
DEPRECATED BG_EXPORT int bg_3d_spsr(int **faces, int *num_faces, point_t **vertices, int *num_vertices,
|
||||
const point_t *input_points_3d, const vect_t *input_normals_3d,
|
||||
int num_input_pnts, struct bg_3d_spsr_opts *opts);
|
||||
|
||||
/** Reasons the SPSR solver may request additional source samples. */
|
||||
#define BG_3D_SPSR_REFINE_RESIDUAL 0x01u
|
||||
#define BG_3D_SPSR_REFINE_NORMAL 0x02u
|
||||
#define BG_3D_SPSR_REFINE_DENSITY 0x04u
|
||||
#define BG_3D_SPSR_REFINE_SURFACE_VARIATION 0x08u
|
||||
|
||||
/** Adaptive SPSR termination states. */
|
||||
#define BG_3D_SPSR_COMPLETE 0
|
||||
#define BG_3D_SPSR_CALLBACK_STOP 1
|
||||
#define BG_3D_SPSR_POINT_LIMIT 2
|
||||
#define BG_3D_SPSR_TIME_LIMIT 3
|
||||
#define BG_3D_SPSR_NO_NEW_SAMPLES 4
|
||||
#define BG_3D_SPSR_SOLVER_ERROR 5
|
||||
|
||||
/**
|
||||
* One oriented source sample. A positive @p thickness records the length of
|
||||
* the solid ray partition that supplied the sample. Non-zero equal pair IDs
|
||||
* identify the entry and exit samples of the same partition.
|
||||
*/
|
||||
struct bg_3d_spsr_sample {
|
||||
point_t point;
|
||||
vect_t normal;
|
||||
fastf_t thickness;
|
||||
uint64_t pair_id;
|
||||
};
|
||||
|
||||
/** A spatial location at which the solver would benefit from more samples. */
|
||||
struct bg_3d_spsr_refinement_hint {
|
||||
point_t point;
|
||||
vect_t normal;
|
||||
fastf_t error;
|
||||
unsigned int reasons;
|
||||
};
|
||||
|
||||
/** Source-to-candidate measurements supplied by an adaptive callback. */
|
||||
struct bg_3d_spsr_validation {
|
||||
size_t ray_count;
|
||||
size_t source_hit_rays;
|
||||
size_t partition_mismatch_rays;
|
||||
size_t thin_partition_count;
|
||||
size_t thin_partition_mismatches;
|
||||
fastf_t endpoint_error_p95;
|
||||
fastf_t endpoint_error_p99;
|
||||
fastf_t chord_error_fraction;
|
||||
fastf_t surface_area_error_fraction;
|
||||
fastf_t volume_error_fraction;
|
||||
int passed;
|
||||
};
|
||||
|
||||
/** Information passed to an adaptive source-sampling callback. */
|
||||
struct bg_3d_spsr_refinement_request {
|
||||
size_t pass;
|
||||
fastf_t target_feature_size;
|
||||
double remaining_time;
|
||||
const point_t *vertices;
|
||||
size_t vertex_count;
|
||||
const int *faces;
|
||||
size_t face_count;
|
||||
const struct bg_3d_spsr_refinement_hint *hints;
|
||||
size_t hint_count;
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback response. Sample storage remains owned by the callback and need
|
||||
* only remain valid until the callback returns; libbg copies it immediately.
|
||||
*/
|
||||
struct bg_3d_spsr_refinement_response {
|
||||
const struct bg_3d_spsr_sample *samples;
|
||||
size_t sample_count;
|
||||
struct bg_3d_spsr_validation validation;
|
||||
/** Non-zero asks the adaptive driver to stop after this candidate. */
|
||||
int stop_refinement;
|
||||
};
|
||||
|
||||
typedef int (*bg_3d_spsr_refinement_func_t)(
|
||||
struct bg_3d_spsr_refinement_response *response,
|
||||
const struct bg_3d_spsr_refinement_request *request,
|
||||
void *client_data);
|
||||
|
||||
/** Options controlling a bounded adaptive SPSR reconstruction. */
|
||||
struct bg_3d_spsr_adaptive_opts {
|
||||
struct bg_3d_spsr_opts solver;
|
||||
size_t max_refinement_passes;
|
||||
size_t max_points;
|
||||
fastf_t target_feature_size;
|
||||
double max_time;
|
||||
};
|
||||
|
||||
/** Last-resort solve budget; callbacks may stop earlier when progress stalls. */
|
||||
#define BG_3D_SPSR_DEFAULT_REFINEMENT_PASSES 8
|
||||
#define BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT { \
|
||||
BG_3D_SPSR_OPTS_DEFAULT, \
|
||||
BG_3D_SPSR_DEFAULT_REFINEMENT_PASSES, \
|
||||
0, \
|
||||
0.0, \
|
||||
0.0 }
|
||||
|
||||
/** Summary of an adaptive SPSR attempt. */
|
||||
struct bg_3d_spsr_report {
|
||||
size_t solve_count;
|
||||
size_t initial_sample_count;
|
||||
size_t final_sample_count;
|
||||
size_t requested_sample_count;
|
||||
size_t accepted_sample_count;
|
||||
double elapsed_time;
|
||||
int termination;
|
||||
struct bg_3d_spsr_validation validation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reconstruct a surface and, when requested, obtain targeted additional
|
||||
* samples from @p refine. A callback is required when refinement passes are
|
||||
* enabled. The final candidate must be marked as passed by the callback.
|
||||
*/
|
||||
BG_EXPORT int bg_3d_spsr_adaptive(int **faces, int *num_faces,
|
||||
point_t **vertices, int *num_vertices,
|
||||
const struct bg_3d_spsr_sample *input_samples, size_t sample_count,
|
||||
const struct bg_3d_spsr_adaptive_opts *opts,
|
||||
bg_3d_spsr_refinement_func_t refine, void *client_data,
|
||||
struct bg_3d_spsr_report *report);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* BG_SPSR_H */
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ BG_EXPORT extern double bg_tri_closest_pt(
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* BG_TRI_TRI_H */
|
||||
#endif /* BG_TRI_PT_H */
|
||||
/** @} */
|
||||
/*
|
||||
* Local Variables:
|
||||
|
||||
@@ -54,7 +54,7 @@ BG_EXPORT extern int bg_isect_tri_line(point_t *isect,
|
||||
* If isect is non-NULL, it is written only when an intersection is found.
|
||||
*/
|
||||
BG_EXPORT extern int bg_isect_tri_ray(const point_t orig,
|
||||
const point_t dir,
|
||||
const vect_t dir,
|
||||
const point_t vert0,
|
||||
const point_t vert1,
|
||||
const point_t vert2,
|
||||
|
||||
+118
-3
@@ -84,10 +84,28 @@ BG_EXPORT extern int bg_trimesh_manifold_closed(int vcnt, int fcnt, fastf_t *v,
|
||||
*/
|
||||
BG_EXPORT extern int bg_trimesh_oriented(int vcnt, int fcnt, fastf_t *v, int *f);
|
||||
|
||||
/**
|
||||
* Check whether the bundled Manifold library accepts an indexed triangle
|
||||
* mesh and returns a topologically equivalent closed mesh. This is a
|
||||
* read-only certification check; it does not expose Manifold's normalized
|
||||
* output or perform a Boolean union.
|
||||
*
|
||||
* @return 1 if accepted and independently validated, 0 otherwise.
|
||||
*
|
||||
* Invalid or resource-exhausted inputs also return 0; C++ exceptions are not
|
||||
* propagated across this C API.
|
||||
*/
|
||||
BG_EXPORT extern int bg_trimesh_manifold_accepted(int vcnt, int fcnt,
|
||||
const fastf_t *v, const int *f);
|
||||
|
||||
/**
|
||||
* Check if a mesh is topologically solid. Returns 1 if the mesh is NOT SOLID
|
||||
* and 0 if the mesh is SOLID. A SOLID (0) outcome indicates the mesh satisfies
|
||||
* all three criteria: Closed, Manifold, Oriented
|
||||
* all three criteria: Closed, Manifold, Oriented. If @p bedges is non-NULL,
|
||||
* it is set to NULL when no edge diagnostics are present, or to a bu_calloc()
|
||||
* allocated array of vertex-index pairs for unmatched, misoriented, and excess
|
||||
* edges. The caller must bu_free() a non-NULL result. Use
|
||||
* bg_trimesh_solid2() when structured diagnostics and their counts are needed.
|
||||
*/
|
||||
BG_EXPORT extern int bg_trimesh_solid(int vcnt, int fcnt, fastf_t *v, int *f, int **bedges);
|
||||
|
||||
@@ -352,8 +370,8 @@ BG_EXPORT extern int bg_trimesh_3d_gc(int **ofaces, point_t **opnts, int *n_opnt
|
||||
* @param[in] f input set of faces.
|
||||
* @param[in] fcnt input face count
|
||||
*
|
||||
* @return -1 if error, otherwise return the number of times a face flipping
|
||||
* operation was performed
|
||||
* @return -1 if the input is invalid or a connected component is
|
||||
* nonorientable, otherwise return the number of face flips performed
|
||||
*/
|
||||
BG_EXPORT extern int
|
||||
bg_trimesh_sync(int *of, int *f, int fcnt);
|
||||
@@ -568,6 +586,103 @@ bg_trimesh_repair(
|
||||
struct bg_trimesh_repair_opts *opts);
|
||||
|
||||
|
||||
/**
|
||||
* Conservative controls for triangle mesh repair.
|
||||
*
|
||||
* Unlike the legacy bg_trimesh_repair_opts interface, component removal and
|
||||
* hole filling are disabled unless their corresponding flags are set. A
|
||||
* zero vertex_tolerance selects 1e-8 times the input bounding-box diagonal.
|
||||
* Absolute area limits take precedence over percentage limits. A zero area
|
||||
* limit with fill_holes enabled permits holes of any area, subject to
|
||||
* max_hole_edges. A zero max_hole_edges permits any boundary length.
|
||||
* union_components requests a regularized Manifold union of closed output
|
||||
* components, resolving compatible bounded overlaps; it is disabled by
|
||||
* default because it may change connectivity and triangulation.
|
||||
* separate_touching_vertices moves disconnected duplicate-coordinate fans
|
||||
* apart along their area-weighted normals by no more than vertex_tolerance.
|
||||
* It is disabled by default because it changes vertex positions.
|
||||
* allow_self_intersections permits hole caps and repaired surfaces to cross;
|
||||
* topological solid, orientation, degeneracy, and vertex-link checks remain
|
||||
* mandatory. require_manifold verifies that the bundled Manifold library can
|
||||
* import the final indexed mesh. Both policies are disabled by default.
|
||||
*/
|
||||
struct bg_trimesh_repair_settings {
|
||||
fastf_t vertex_tolerance;
|
||||
int remove_small_components;
|
||||
fastf_t max_component_area;
|
||||
fastf_t max_component_area_percent;
|
||||
int fill_holes;
|
||||
fastf_t max_hole_area;
|
||||
fastf_t max_hole_area_percent;
|
||||
size_t max_hole_edges;
|
||||
int max_iterations;
|
||||
int separate_touching_vertices;
|
||||
int union_components;
|
||||
int allow_self_intersections;
|
||||
int require_manifold;
|
||||
int require_solid;
|
||||
};
|
||||
|
||||
#define BG_TRIMESH_REPAIR_SETTINGS_INIT {0.0, 0, 0.0, 0.0, 0, 0.0, 0.0, 0, 10, 0, 0, 0, 0, 1}
|
||||
|
||||
/** Summary of the operations performed by bg_trimesh_repair_ex. */
|
||||
struct bg_trimesh_repair_report {
|
||||
int input_vertices;
|
||||
int input_faces;
|
||||
int output_vertices;
|
||||
int output_faces;
|
||||
int removed_faces;
|
||||
int added_faces;
|
||||
int repair_iterations;
|
||||
int separated_vertices;
|
||||
int component_union_applied;
|
||||
int manifold_normalization_applied;
|
||||
int self_intersections_allowed;
|
||||
int manifold_accepted;
|
||||
int solid;
|
||||
int rejected_hole_faces;
|
||||
int geometric_degenerate_faces;
|
||||
int unmatched_edges;
|
||||
int excess_edges;
|
||||
int misoriented_edges;
|
||||
int invalid_vertex_links;
|
||||
int reoriented_faces;
|
||||
fastf_t input_area;
|
||||
fastf_t output_area;
|
||||
fastf_t output_volume;
|
||||
fastf_t max_vertex_displacement;
|
||||
/** An allocation failure prevented completion of the repair attempt. */
|
||||
int allocation_failed;
|
||||
};
|
||||
|
||||
#define BG_TRIMESH_REPAIR_REPORT_INIT {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0.0, 0.0, 0.0, 0}
|
||||
|
||||
/**
|
||||
* Attempt a bounded, explicitly configured triangle mesh repair.
|
||||
*
|
||||
* Unless separate_touching_vertices is enabled, current operations preserve
|
||||
* input vertex positions: welding selects an existing representative,
|
||||
* topology splitting duplicates an existing point, and hole filling
|
||||
* triangulates existing boundary vertices. The report records the maximum
|
||||
* displacement when point-contact separation is requested.
|
||||
*
|
||||
* @return 1 if the input was already solid, 0 if repair produced an accepted
|
||||
* output, and -1 on invalid input or if the requested postconditions were not
|
||||
* satisfied. On return 1, the output pointers remain NULL and their counts
|
||||
* remain zero unless topology normalization required an exported copy.
|
||||
* Allocation and library exceptions are converted to return value -1.
|
||||
* The report distinguishes allocation failure from geometric rejection.
|
||||
*/
|
||||
BG_EXPORT extern int
|
||||
bg_trimesh_repair_ex(
|
||||
int **ofaces, int *n_ofaces,
|
||||
point_t **opnts, int *n_opnts,
|
||||
const int *ifaces, int n_ifaces,
|
||||
const point_t *ipnts, int n_ipnts,
|
||||
const struct bg_trimesh_repair_settings *settings,
|
||||
struct bg_trimesh_repair_report *report);
|
||||
|
||||
|
||||
/**
|
||||
* Options governing triangle mesh remeshing operations.
|
||||
*
|
||||
|
||||
+15
-12
@@ -214,8 +214,8 @@ BN_EXPORT extern void bn_mat_ae(mat_t m,
|
||||
double elev);
|
||||
|
||||
/**
|
||||
* Find the azimuth and elevation angles that correspond to the
|
||||
* direction (not including twist) given by a direction vector.
|
||||
* Find the azimuth and elevation angles, in degrees, that correspond
|
||||
* to the direction (not including twist) given by a direction vector.
|
||||
*/
|
||||
BN_EXPORT extern void bn_ae_vec(fastf_t *azp,
|
||||
fastf_t *elp,
|
||||
@@ -236,14 +236,16 @@ BN_EXPORT extern void bn_aet_vec(fastf_t *az,
|
||||
fastf_t accuracy);
|
||||
|
||||
/**
|
||||
* Find a unit vector from the origin given azimuth and elevation.
|
||||
* Find a unit vector from the origin given azimuth and elevation in
|
||||
* radians.
|
||||
*/
|
||||
BN_EXPORT extern void bn_vec_ae(vect_t vec,
|
||||
fastf_t az,
|
||||
fastf_t el);
|
||||
|
||||
/**
|
||||
* Find a vector from the origin given azimuth, elevation, and distance.
|
||||
* Find a vector from the origin given azimuth and elevation in radians,
|
||||
* and distance.
|
||||
*/
|
||||
BN_EXPORT extern void bn_vec_aed(vect_t vec,
|
||||
fastf_t az,
|
||||
@@ -310,8 +312,10 @@ BN_EXPORT extern void bn_vec_perp(vect_t new_vec,
|
||||
* space by the angle between the two. There are many candidate
|
||||
* matrices.
|
||||
*
|
||||
* The input 'from' and 'to' vectors need not be unit length.
|
||||
* MAT4X3VEC(to, m, from) is the identity that is created.
|
||||
* The input 'from' and 'to' vectors need not be unit length. They are
|
||||
* normalized internally. Near-parallel inputs are handled using the
|
||||
* angular tolerance in 'tol'. If either input cannot be normalized,
|
||||
* the result is the identity matrix.
|
||||
*
|
||||
*/
|
||||
BN_EXPORT extern void bn_mat_fromto(mat_t m,
|
||||
@@ -344,12 +348,11 @@ BN_EXPORT extern void bn_mat_zrot(mat_t m,
|
||||
double cosz);
|
||||
|
||||
/**
|
||||
* Given a direction vector D of unit length, product a matrix which
|
||||
* rotates that vector D onto the -Z axis. This matrix will be
|
||||
* suitable for use as a "model2view" matrix.
|
||||
*
|
||||
* XXX This routine will fail if the vector is already more or less
|
||||
* aligned with the Z axis.
|
||||
* Given a nonzero direction vector D, produce a matrix which
|
||||
* rotates D onto the -Z axis. D is normalized internally. Z-axis
|
||||
* directions use a deterministic roll, and a zero direction produces
|
||||
* the identity matrix. This matrix will be suitable for use as a
|
||||
* model2view matrix.
|
||||
*
|
||||
* This is done in several steps.
|
||||
*
|
||||
|
||||
@@ -37,11 +37,42 @@ __BEGIN_DECLS
|
||||
|
||||
typedef struct bn_multipoly {
|
||||
uint32_t magic;
|
||||
/**< Number of coefficients in the first polynomial variable. */
|
||||
int dgrs;
|
||||
/**< Number of coefficients in the second polynomial variable. */
|
||||
int dgrt;
|
||||
/**< Coefficient of S^s * T^t is cf[s][t]. */
|
||||
double **cf;
|
||||
} bn_multipoly_t;
|
||||
|
||||
/**
|
||||
* Allocate a zero-valued bivariate polynomial with the specified
|
||||
* coefficient counts. Both counts must be positive.
|
||||
*/
|
||||
BN_EXPORT extern struct bn_multipoly *bn_multipoly_new(int dgrs, int dgrt);
|
||||
|
||||
/** Release a bivariate polynomial allocated by bn_multipoly_new(). */
|
||||
BN_EXPORT extern void bn_multipoly_free(struct bn_multipoly *poly);
|
||||
|
||||
/** Grow a polynomial to at least the specified coefficient counts. */
|
||||
BN_EXPORT extern struct bn_multipoly *bn_multipoly_grow(struct bn_multipoly *poly,
|
||||
int dgrs,
|
||||
int dgrt);
|
||||
|
||||
/** Set the coefficient of S^s * T^t, growing the polynomial as needed. */
|
||||
BN_EXPORT extern struct bn_multipoly *bn_multipoly_set(struct bn_multipoly *poly,
|
||||
int s,
|
||||
int t,
|
||||
double value);
|
||||
|
||||
/** Return the sum of two bivariate polynomials. */
|
||||
BN_EXPORT extern struct bn_multipoly *bn_multipoly_add(const struct bn_multipoly *p1,
|
||||
const struct bn_multipoly *p2);
|
||||
|
||||
/** Return the product of two bivariate polynomials. */
|
||||
BN_EXPORT extern struct bn_multipoly *bn_multipoly_mul(const struct bn_multipoly *p1,
|
||||
const struct bn_multipoly *p2);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
#endif /* BN_MULTIPOLY_H */
|
||||
|
||||
+20
-4
@@ -45,7 +45,10 @@ __BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* Polynomial data type
|
||||
* bn_poly->cf[n] corresponds to X^n
|
||||
*
|
||||
* Coefficients are stored from the highest to the lowest degree:
|
||||
* `cf[0]` is the coefficient of `X^dgr` and `cf[dgr]` is the
|
||||
* constant coefficient.
|
||||
*/
|
||||
typedef struct bn_poly {
|
||||
uint32_t magic;
|
||||
@@ -63,6 +66,9 @@ typedef struct bn_poly {
|
||||
*
|
||||
* @brief
|
||||
* multiply two polynomials
|
||||
*
|
||||
* The output may alias either input. If the result exceeds
|
||||
* BN_MAX_POLY_DEGREE, returns BN_POLY_NULL without modifying product.
|
||||
*/
|
||||
BN_EXPORT extern struct bn_poly *bn_poly_mul(struct bn_poly *product,
|
||||
const struct bn_poly *m1,
|
||||
@@ -81,6 +87,8 @@ BN_EXPORT extern struct bn_poly *bn_poly_scale(struct bn_poly *eqn,
|
||||
* bn_poly_add
|
||||
* @brief
|
||||
* add two polynomials
|
||||
*
|
||||
* The output may alias either input.
|
||||
*/
|
||||
BN_EXPORT extern struct bn_poly *bn_poly_add(struct bn_poly *sum,
|
||||
const struct bn_poly *poly1,
|
||||
@@ -90,6 +98,8 @@ BN_EXPORT extern struct bn_poly *bn_poly_add(struct bn_poly *sum,
|
||||
* bn_poly_sub
|
||||
* @brief
|
||||
* subtract two polynomials
|
||||
*
|
||||
* The output may alias either input.
|
||||
*/
|
||||
BN_EXPORT extern struct bn_poly *bn_poly_sub(struct bn_poly *diff,
|
||||
const struct bn_poly *poly1,
|
||||
@@ -98,7 +108,10 @@ BN_EXPORT extern struct bn_poly *bn_poly_sub(struct bn_poly *diff,
|
||||
/**
|
||||
* @brief
|
||||
* Divides any polynomial into any other polynomial using synthetic
|
||||
* division. Both polynomials must have real coefficients.
|
||||
* division. Both polynomials must have real coefficients and the
|
||||
* divisor's leading coefficient must be nonzero. The quotient and
|
||||
* remainder outputs must be distinct; the quotient may alias the
|
||||
* dividend. All other output/input pairs must be distinct.
|
||||
*/
|
||||
BN_EXPORT extern void bn_poly_synthetic_division(struct bn_poly *quo,
|
||||
struct bn_poly *rem,
|
||||
@@ -108,7 +121,8 @@ BN_EXPORT extern void bn_poly_synthetic_division(struct bn_poly *quo,
|
||||
/**
|
||||
*@brief
|
||||
* Uses the quadratic formula to find the roots (in `complex' form) of
|
||||
* any quadratic equation with real coefficients.
|
||||
* any quadratic equation with real coefficients. A nonzero leading
|
||||
* coefficient need not be one.
|
||||
*
|
||||
* @return 1 for success
|
||||
* @return 0 for fail.
|
||||
@@ -144,6 +158,7 @@ BN_EXPORT extern int bn_poly_quadratic_roots(struct bn_complex roots[],
|
||||
* If D < 0, there will be three unequal real roots.
|
||||
*
|
||||
* Returns 1 for success, 0 for fail.
|
||||
* A nonzero leading coefficient need not be one.
|
||||
*/
|
||||
BN_EXPORT extern int bn_poly_cubic_roots(struct bn_complex roots[],
|
||||
const struct bn_poly *eqn);
|
||||
@@ -151,7 +166,8 @@ BN_EXPORT extern int bn_poly_cubic_roots(struct bn_complex roots[],
|
||||
/**
|
||||
*@brief
|
||||
* Uses the quartic formula to find the roots (in `complex' form)
|
||||
* of any quartic equation with real coefficients.
|
||||
* of any quartic equation with real coefficients. A nonzero leading
|
||||
* coefficient need not be one.
|
||||
*
|
||||
* @return 1 for success
|
||||
* @return 0 for fail.
|
||||
|
||||
+4
-3
@@ -100,8 +100,8 @@ BN_EXPORT extern void quat_double(quat_t qout,
|
||||
/**
|
||||
*@brief Gives the bisector of quaternions q1 and q2.
|
||||
*
|
||||
* (Could be done with quat_slerp and factor 0.5)
|
||||
* [I believe they must be unit quaternions this to work]
|
||||
* Inputs must be unit quaternions. Antipodal inputs use a valid
|
||||
* great-circle midpoint.
|
||||
*/
|
||||
BN_EXPORT extern void quat_bisect(quat_t qout,
|
||||
const quat_t q1,
|
||||
@@ -161,7 +161,8 @@ BN_EXPORT extern void quat_exp(quat_t out,
|
||||
|
||||
/**
|
||||
*@brief
|
||||
* Take the natural logarithm of a unit quaternion.
|
||||
* Take the natural logarithm of a unit quaternion. The identity
|
||||
* quaternion maps to the zero quaternion.
|
||||
* Code by Ken Shoemake.
|
||||
*/
|
||||
BN_EXPORT extern void quat_log(quat_t out,
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
extern "C++" {
|
||||
namespace brlcad {
|
||||
class BBNode;
|
||||
class SurfaceTree;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -152,8 +153,13 @@ namespace brlcad {
|
||||
|
||||
|
||||
private:
|
||||
friend class SurfaceTree;
|
||||
|
||||
BBNode(const BBNode &source);
|
||||
BBNode &operator=(const BBNode &source);
|
||||
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt,
|
||||
ON_Interval &u, ON_Interval &v,
|
||||
const ON_Surface *surface) const;
|
||||
|
||||
void removeChild(BBNode *child);
|
||||
bool intersectedBy(const ON_Ray &ray, double *tnear = NULL, double *tfar = NULL) const;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C++" {
|
||||
/* @cond */
|
||||
# include <cstddef>
|
||||
# include <vector>
|
||||
# include <list>
|
||||
/* @endcond */
|
||||
@@ -77,6 +78,12 @@ namespace brlcad {
|
||||
/** Report the depth of this node in the hierarchy */
|
||||
int depth() const;
|
||||
|
||||
/**
|
||||
* Return the conservative per-node storage estimate used to translate
|
||||
* caller byte budgets into bounded hierarchy work.
|
||||
*/
|
||||
static std::size_t estimated_allocation_size();
|
||||
|
||||
/**
|
||||
* Get 2 points defining bounding box:
|
||||
*
|
||||
|
||||
+532
-18
@@ -34,6 +34,7 @@
|
||||
#include "bv/vlist.h"
|
||||
#include "bn/tol.h"
|
||||
#include "bg/defines.h"
|
||||
#include "bg/trimesh.h"
|
||||
#include "brep/defines.h"
|
||||
|
||||
__BEGIN_DECLS
|
||||
@@ -41,29 +42,351 @@ __BEGIN_DECLS
|
||||
/* Container that holds the state of a triangulation */
|
||||
struct ON_Brep_CDT_State;
|
||||
|
||||
/* Create and initialize a CDT state with default tolerances. bv
|
||||
/* Conversion-quality tessellation result categories. These supplement the
|
||||
* legacy integer return from ON_Brep_CDT_Tessellate with a stable reason for
|
||||
* failure. */
|
||||
#define BREP_CDT_RESULT_UNATTEMPTED 0
|
||||
#define BREP_CDT_RESULT_SUCCESS 1
|
||||
#define BREP_CDT_RESULT_PARTIAL 2
|
||||
#define BREP_CDT_RESULT_INVALID_BREP -1
|
||||
#define BREP_CDT_RESULT_INVALID_TOLERANCE -2
|
||||
#define BREP_CDT_RESULT_INITIALIZATION_FAILED -3
|
||||
#define BREP_CDT_RESULT_FACE_FAILED -4
|
||||
#define BREP_CDT_RESULT_MESH_EXPORT_FAILED -5
|
||||
#define BREP_CDT_RESULT_NON_SOLID -6
|
||||
#define BREP_CDT_RESULT_INVALID_PSLG -7
|
||||
#define BREP_CDT_RESULT_DETRIA_FAILED -8
|
||||
#define BREP_CDT_RESULT_CERTIFICATION_FAILED -9
|
||||
#define BREP_CDT_RESULT_CHART_FAILED -10
|
||||
#define BREP_CDT_RESULT_REFINEMENT_LIMIT -11
|
||||
#define BREP_CDT_RESULT_GEOMETRIC_FAILED -12
|
||||
#define BREP_CDT_RESULT_REPAIRED 3
|
||||
#define BREP_CDT_RESULT_REPAIR_FAILED -13
|
||||
|
||||
#define BREP_CDT_STAGE_NONE 0
|
||||
#define BREP_CDT_STAGE_INPUT 1
|
||||
#define BREP_CDT_STAGE_TOPOLOGY 2
|
||||
#define BREP_CDT_STAGE_EDGE_INITIALIZATION 3
|
||||
#define BREP_CDT_STAGE_FACE_TRIANGULATION 4
|
||||
#define BREP_CDT_STAGE_MESH_ASSEMBLY 5
|
||||
#define BREP_CDT_STAGE_SOLID_VALIDATION 6
|
||||
#define BREP_CDT_STAGE_PSLG_VALIDATION 7
|
||||
#define BREP_CDT_STAGE_DETRIA 8
|
||||
#define BREP_CDT_STAGE_CHART_CONSTRUCTION 9
|
||||
#define BREP_CDT_STAGE_ADAPTIVE_REFINEMENT 10
|
||||
#define BREP_CDT_STAGE_GEOMETRIC_VALIDATION 11
|
||||
#define BREP_CDT_STAGE_MESH_REPAIR 12
|
||||
|
||||
/* A snapshot of the most recent tessellation attempt. message is always
|
||||
* NUL-terminated. face_index is -1 when no individual face is responsible. */
|
||||
struct brep_cdt_diagnostic {
|
||||
int result;
|
||||
int stage;
|
||||
int face_index;
|
||||
int completed_faces;
|
||||
int failed_faces;
|
||||
char message[256];
|
||||
};
|
||||
|
||||
/**
|
||||
* Explicit controls for the post-tessellation mesh-repair tier.
|
||||
*
|
||||
* Repair is never attempted by ON_Brep_CDT_Tessellate. Hole and component
|
||||
* operations must also be enabled and bounded explicitly in @p mesh. A zero
|
||||
* max_surface_deviation uses the tessellation's maximum chord tolerance. A
|
||||
* zero max_deviation_samples uses the library's bounded default. The area
|
||||
* change limit applies to the complete repaired mesh; zero disables that
|
||||
* additional aggregate limit. By default deviation samples must project
|
||||
* inside a trimmed source face. allow_untrimmed_surface_match permits an
|
||||
* explicitly reported fallback to the source face's underlying analytic
|
||||
* surface when defective trim topology prevents that classification. The
|
||||
* fast-face fallback supplies display-CDT triangles for failed rigorous faces
|
||||
* before mesh repair; its point, byte, and time limits bound that extra work.
|
||||
* use_full_fast_fallback instead supplies one coherent whole-B-Rep display
|
||||
* mesh. It is intended as a lower-fidelity alternative when mixing rigorous
|
||||
* and display face meshes leaves irreparable boundary topology.
|
||||
* use_full_fast_fallback_if_needed preserves the rigorous-first attempt and
|
||||
* retries with that whole display mesh only if the mixed mesh cannot be
|
||||
* certified. try_invalid_brep permits the repair entry point to make one
|
||||
* rigorous attempt after broad OpenNURBS validity failure, while retaining
|
||||
* the mesher's closed-manifold and paired-edge topology prerequisites.
|
||||
* If ordinary repair fails, that option also permits a bounded topology
|
||||
* healing attempt on an owned copy: unused edges, inconsistent orientations,
|
||||
* and supported planar trim loops may be corrected without changing source
|
||||
* surfaces or 3-D curves. Sampling, storage, and time are bounded by the fast
|
||||
* fallback limits. Ambiguous boundaries are left unresolved; any candidate
|
||||
* must still pass the requested mesh and fidelity checks. Provenance uses
|
||||
* the caller's original face and edge indices, including ignored wire edges.
|
||||
* A failed whole-display repair of a valid closed source may retry a small
|
||||
* open boundary residue with its validated pullback samples retained. The
|
||||
* same resource, solid, and fidelity requirements apply to that retry.
|
||||
* Edge-initialization failures caused by disagreeing paired p-curves may make
|
||||
* one repair-only retry when their shared midpoint stays within the same
|
||||
* maximum surface-deviation bound of both faces and the native edge curve.
|
||||
* With the automatic zero poisson_scale, use_poisson_reconstruction first
|
||||
* gives conservative mesh repair one chance to close the whole display mesh
|
||||
* without replacing its triangles. Only if that fails does Screened Poisson
|
||||
* replace the display mesh with an implicit-surface reconstruction. An
|
||||
* internal triangle cap bounds that opportunistic pass; use_full_fast_fallback
|
||||
* without Poisson remains the explicit route for larger local repairs. An
|
||||
* explicit nonzero scale requests Poisson directly. This is the most
|
||||
* approximate tier and is therefore separately opt-in. Its depth
|
||||
* is restricted to the bounded range accepted by this API. A zero
|
||||
* poisson_scale tries the upstream 1.1 domain scale followed by a 1.2 retry
|
||||
* if the first reconstructed mesh cannot be certified. If either scale
|
||||
* exceeds the reference-area bound or omits input coverage, the same bounded
|
||||
* scale pair is retried with one area-weighted sample budget. If the default
|
||||
* Neumann reconstruction remains open, the bounded scale and sampling
|
||||
* attempts are repeated with a Dirichlet boundary
|
||||
* and stronger non-exact screening. This closure-biased fallback is reported
|
||||
* explicitly and remains subject to every solid, area, deviation, and input
|
||||
* coverage gate. A value from 1.0 through 2.0 requests one fixed scale
|
||||
* without either automatic retry. Disconnected B-Rep face components are
|
||||
* reconstructed independently so a large component cannot erase a smaller
|
||||
* one; max_poisson_components bounds that work.
|
||||
* relaxed_fidelity_factor is a separately opt-in final acceptance tier for a
|
||||
* mesh which already satisfies every solid and Manifold requirement. A
|
||||
* value from 1 through 4 multiplies the strict surface-deviation and enabled
|
||||
* area-change limits without changing the generated mesh. Acceptance is
|
||||
* tagged explicitly in the report and provenance callback; zero disables it.
|
||||
*/
|
||||
/* These identifiers describe accepted paths; their values do not rank
|
||||
* geometric error. */
|
||||
/** No approximate geometry was needed. */
|
||||
#define BREP_CDT_REPAIR_APPROX_NONE 0
|
||||
/** A failed face used display triangulation with rigorous edge samples. */
|
||||
#define BREP_CDT_REPAIR_APPROX_CONSTRAINED_FACE 1
|
||||
/** Bounded local mesh repair added or reconstructed a tagged neighborhood. */
|
||||
#define BREP_CDT_REPAIR_APPROX_LOCAL_MESH 2
|
||||
/** The complete B-Rep used display triangulation. */
|
||||
#define BREP_CDT_REPAIR_APPROX_FULL_FAST 3
|
||||
/** The complete display mesh was replaced by an implicit reconstruction. */
|
||||
#define BREP_CDT_REPAIR_APPROX_POISSON 4
|
||||
/** A Manifold mesh used the explicitly relaxed final fidelity bound. */
|
||||
#define BREP_CDT_REPAIR_APPROX_RELAXED_FIDELITY 5
|
||||
/** Source topology was healed without replacing its support geometry. */
|
||||
#define BREP_CDT_REPAIR_APPROX_TOPOLOGY 6
|
||||
|
||||
/**
|
||||
* Report the B-Rep topology whose interpretation required approximation.
|
||||
* The callback is invoked only for an accepted repaired solid. face_indices
|
||||
* and edge_indices remain valid only for the duration of the call. Callers
|
||||
* producing a BoT may use these lists and approximation_tier to attach
|
||||
* provenance attributes to the converted object.
|
||||
*/
|
||||
typedef void (*brep_cdt_repair_provenance_t)(int approximation_tier,
|
||||
const int *face_indices, size_t face_count, const int *edge_indices,
|
||||
size_t edge_count, void *data);
|
||||
|
||||
struct brep_cdt_repair_settings {
|
||||
struct bg_trimesh_repair_settings mesh;
|
||||
fastf_t max_surface_deviation;
|
||||
size_t max_deviation_samples;
|
||||
fastf_t max_area_change_percent;
|
||||
int allow_untrimmed_surface_match;
|
||||
int use_fast_face_fallback;
|
||||
int use_full_fast_fallback;
|
||||
int use_poisson_reconstruction;
|
||||
int poisson_depth;
|
||||
size_t max_poisson_components;
|
||||
size_t max_fast_points;
|
||||
size_t max_fast_result_bytes;
|
||||
long max_fast_time_ms;
|
||||
fastf_t poisson_scale;
|
||||
int use_full_fast_fallback_if_needed;
|
||||
int try_invalid_brep;
|
||||
brep_cdt_repair_provenance_t provenance;
|
||||
void *provenance_data;
|
||||
fastf_t relaxed_fidelity_factor;
|
||||
/**
|
||||
* Optional second-pass ceiling when only open boundary edges remain.
|
||||
* max_hole_edges remains the normal limit; zero disables the retry.
|
||||
* Earlier rigorous-boundary reconstruction keeps its original budget.
|
||||
*/
|
||||
size_t max_adaptive_hole_edges;
|
||||
/**
|
||||
* Final-repair-only hole candidate ceiling for a bounded open-edge
|
||||
* residue. The independent aggregate area-change and surface-deviation
|
||||
* limits remain authoritative for accepting the resulting solid.
|
||||
*/
|
||||
fastf_t max_adaptive_hole_area_percent;
|
||||
/** Opt-in planar caps for simple open boundaries on a multi-face B-Rep.
|
||||
* Cap bounding boxes must be disjoint. Their aggregate conservative
|
||||
* area bound must not exceed this percentage of
|
||||
* the accepted mesh's remaining area. Zero disables capping. Capped
|
||||
* candidates require Manifold acceptance and retain the caller's mesh
|
||||
* intersection policy and fidelity limits. Requires try_invalid_brep. */
|
||||
fastf_t max_planar_cap_area_percent;
|
||||
};
|
||||
|
||||
#define BREP_CDT_REPAIR_SETTINGS_INIT {BG_TRIMESH_REPAIR_SETTINGS_INIT, 0.0, 4096, 1.0, 0, 1, 0, 0, 8, 64, 1048576, 134217728, 5000, 0.0, 0, 0, NULL, NULL, 0.0, 0, 0.0, 0.0}
|
||||
|
||||
/** Bounded topology interpretation, distinct from triangle-mesh repair.
|
||||
* Counts describe a candidate; applied is set only after mesh acceptance. */
|
||||
struct brep_cdt_healing_report {
|
||||
int attempted;
|
||||
int applied;
|
||||
int limited;
|
||||
int restored_outer_loops;
|
||||
int corrected_loop_roles;
|
||||
int reoriented_faces;
|
||||
int removed_unused_edges;
|
||||
/** Plane-distance bound for reconstructed outer loops and cap curves. */
|
||||
fastf_t max_edge_deviation;
|
||||
int capped_loops;
|
||||
fastf_t cap_area_bound;
|
||||
fastf_t cap_area_percent;
|
||||
};
|
||||
|
||||
#define BREP_CDT_HEALING_REPORT_INIT {0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0.0, 0.0}
|
||||
|
||||
/* Resource limits encountered by any nested repair attempt. */
|
||||
#define BREP_CDT_REPAIR_LIMIT_TIME 1u
|
||||
#define BREP_CDT_REPAIR_LIMIT_MEMORY 2u
|
||||
#define BREP_CDT_REPAIR_LIMIT_POINTS 4u
|
||||
/* Bounded refinement or topology healing exhausted its work budget. */
|
||||
#define BREP_CDT_REPAIR_LIMIT_REFINEMENT 8u
|
||||
|
||||
/** Provenance and quality measurements for a repair attempt. */
|
||||
struct brep_cdt_repair_report {
|
||||
struct bg_trimesh_repair_report mesh;
|
||||
struct brep_cdt_diagnostic source_diagnostic;
|
||||
int source_failed_faces;
|
||||
int changed_faces;
|
||||
size_t deviation_samples;
|
||||
size_t deviation_projection_failures;
|
||||
size_t untrimmed_surface_samples;
|
||||
size_t input_mesh_surface_samples;
|
||||
int fast_fallback_attempted_faces;
|
||||
int fast_fallback_used_faces;
|
||||
int fast_fallback_failed_faces;
|
||||
int fast_fallback_triangles;
|
||||
int full_fast_fallback_used;
|
||||
int poisson_reconstruction_attempted;
|
||||
int poisson_reconstruction_applied;
|
||||
int poisson_input_points;
|
||||
int poisson_components;
|
||||
int poisson_output_points;
|
||||
int poisson_output_faces;
|
||||
int poisson_attempts;
|
||||
int poisson_area_sampling_applied;
|
||||
int poisson_boundary_fallback_applied;
|
||||
fastf_t poisson_scale;
|
||||
fastf_t max_surface_deviation;
|
||||
fastf_t rms_surface_deviation;
|
||||
fastf_t allowed_surface_deviation;
|
||||
fastf_t reference_area;
|
||||
fastf_t reference_area_change_percent;
|
||||
fastf_t area_change_percent;
|
||||
size_t coverage_samples;
|
||||
size_t coverage_failures;
|
||||
fastf_t max_coverage_deviation;
|
||||
fastf_t rms_coverage_deviation;
|
||||
int relaxed_tessellation_attempted;
|
||||
int relaxed_tessellation_completed_faces;
|
||||
int bounded_edge_retry_attempted;
|
||||
int bounded_edge_retry_completed_faces;
|
||||
int bounded_edge_approximation_edges;
|
||||
int bounded_edge_approximation_faces;
|
||||
fastf_t max_bounded_edge_deviation;
|
||||
size_t fast_fallback_constrained_edges;
|
||||
size_t fast_fallback_constrained_samples;
|
||||
int added_patch_components;
|
||||
int largest_added_patch_faces;
|
||||
fastf_t largest_added_patch_area;
|
||||
int rigorous_first_attempted;
|
||||
int rigorous_first_result;
|
||||
int rigorous_first_fast_faces;
|
||||
size_t rigorous_first_constrained_edges;
|
||||
size_t rigorous_first_constrained_samples;
|
||||
fastf_t rigorous_first_reference_area;
|
||||
fastf_t rigorous_first_output_area;
|
||||
fastf_t rigorous_first_area_change_percent;
|
||||
int approximation_tier;
|
||||
int approximation_faces;
|
||||
int approximation_edges;
|
||||
/** Triangles retained byte-for-byte from the rigorous input mesh. */
|
||||
int retained_rigorous_triangles;
|
||||
/** Triangles replaced only inside accepted, tagged local neighborhoods. */
|
||||
int missing_rigorous_triangles;
|
||||
/** Certified triangles retained by a bounded hanging-edge subdivision. */
|
||||
int subdivided_rigorous_triangles;
|
||||
/** Accepted local neighborhoods replacing certified triangles. */
|
||||
int replaced_rigorous_components;
|
||||
int largest_replaced_rigorous_triangles;
|
||||
size_t largest_replaced_boundary_edges;
|
||||
fastf_t replaced_rigorous_area;
|
||||
fastf_t largest_replaced_rigorous_area;
|
||||
/** Failed adaptive faces retained as approximate repair input. */
|
||||
int best_effort_faces;
|
||||
int best_effort_triangles;
|
||||
int best_effort_folded_triangles;
|
||||
/** Chord samples projected to their source face within four tolerances. */
|
||||
size_t best_effort_reference_samples;
|
||||
/** Chord samples with no source projection inside that diagnostic bound. */
|
||||
size_t best_effort_reference_failures;
|
||||
fastf_t max_best_effort_surface_deviation;
|
||||
/** Sparse local periodic strips joining rigorous neighboring boundaries. */
|
||||
int boundary_strip_faces;
|
||||
int boundary_strip_triangles;
|
||||
size_t boundary_strip_constrained_edges;
|
||||
size_t boundary_strip_constrained_samples;
|
||||
/** Topological disks spanning complete authoritative shared boundaries. */
|
||||
int topological_disk_faces;
|
||||
int topological_disk_triangles;
|
||||
size_t topological_disk_constrained_edges;
|
||||
size_t topological_disk_constrained_samples;
|
||||
/** Final acceptance used the separately enabled relaxed fidelity tier. */
|
||||
int relaxed_fidelity_applied;
|
||||
fastf_t relaxed_fidelity_factor;
|
||||
fastf_t relaxed_surface_deviation_limit;
|
||||
fastf_t relaxed_area_change_percent_limit;
|
||||
int adaptive_hole_retry_attempted;
|
||||
size_t adaptive_hole_edges;
|
||||
int adaptive_hole_area_retry_attempted;
|
||||
fastf_t adaptive_hole_area_percent;
|
||||
struct brep_cdt_healing_report healing;
|
||||
/** A closed-source retry retained validated pullback boundary samples. */
|
||||
int pullback_retry_attempted;
|
||||
int pullback_retry_applied;
|
||||
/** Cumulative BREP_CDT_REPAIR_LIMIT_* flags, also retained on success. */
|
||||
unsigned int resource_limits;
|
||||
};
|
||||
|
||||
#define BREP_CDT_REPAIR_REPORT_INIT {BG_TRIMESH_REPAIR_REPORT_INIT, {BREP_CDT_RESULT_UNATTEMPTED, BREP_CDT_STAGE_NONE, -1, 0, 0, {0}}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0.0, 0.0, 0, 0, 0, 0.0, BREP_CDT_HEALING_REPORT_INIT, 0, 0, 0u}
|
||||
|
||||
/** Create and initialize a CDT state with default tolerances. bv
|
||||
* must be a pointer to an ON_Brep object. */
|
||||
extern BREP_EXPORT struct ON_Brep_CDT_State *
|
||||
ON_Brep_CDT_Create(void *bv, const char *objname);
|
||||
|
||||
/* Destroy a CDT state */
|
||||
/** Destroy a CDT state. */
|
||||
extern BREP_EXPORT void
|
||||
ON_Brep_CDT_Destroy(struct ON_Brep_CDT_State *s);
|
||||
|
||||
/** Return the diagnostic object name associated with a CDT state. */
|
||||
extern BREP_EXPORT const char *
|
||||
ON_Brep_CDT_ObjName(struct ON_Brep_CDT_State *s);
|
||||
|
||||
/* Set/get the CDT tolerances. */
|
||||
/** Set the CDT tolerances. */
|
||||
extern BREP_EXPORT void
|
||||
ON_Brep_CDT_Tol_Set(struct ON_Brep_CDT_State *s, const struct bg_tess_tol *t);
|
||||
extern BREP_EXPORT void
|
||||
ON_Brep_CDT_Tol_Get(struct bg_tess_tol *t, const struct ON_Brep_CDT_State *s);
|
||||
|
||||
/* Return the ON_Brep associated with state s. */
|
||||
/** Set a wall-clock limit for each rigorous face triangulation. A positive
|
||||
* value reports an adaptive-refinement failure when the limit is reached,
|
||||
* allowing callers to continue with an explicitly bounded repair tier.
|
||||
* Zero preserves the default unlimited behavior. */
|
||||
extern BREP_EXPORT void
|
||||
ON_Brep_CDT_Face_Time_Limit_Set(struct ON_Brep_CDT_State *s,
|
||||
long max_time_ms);
|
||||
|
||||
/** Return the ON_Brep associated with state s. */
|
||||
extern BREP_EXPORT void *
|
||||
ON_Brep_CDT_Brep(struct ON_Brep_CDT_State *s);
|
||||
|
||||
/* Given a state, produce a triangulation. Returns 0 if a solid, valid
|
||||
/** Given a state, produce a triangulation. Returns 0 if a solid, valid
|
||||
* triangulation was produced, 1 if a triangulation was produced but it
|
||||
* isn't solid, and -1 if no triangulation could be produced. If faces is
|
||||
* non-null, the triangulation will only attempt to triangulate the
|
||||
@@ -73,7 +396,31 @@ ON_Brep_CDT_Brep(struct ON_Brep_CDT_State *s);
|
||||
extern BREP_EXPORT int
|
||||
ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s, int face_cnt, int *faces);
|
||||
|
||||
/* Given a state, report the status of its triangulation. -3 indicates a
|
||||
/**
|
||||
* Attempt an explicitly bounded mesh repair after tessellation left a
|
||||
* partial or non-solid mesh. The original failed-face diagnostics remain
|
||||
* available as repair provenance. A repaired result is accepted only when
|
||||
* it is a closed, oriented manifold with valid vertex links, nondegenerate
|
||||
* triangles, bounded area change, and sampled deviation from the source
|
||||
* B-Rep within the requested tolerance. Nonadjacent triangle intersections
|
||||
* are rejected unless explicitly permitted by the mesh settings. A
|
||||
* certified triangle neighborhood may be reconstructed only when its area
|
||||
* and boundary stay within the configured hole limits; every affected B-Rep
|
||||
* face and edge is then reported as approximation provenance. A failed
|
||||
* single-loop face may also be spanned as a topological disk when every trim
|
||||
* has complete authoritative shared-edge samples. This preserves its exact
|
||||
* boundary while explicitly reporting the interior as a local approximation.
|
||||
*
|
||||
* Returns 1 if the state already contains a certified solid, 0 if repair
|
||||
* produced a certified approximation, and -1 if repair was not possible or
|
||||
* did not satisfy all postconditions.
|
||||
*/
|
||||
extern BREP_EXPORT int
|
||||
ON_Brep_CDT_Repair(struct ON_Brep_CDT_State *s,
|
||||
const struct brep_cdt_repair_settings *settings,
|
||||
struct brep_cdt_repair_report *report);
|
||||
|
||||
/** Given a state, report the status of its triangulation. -3 indicates a
|
||||
* failed attempt to tessellate, -2 indicates a non-solid tessellation is
|
||||
* present after an attempt to tessellate all faces, -1 is a state which
|
||||
* has had no tessellation attempt made, 0 indicates a solid, valid full
|
||||
@@ -82,7 +429,26 @@ ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s, int face_cnt, int *faces);
|
||||
extern BREP_EXPORT int
|
||||
ON_Brep_CDT_Status(struct ON_Brep_CDT_State *s);
|
||||
|
||||
/* Construct a vlist plot from the tessellation. Modes are:
|
||||
/** Retrieve structured information about the most recent tessellation attempt.
|
||||
* Returns 0 on success and -1 for invalid arguments. */
|
||||
extern BREP_EXPORT int
|
||||
ON_Brep_CDT_Diagnostic(struct brep_cdt_diagnostic *diagnostic,
|
||||
const struct ON_Brep_CDT_State *s);
|
||||
|
||||
/** Return the number of faces which failed in the most recent tessellation.
|
||||
* If faces is non-NULL, copy at most capacity stable face indices. */
|
||||
extern BREP_EXPORT int
|
||||
ON_Brep_CDT_Failed_Faces(int *faces, int capacity,
|
||||
const struct ON_Brep_CDT_State *s);
|
||||
|
||||
/** Copy the diagnostic recorded for a failed face in the most recent
|
||||
* tessellation. Returns zero on success and -1 if no failure was recorded
|
||||
* for face_index. */
|
||||
extern BREP_EXPORT int
|
||||
ON_Brep_CDT_Face_Diagnostic(struct brep_cdt_diagnostic *diagnostic,
|
||||
int face_index, const struct ON_Brep_CDT_State *s);
|
||||
|
||||
/** Construct a vlist plot from the tessellation. Modes are:
|
||||
*
|
||||
* 0 - shaded 3D triangles
|
||||
* 1 - 3D triangle wireframe
|
||||
@@ -98,7 +464,7 @@ ON_Brep_CDT_VList(
|
||||
int mode,
|
||||
struct ON_Brep_CDT_State *s);
|
||||
|
||||
/* Given two or more triangulation states, refine them to clear any face
|
||||
/** Given two or more triangulation states, refine them to clear any face
|
||||
* overlaps introduced by the triangulation. If any of the states are
|
||||
* un-tessellated, first perform the tessellation indicated by the state
|
||||
* settings and then proceed to resolve after all states have an initial
|
||||
@@ -119,7 +485,7 @@ extern BREP_EXPORT int
|
||||
ON_Brep_CDT_UnResolvable_Ovlps(std::vector<struct ON_Brep_CDT_State *> *ovlps, struct ON_Brep_CDT_State *s);
|
||||
#endif
|
||||
|
||||
/* Retrieve the face, vertex and normal information from a tessellation state
|
||||
/** Retrieve the face, vertex and normal information from a tessellation state
|
||||
* in the form of integer and fastf_t arrays. */
|
||||
/* TODO - need to allow optional specification of specific faces here -
|
||||
* have already hit one scenario where I want triangle information from
|
||||
@@ -148,17 +514,165 @@ brep_facecdt_plot(struct bu_vls *vls, const char *solid_name,
|
||||
extern BREP_EXPORT int
|
||||
brep_cdt_fast(int **faces, int *face_cnt, vect_t **pnt_norms, point_t **pnts, int *pntcnt,
|
||||
const ON_Brep *brep, int index, const struct bg_tess_tol *ttol, const struct bn_tol *tol);
|
||||
#endif
|
||||
|
||||
/* PImpl exposure of some mesh operations for use in tests - not to be considered public API */
|
||||
struct cdt_bmesh_impl;
|
||||
struct cdt_bmesh {
|
||||
struct cdt_bmesh_impl *i;
|
||||
/* Resource controls and diagnostics for display-quality tessellation. Zero
|
||||
* resource and tolerance values select library defaults; adaptive_quality is
|
||||
* a boolean switch. max_time_ms is checked
|
||||
* between faces; the per-face samplers also have fixed progress and recursion
|
||||
* guards to prevent non-terminating refinement. face_status, when non-NULL,
|
||||
* is called exactly once per requested face during serial result assembly.
|
||||
* face_output, when non-NULL, reports the contiguous output ranges assigned
|
||||
* to each completed face with drawable geometry. The optional trim-sample
|
||||
* callbacks replace sampling for trims where trim_sample_count returns at
|
||||
* least two points. Samples must be ordered in trim direction and supply
|
||||
* the trim parameter, face UV coordinate, and exact 3D boundary coordinate.
|
||||
* trim_sample_source may associate an opaque identity with each supplied
|
||||
* sample. point_source reports that identity for output points derived from
|
||||
* supplied samples, or NULL for generated points. Its point_index is in the
|
||||
* final, concatenated output point array. The identity is never dereferenced
|
||||
* by libbrep and need remain valid only until brep_cdt_fast_ex returns.
|
||||
* Callbacks may be invoked concurrently when max_workers exceeds one, except
|
||||
* face_status, face_diagnostic, and face_output, which run during serial
|
||||
* result assembly. */
|
||||
struct brep_cdt_fast_options {
|
||||
size_t max_workers;
|
||||
size_t max_result_bytes;
|
||||
size_t max_points;
|
||||
long max_time_ms;
|
||||
int allow_partial;
|
||||
void (*face_status)(int face_index, int status, void *data);
|
||||
void *face_status_data;
|
||||
void (*face_diagnostic)(int face_index, int result, int stage,
|
||||
const char *message, void *data);
|
||||
void *face_diagnostic_data;
|
||||
void (*face_output)(int face_index, size_t first_face,
|
||||
size_t face_count, size_t first_point, size_t point_count, void *data);
|
||||
void *face_output_data;
|
||||
size_t (*trim_sample_count)(int face_index, int trim_index, void *data);
|
||||
int (*trim_sample)(int face_index, int trim_index, size_t sample_index,
|
||||
fastf_t *trim_parameter, point2d_t uv, point_t point, void *data);
|
||||
void *trim_sample_data;
|
||||
const void *(*trim_sample_source)(int face_index, int trim_index,
|
||||
size_t sample_index, void *data);
|
||||
void (*point_source)(int face_index, size_t point_index,
|
||||
const void *source, void *data);
|
||||
void *point_source_data;
|
||||
/* Shared estimated transient-memory allowance for concurrent face jobs.
|
||||
* This is independent of max_result_bytes, which bounds retained output.
|
||||
* A zero value selects an availability-calibrated library default. */
|
||||
size_t max_working_bytes;
|
||||
/* Display triangle target. Adaptive whole-object budgeting may select a
|
||||
* lower target according to B-Rep topology. Authoritative trim
|
||||
* boundaries are retained and may make the final count exceed it. */
|
||||
size_t max_triangles;
|
||||
/* Generate a complete coarse mesh before refining toward the requested
|
||||
* tolerance. Intended for visual display; rigorous callers may disable
|
||||
* it to request the specified tolerance directly. */
|
||||
int adaptive_quality;
|
||||
/* Initial relative display tolerance and per-face unsigned area-change
|
||||
* convergence threshold. Zero values select library defaults. */
|
||||
double coarse_relative_tolerance;
|
||||
double area_change_tolerance;
|
||||
/* Retain validated pcurve-repair samples for cross-face mesh assembly.
|
||||
* Display callers normally simplify them within the repair tolerance. */
|
||||
int preserve_pullback_samples;
|
||||
};
|
||||
extern BREP_EXPORT int cdt_bmesh_create(struct cdt_bmesh **m);
|
||||
extern BREP_EXPORT void cdt_bmesh_destroy(struct cdt_bmesh *m);
|
||||
extern BREP_EXPORT int cdt_bmesh_deserialize(const char *fname, struct cdt_bmesh *m);
|
||||
extern BREP_EXPORT int cdt_bmesh_repair(struct cdt_bmesh *m);
|
||||
|
||||
#define BREP_CDT_FAST_FACE_COMPLETED 0
|
||||
#define BREP_CDT_FAST_FACE_FAILED 1
|
||||
#define BREP_CDT_FAST_FACE_SKIPPED_DEGENERATE 2
|
||||
#define BREP_CDT_FAST_FACE_NOT_PROCESSED 3
|
||||
#define BREP_CDT_FAST_FACE_APPROXIMATED 4
|
||||
#define BREP_CDT_FAST_FACE_SKIPPED_TOLERANCE 5
|
||||
|
||||
/* completed_faces includes faces with no drawable output. Exact zero-area
|
||||
* faces and faces which collapse at the requested display tolerance are
|
||||
* reported separately. */
|
||||
struct brep_cdt_fast_report {
|
||||
int requested_faces;
|
||||
int completed_faces;
|
||||
int failed_faces;
|
||||
size_t result_bytes;
|
||||
int hit_time_limit;
|
||||
int hit_memory_limit;
|
||||
int hit_point_limit;
|
||||
/* Completed faces proven to have exactly zero parametric area. */
|
||||
int skipped_degenerate_faces;
|
||||
/* Completed faces with no boundary resolvable at the requested
|
||||
* tessellation and model tolerances. */
|
||||
int skipped_tolerance_faces;
|
||||
/* Peak sum of conservative face-work reservations. */
|
||||
size_t peak_working_bytes;
|
||||
/* Adaptive display-quality provenance. */
|
||||
size_t triangle_budget;
|
||||
int approximated_faces;
|
||||
int area_converged_faces;
|
||||
int triangle_budget_limited_faces;
|
||||
int refinement_passes;
|
||||
int refinement_time_limited;
|
||||
/* Faces whose realized triangles do not cover the sampled B-Rep edge
|
||||
* envelope within the requested display tolerance. */
|
||||
int boundary_envelope_incomplete_faces;
|
||||
};
|
||||
|
||||
#define BREP_CDT_FAST_OK 0
|
||||
#define BREP_CDT_FAST_PARTIAL 1
|
||||
#define BREP_CDT_FAST_ERROR -1
|
||||
#define BREP_CDT_FAST_LIMIT -2
|
||||
|
||||
/**
|
||||
* Initialize display-quality CDT options with library defaults.
|
||||
*
|
||||
* A NULL options pointer is ignored. Callers may override any nonzero
|
||||
* resource limit or other option after initialization.
|
||||
*
|
||||
* @param options options structure to initialize
|
||||
*/
|
||||
extern BREP_EXPORT void
|
||||
brep_cdt_fast_options_default(struct brep_cdt_fast_options *options);
|
||||
|
||||
/**
|
||||
* Tessellate one B-Rep face, or all faces when @p index is -1, using the
|
||||
* bounded display-quality CDT path.
|
||||
*
|
||||
* Output arrays are allocated with the BRL-CAD allocator and are owned by
|
||||
* the caller. They are initialized to NULL/zero before any validation or
|
||||
* processing. The optional report receives resource and per-face status
|
||||
* information. A partial result is returned only when the options permit
|
||||
* partial output. An all-faces request for a B-Rep with no faces succeeds
|
||||
* with empty output; selecting an individual nonexistent face is an error.
|
||||
*
|
||||
* Failed trim boundaries may be retried after bounded topology healing on
|
||||
* an owned copy. The source is unchanged and no holes are capped. This
|
||||
* recovery is disabled when authoritative trim samples, source callbacks,
|
||||
* or preserve_pullback_samples require the original topology identities.
|
||||
*
|
||||
* @param faces receives triangle vertex indices
|
||||
* @param face_cnt receives the number of triangles
|
||||
* @param pnt_norms receives three face-oriented corner normals per triangle,
|
||||
* in the same order as faces; smooth normals opposing a realized
|
||||
* nondegenerate facet are replaced by its geometric normal
|
||||
* @param pnts receives output vertices
|
||||
* @param pntcnt receives the number of output vertices
|
||||
* @param brep source B-Rep
|
||||
* @param index face index, or -1 for all faces
|
||||
* @param ttol tessellation tolerance
|
||||
* @param tol geometric tolerance
|
||||
* @param options optional resource and callback settings; NULL selects
|
||||
* defaults
|
||||
* @param report optional diagnostic report
|
||||
* @return BREP_CDT_FAST_OK for complete output, BREP_CDT_FAST_PARTIAL for
|
||||
* permitted partial output, BREP_CDT_FAST_LIMIT for a bounded
|
||||
* resource stop, or BREP_CDT_FAST_ERROR for invalid input or other
|
||||
* failure
|
||||
*/
|
||||
extern BREP_EXPORT int
|
||||
brep_cdt_fast_ex(int **faces, int *face_cnt, vect_t **pnt_norms,
|
||||
point_t **pnts, int *pntcnt, const ON_Brep *brep, int index,
|
||||
const struct bg_tess_tol *ttol, const struct bn_tol *tol,
|
||||
const struct brep_cdt_fast_options *options,
|
||||
struct brep_cdt_fast_report *report);
|
||||
#endif
|
||||
|
||||
__END_DECLS
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#ifdef __cplusplus
|
||||
// @cond SKIP_C++_INCLUDE
|
||||
extern "C++" {
|
||||
# include <cstddef>
|
||||
# include <cstdint>
|
||||
# include <map>
|
||||
}
|
||||
// @endcond
|
||||
@@ -56,7 +58,24 @@ namespace brlcad {
|
||||
*/
|
||||
class BREP_EXPORT CurveTree : public PooledObject<CurveTree> {
|
||||
public:
|
||||
explicit CurveTree(const ON_BrepFace *face);
|
||||
/**
|
||||
* Build a curve tree with an optional transient-memory limit.
|
||||
* A zero byte limit selects the library default.
|
||||
*/
|
||||
explicit CurveTree(const ON_BrepFace *face,
|
||||
std::size_t max_bytes = 0);
|
||||
/**
|
||||
* Build a curve tree with a minimum feature-size sampling limit.
|
||||
* A zero feature size selects the library default.
|
||||
*/
|
||||
CurveTree(const ON_BrepFace *face, std::size_t max_bytes,
|
||||
double min_feature_size);
|
||||
/**
|
||||
* Build a curve tree with feature-size and deadline limits.
|
||||
* The deadline is an absolute timestamp from bu_gettime(); zero disables it.
|
||||
*/
|
||||
CurveTree(const ON_BrepFace *face, std::size_t max_bytes,
|
||||
double min_feature_size, int64_t deadline);
|
||||
~CurveTree();
|
||||
|
||||
CurveTree(Deserializer &deserializer, const ON_BrepFace &face);
|
||||
@@ -65,6 +84,14 @@ namespace brlcad {
|
||||
std::list<const BRNode *> serialize_get_leaves(const std::size_t *keys, std::size_t num_keys) const;
|
||||
void serialize_cleanup() const;
|
||||
|
||||
/**
|
||||
* Report whether construction stopped at the node/memory limit.
|
||||
* A limited tree must not be used for geometric queries.
|
||||
*/
|
||||
bool limit_reached() const { return m_limit_reached; }
|
||||
/** Report whether construction stopped at its time deadline. */
|
||||
bool time_limit_reached() const { return m_time_limit_reached; }
|
||||
|
||||
/**
|
||||
* Return just the leaves of the surface tree
|
||||
*/
|
||||
@@ -95,12 +122,19 @@ namespace brlcad {
|
||||
|
||||
bool getHVTangents(const ON_Curve *curve, const ON_Interval &t, std::list<fastf_t> &list) const;
|
||||
bool isLinear(const ON_Curve *curve, double min, double max) const;
|
||||
bool deadlineExpired() const;
|
||||
BRNode *subdivideCurve(const ON_Curve *curve, int trim_index, int adj_face_index, double min, double max, bool innerTrim, int depth) const;
|
||||
BRNode *curveBBox(const ON_Curve *curve, int trim_index, int adj_face_index, const ON_Interval &t, bool isLeaf, bool innerTrim, const ON_BoundingBox &bb) const;
|
||||
static ON_BoundingBox initialLoopBBox(const ON_BrepFace &face);
|
||||
|
||||
const ON_BrepFace * const m_face;
|
||||
BRNode *m_root;
|
||||
const std::size_t m_max_nodes;
|
||||
const double m_min_feature_size;
|
||||
const int64_t m_deadline;
|
||||
mutable std::size_t m_node_count;
|
||||
mutable bool m_limit_reached;
|
||||
mutable bool m_time_limit_reached;
|
||||
|
||||
|
||||
struct Stl : public PooledObject<Stl> {
|
||||
|
||||
+23
-11
@@ -199,19 +199,31 @@ typedef struct _on_brep_placeholder {
|
||||
extern "C++" {
|
||||
struct BrepTrimPoint
|
||||
{
|
||||
int edge_ind;
|
||||
double e; /* corresponding edge curve parameter (ON_UNSET_VALUE if using trim not edge) */
|
||||
ON_3dPoint *p3d; /* 3d edge/trim point depending on whether we're using the 3d edge to generate points or the trims */
|
||||
ON_3dPoint *n3d; /* normal on edge, average of the normals from the two surfaces at this point, or of all surface points associated with a vertex if this is a vertex point. */
|
||||
ON_3dVector tangent; /* Tangent from the curve, or from the surfaces if the curve wasn't usable at this point. */
|
||||
int edge_ind = -1;
|
||||
double e = ON_UNSET_VALUE; /* corresponding edge curve parameter (ON_UNSET_VALUE if using trim not edge) */
|
||||
ON_3dPoint *p3d = NULL; /* 3d edge/trim point depending on whether we're using the 3d edge to generate points or the trims */
|
||||
ON_3dPoint *n3d = NULL; /* normal on edge, average of the normals from the two surfaces at this point, or of all surface points associated with a vertex if this is a vertex point. */
|
||||
ON_3dVector tangent = ON_3dVector::UnsetVector; /* Tangent from the curve, or from the surfaces if the curve wasn't usable at this point. */
|
||||
|
||||
int trim_ind;
|
||||
double t; /* corresponding trim curve parameter (ON_UNSET_VALUE if unknown or not pulled back) */
|
||||
ON_2dPoint p2d; /* 2d surface parameter space point */
|
||||
ON_3dVector normal; /* normal as calculated by this trim */
|
||||
int trim_ind = -1;
|
||||
double t = ON_UNSET_VALUE; /* corresponding trim curve parameter (ON_UNSET_VALUE if unknown or not pulled back) */
|
||||
ON_2dPoint p2d = ON_2dPoint::UnsetPoint; /* 2d surface parameter space point */
|
||||
ON_3dVector normal = ON_3dVector::UnsetVector; /* normal as calculated by this trim */
|
||||
|
||||
BrepTrimPoint *other_face_trim_pnt;
|
||||
int from_singular;
|
||||
/**
|
||||
* Optional opaque caller identity retained when this point is copied.
|
||||
*
|
||||
* The pointer is non-owning: libbrep compares or reports its address but
|
||||
* never dereferences it. The caller must keep the pointed-to identity at
|
||||
* a stable address until the operation retaining the point has returned.
|
||||
* This field is part of the public C++ structure layout; adding or removing
|
||||
* fields in BrepTrimPoint changes its ABI and aggregate-initialization
|
||||
* compatibility.
|
||||
*/
|
||||
const void *source_id = NULL;
|
||||
|
||||
BrepTrimPoint *other_face_trim_pnt = NULL;
|
||||
int from_singular = -1;
|
||||
};}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C++" {
|
||||
/* @cond */
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
/* @endcond */
|
||||
@@ -57,7 +59,26 @@ namespace brlcad {
|
||||
*/
|
||||
class BREP_EXPORT SurfaceTree {
|
||||
public:
|
||||
explicit SurfaceTree(const ON_BrepFace *face, bool removeTrimmed = true, int depthLimit = BREP_MAX_FT_DEPTH, double within_distance_tol = BREP_EDGE_MISS_TOLERANCE);
|
||||
/**
|
||||
* Build a surface tree with optional curve-memory and depth limits.
|
||||
* A zero curve-memory limit selects the library default.
|
||||
*/
|
||||
explicit SurfaceTree(const ON_BrepFace *face, bool removeTrimmed = true, int depthLimit = BREP_MAX_FT_DEPTH, double within_distance_tol = BREP_EDGE_MISS_TOLERANCE, std::size_t max_curve_bytes = 0);
|
||||
/**
|
||||
* Build a surface tree with a minimum curve feature-size limit.
|
||||
* A zero feature size selects the library default.
|
||||
*/
|
||||
SurfaceTree(const ON_BrepFace *face, bool removeTrimmed,
|
||||
int depthLimit, double within_distance_tol,
|
||||
std::size_t max_curve_bytes, double min_curve_feature_size);
|
||||
/**
|
||||
* Build a surface tree with feature-size and curve deadline limits.
|
||||
* The deadline is an absolute timestamp from bu_gettime(); zero disables it.
|
||||
*/
|
||||
SurfaceTree(const ON_BrepFace *face, bool removeTrimmed,
|
||||
int depthLimit, double within_distance_tol,
|
||||
std::size_t max_curve_bytes, double min_curve_feature_size,
|
||||
int64_t curve_deadline);
|
||||
~SurfaceTree();
|
||||
|
||||
/* Report whether the tree is in a usable state */
|
||||
@@ -67,6 +88,15 @@ namespace brlcad {
|
||||
return true;
|
||||
};
|
||||
|
||||
/** Report whether curve-tree construction hit its resource limit. */
|
||||
bool CurveTreeLimitReached() const {
|
||||
return m_ctree && m_ctree->limit_reached();
|
||||
};
|
||||
/** Report whether curve-tree construction hit its time deadline. */
|
||||
bool CurveTreeTimeLimitReached() const {
|
||||
return m_ctree && m_ctree->time_limit_reached();
|
||||
};
|
||||
|
||||
BBNode *getRootNode() const;
|
||||
|
||||
/**
|
||||
|
||||
+16
-9
@@ -205,23 +205,30 @@ BU_EXPORT extern int bu_sscanf(const char *src, const char *fmt, ...) _BU_ATTR_S
|
||||
/** Routines for scanning certain kinds of data. */
|
||||
|
||||
/**
|
||||
* Scans a sequence of fastf_t numbers from a string or stdin
|
||||
* Scans a sequence of fastf_t numbers from a string or stdin.
|
||||
*
|
||||
* Scanning fastf_t numbers with bu_sscanf() is difficult, because
|
||||
* doing so requires scanning to some intermediate type like double
|
||||
* and then assigning to the fastf_t variable to convert the value to
|
||||
* whatever type fastf_t really is. This function makes it possible
|
||||
* to scan a series of fastf_t numbers separated by some character(s)
|
||||
* easily, by doing the required conversion internally to the
|
||||
* functions. As series of delimiter characters will be skipped,
|
||||
* empty scan fields are not supported (e.g., "0.0,,0.0,1.0" will scan
|
||||
* as 3 fields, not 4 with the 2nd skipped).
|
||||
* to scan a series of fastf_t numbers easily, by doing the required
|
||||
* conversion internally. Delimiters form a non-empty character set:
|
||||
* one or more delimiter characters must separate values, and repeated
|
||||
* delimiters are skipped. Empty scan fields are therefore not
|
||||
* supported (e.g., "0.0,,0.0,1.0" scans as 3 fields, not 4 with the
|
||||
* second skipped). A trailing delimiter is not consumed after the
|
||||
* final requested value.
|
||||
*
|
||||
* @param[out] c Returns number of characters scanned by the function
|
||||
* @param[out] c Optional destination for the number of input characters
|
||||
* consumed. It is set to zero before scanning and remains zero for
|
||||
* invalid arguments or when no value is scanned.
|
||||
* @param[in] src A source string to scan from, or NULL to read from stdin
|
||||
* @param[in] delim Any delimiter character(s) to skip between scan values
|
||||
* @param[in] delim Non-empty set of delimiter characters to skip between
|
||||
* scan values
|
||||
* @param[in] n Number of fastf_t values to scan from the src input string
|
||||
* @param[out] ... Pointers to fastf_t for storing scanned values (optional)
|
||||
* @param[out] ... Exactly n fastf_t pointers for storing scanned values;
|
||||
* individual pointers may be NULL
|
||||
* @return Number of values scanned
|
||||
*
|
||||
*/
|
||||
BU_EXPORT extern int bu_scan_fastf_t(int *c, const char *src, const char *delim, size_t n, ...);
|
||||
|
||||
+7
-7
@@ -146,10 +146,10 @@ BU_EXPORT extern size_t bu_malloc_len_roundup(size_t nbytes);
|
||||
* really fast heap-based memory allocation intended for "small"
|
||||
* allocation sizes (e.g., single structs).
|
||||
*
|
||||
* the implementation allocates chunks of memory ('pages') in order to
|
||||
* substantially reduce calls to system malloc. it has a nice
|
||||
* property of having O(1) constant time complexity and profiles
|
||||
* significantly faster than system malloc().
|
||||
* Allocations may be obtained and released by different threads. Small
|
||||
* released allocations may be retained in bounded thread-affine caches.
|
||||
* The requested size must be nonzero. Returned storage is suitably aligned,
|
||||
* but its contents are unspecified.
|
||||
*
|
||||
* release memory with bu_heap_put() only.
|
||||
*/
|
||||
@@ -159,9 +159,9 @@ BU_EXPORT extern void *bu_heap_get(size_t sz);
|
||||
* counterpart to bu_heap_get() for releasing fast heap-based memory
|
||||
* allocations.
|
||||
*
|
||||
* the implementation may do nothing, relying on free-on-exit, or may
|
||||
* mark deallocations for reuse. pass a NULL pointer and zero size to
|
||||
* force compaction of any unused memory.
|
||||
* The allocation size must match the size passed to bu_heap_get(). A NULL
|
||||
* pointer is ignored; passing NULL with a zero size requests compaction of
|
||||
* any unused implementation storage.
|
||||
*/
|
||||
BU_EXPORT extern void bu_heap_put(void *ptr, size_t sz);
|
||||
|
||||
|
||||
+14
-1
@@ -103,10 +103,23 @@ BU_EXPORT extern void bu_nice_set(int newnice);
|
||||
|
||||
/**
|
||||
* Return the maximum number of physical CPUs that are considered to
|
||||
* be available to this process now.
|
||||
* be available to this process now, subject to any process-local limit set by
|
||||
* bu_avail_cpus_set().
|
||||
*/
|
||||
BU_EXPORT extern size_t bu_avail_cpus(void);
|
||||
|
||||
/**
|
||||
* Limit the CPU count reported by bu_avail_cpus() and honored by subsequent
|
||||
* bu_parallel() calls in this process. A value of zero removes the limit.
|
||||
* Call this before starting worker threads; changing the limit does not resize
|
||||
* work that is already running.
|
||||
*
|
||||
* This is useful when several independent processes share one machine and
|
||||
* each process contains code that selects its own parallelism with
|
||||
* bu_avail_cpus().
|
||||
*/
|
||||
BU_EXPORT extern void bu_avail_cpus_set(size_t ncpu);
|
||||
|
||||
|
||||
/**
|
||||
* Create parallel threads of execution.
|
||||
|
||||
+2
-2
@@ -84,7 +84,7 @@ __BEGIN_DECLS
|
||||
#define RT_DEBUG_HF 0x04000000 /**< @brief 27 Height Field solids */
|
||||
|
||||
#define RT_DEBUG_MESHING 0x08000000 /**< @brief 28 Print meshing/triangulation details */
|
||||
#define RT_DEBUG_UNUSED_3 0x10000000 /**< @brief 29 Unassigned */
|
||||
#define RT_DEBUG_RTCMP 0x10000000 /**< @brief 29 Set up rtcmp capture; recording uses a runtime toggle. If LIBRT_RTCMP_PRIMITIVES=1 includes primitive segments. */
|
||||
#define RT_DEBUG_UNUSED_4 0x20000000 /**< @brief 30 Unassigned */
|
||||
|
||||
/* Options which will cause the library to write binary debugging output */
|
||||
@@ -96,7 +96,7 @@ __BEGIN_DECLS
|
||||
"\040PL_BOX" \
|
||||
"\037PL_SOLIDS" \
|
||||
"\036UNUSED_4" \
|
||||
"\035UNUSED_3" \
|
||||
"\035RTCMP" \
|
||||
"\034UNUSED_2" \
|
||||
"\033HF" \
|
||||
"\032EBM" \
|
||||
|
||||
+23
-6
@@ -465,6 +465,17 @@ rt_get_solid_keypoint(struct rt_edit *s, point_t *pt, const char **strp, fastf_t
|
||||
RT_EXPORT extern void
|
||||
rt_edit_set_edflag(struct rt_edit *s, int edflag);
|
||||
|
||||
/**
|
||||
* Set a parameter-edit translation target expressed in model base units.
|
||||
*
|
||||
* rt_edit's parameter-edit handlers consume e_para in database local units,
|
||||
* while keypoints and edit axes are maintained in model base units. This
|
||||
* helper performs the boundary conversion for callers that have computed a
|
||||
* target from a keypoint, axis, or view transform.
|
||||
*/
|
||||
RT_EXPORT extern void
|
||||
rt_edit_set_translation_target(struct rt_edit *s, const point_t target);
|
||||
|
||||
RT_EXPORT extern int
|
||||
rt_edit_knob_cmd_process(
|
||||
struct rt_edit *s,
|
||||
@@ -499,8 +510,10 @@ rt_knob_edit_sca(
|
||||
int matrix_edit);
|
||||
|
||||
/* Equivalent to sedit - run editing logic after input data is set in
|
||||
* rt_edit container */
|
||||
RT_EXPORT extern void
|
||||
* rt_edit container. Transient input flags are consumed whether the edit
|
||||
* succeeds or fails; parameter values remain available to the caller.
|
||||
* Returns BRLCAD_ERROR if the primitive edit fails. */
|
||||
RT_EXPORT extern int
|
||||
rt_edit_process(struct rt_edit *s);
|
||||
|
||||
/**
|
||||
@@ -521,7 +534,7 @@ rt_edit_snap_point(point2d_t pt, const struct rt_edit *s);
|
||||
* restored later with rt_edit_revert().
|
||||
*
|
||||
* The snapshot is stored inside the rt_edit struct. Calling this
|
||||
* function again overwrites any previous snapshot (single-level undo).
|
||||
* function again replaces the previous snapshot only on success.
|
||||
*
|
||||
* @return BRLCAD_OK on success, BRLCAD_ERROR if the export failed.
|
||||
*/
|
||||
@@ -532,8 +545,8 @@ rt_edit_checkpoint(struct rt_edit *s);
|
||||
* Restore primitive parameters from the snapshot saved by
|
||||
* rt_edit_checkpoint().
|
||||
*
|
||||
* If no snapshot has been saved (or the last snapshot was already
|
||||
* consumed) this function logs a message and returns BRLCAD_ERROR.
|
||||
* The snapshot remains available for repeated restores. If none has
|
||||
* been saved, this function logs a message and returns BRLCAD_ERROR.
|
||||
*
|
||||
* @return BRLCAD_OK on success, BRLCAD_ERROR otherwise.
|
||||
*/
|
||||
@@ -697,7 +710,11 @@ enum rt_constraint_edit_op_kind {
|
||||
RT_CONSTRAINT_EDIT_OP_SET_BEND,
|
||||
RT_CONSTRAINT_EDIT_OP_SCALE_OD,
|
||||
RT_CONSTRAINT_EDIT_OP_SCALE_ID,
|
||||
RT_CONSTRAINT_EDIT_OP_SCALE_BEND
|
||||
RT_CONSTRAINT_EDIT_OP_SCALE_BEND,
|
||||
/* Set every point when an all-zero dimension has no scale reference. */
|
||||
RT_CONSTRAINT_EDIT_OP_SET_ALL_OD,
|
||||
RT_CONSTRAINT_EDIT_OP_SET_ALL_ID,
|
||||
RT_CONSTRAINT_EDIT_OP_SET_ALL_BEND
|
||||
};
|
||||
|
||||
struct rt_constraint_edit_param_ref {
|
||||
|
||||
@@ -55,14 +55,11 @@ struct clt_tri_specific {
|
||||
};
|
||||
#endif
|
||||
|
||||
#define RT_BOT_SURFNO_INTERIOR (-1)
|
||||
|
||||
/* Shared between bot and ars at the moment */
|
||||
struct bot_specific {
|
||||
unsigned char bot_mode;
|
||||
unsigned char bot_orientation;
|
||||
unsigned char bot_flags;
|
||||
unsigned char bot_parity_shot;
|
||||
size_t bot_ntri;
|
||||
fastf_t *bot_thickness;
|
||||
struct bu_bitv *bot_facemode;
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#ifndef RT_PRIMITIVES_BREP_H
|
||||
#define RT_PRIMITIVES_BREP_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "vmath.h"
|
||||
#include "bu/list.h"
|
||||
@@ -39,6 +41,61 @@ RT_EXPORT extern int rt_brep_plot(struct bu_list *vhead,
|
||||
const struct bg_tess_tol *ttol,
|
||||
const struct bn_tol *tol,
|
||||
const struct bview *info);
|
||||
|
||||
/** Options for bounded B-Rep wireframe and surface-cue drawing. */
|
||||
struct rt_brep_draw_options {
|
||||
size_t max_workers;
|
||||
/** Retained output memory; zero selects the library default. */
|
||||
size_t max_result_bytes;
|
||||
/** Temporary edge and curve-tree working memory; zero selects default. */
|
||||
size_t max_working_bytes;
|
||||
size_t max_points;
|
||||
long max_time_ms;
|
||||
int include_surface_cues;
|
||||
void (*item_status)(int item_type, int item_index, int status,
|
||||
void *data);
|
||||
void *item_status_data;
|
||||
};
|
||||
|
||||
#define RT_BREP_DRAW_EDGE 0
|
||||
#define RT_BREP_DRAW_SURFACE_CUE 1
|
||||
#define RT_BREP_DRAW_ITEM_COMPLETED 0
|
||||
#define RT_BREP_DRAW_ITEM_FAILED 1
|
||||
#define RT_BREP_DRAW_ITEM_NOT_PROCESSED 2
|
||||
#define RT_BREP_DRAW_ITEM_APPROXIMATED 3
|
||||
|
||||
struct rt_brep_draw_report {
|
||||
int requested_edges;
|
||||
int completed_edges;
|
||||
int failed_edges;
|
||||
int requested_surface_cues;
|
||||
int completed_surface_cues;
|
||||
/** Cues drawn from the bounded untrimmed surface envelope. */
|
||||
int approximated_surface_cues;
|
||||
int memory_approximated_surface_cues;
|
||||
int time_approximated_surface_cues;
|
||||
size_t output_points;
|
||||
size_t result_bytes;
|
||||
int hit_time_limit;
|
||||
int hit_memory_limit;
|
||||
int hit_point_limit;
|
||||
};
|
||||
|
||||
#define RT_BREP_DRAW_OK 0
|
||||
#define RT_BREP_DRAW_PARTIAL 1
|
||||
#define RT_BREP_DRAW_ERROR -1
|
||||
#define RT_BREP_DRAW_LIMIT -2
|
||||
|
||||
/** Initialize drawing options with safe bounded defaults. */
|
||||
RT_EXPORT extern void
|
||||
rt_brep_draw_options_default(struct rt_brep_draw_options *options);
|
||||
|
||||
/** Draw bounded B-Rep wireframe edges and optional surface cues. */
|
||||
RT_EXPORT extern int
|
||||
rt_brep_plot_ex(struct bu_list *vhead, struct rt_db_internal *ip,
|
||||
const struct bg_tess_tol *ttol, const struct bn_tol *tol,
|
||||
const struct bview *info, const struct rt_brep_draw_options *options,
|
||||
struct rt_brep_draw_report *report);
|
||||
RT_EXPORT extern int rt_brep_plot_poly(struct bu_list *vhead,
|
||||
const struct directory *dp,
|
||||
struct rt_db_internal *ip,
|
||||
|
||||
@@ -41,6 +41,7 @@ struct rt_nmg_edit {
|
||||
struct loopuse *lu_copy; /* copy of loop to be extruded */
|
||||
plane_t lu_pl; /* plane equation for loop to be extruded */
|
||||
struct shell *es_s; /* Shell where extrusion is to end up */
|
||||
int es_s_from_extrusion; /* Replace this edit's prior preview */
|
||||
point_t lu_keypoint; /* keypoint of lu_copy for extrusion */
|
||||
struct vertex *es_v; /* Currently selected NMG vertex (ECMD_NMG_VPICK) */
|
||||
struct faceuse *es_fu; /* Currently selected NMG faceuse (ECMD_NMG_FPICK) */
|
||||
|
||||
@@ -55,6 +55,18 @@
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
#define RT_RTCMP_CAPTURE_RUNTIME_TOGGLE 1
|
||||
|
||||
/** Set RT_DEBUG_RTCMP recording: nonzero enables, zero disables.
|
||||
* Enabling requires the debug flag and a usable output writer.
|
||||
* Return 0 on success and -1 on failure. Disabling succeeds even
|
||||
* before setup.
|
||||
*/
|
||||
RT_EXPORT extern int rt_rtcmp_capture_set_enabled(int enabled);
|
||||
|
||||
/** Wait until pending RT_DEBUG_RTCMP records have reached the output file. */
|
||||
RT_EXPORT extern int rt_rtcmp_capture_flush(void);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* Shoot a ray
|
||||
|
||||
@@ -2200,7 +2200,11 @@ macro(find_package_opencv)
|
||||
set(OpenCV_DIR_TMP "${OpenCV_DIR}")
|
||||
set(OpenCV_DIR "${CMAKE_BINARY_DIR}/${LIB_DIR}/cmake/opencv4")
|
||||
set(OpenCV_ROOT ${CMAKE_BINARY_DIR})
|
||||
find_package(OpenCV COMPONENTS core features2d imgproc highgui)
|
||||
find_package(OpenCV CONFIG COMPONENTS core imgproc imgcodecs highgui)
|
||||
if(OpenCV_FOUND AND OpenCV_VERSION_MAJOR GREATER_EQUAL 5)
|
||||
# OpenCV 5 moved boundingRect from imgproc to geometry.
|
||||
find_package(OpenCV CONFIG COMPONENTS geometry)
|
||||
endif()
|
||||
unset(OpenCV_ROOT)
|
||||
|
||||
# If no bundled copy, see what the system has
|
||||
|
||||
@@ -140,7 +140,13 @@ add_subdirectory(ttcp)
|
||||
|
||||
if(SH_EXEC)
|
||||
brlcad_add_test(NAME regress-usage COMMAND ${SH_EXEC} "${CMAKE_SOURCE_DIR}/regress/usage.sh" ${CMAKE_SOURCE_DIR})
|
||||
brlcad_regression_test(regress-usage "rt" STAND_ALONE TEST_DEFINED)
|
||||
set(regress_usage_timeout 300)
|
||||
if(WIN32)
|
||||
# Starting and inspecting the full executable set is substantially slower on Windows.
|
||||
set(regress_usage_timeout 900)
|
||||
endif()
|
||||
brlcad_regression_test(regress-usage "rt" STAND_ALONE TEST_DEFINED TIMEOUT ${regress_usage_timeout})
|
||||
unset(regress_usage_timeout)
|
||||
|
||||
# TODO - test for Python before adding this one
|
||||
brlcad_add_test(NAME regress-flawfinder COMMAND ${SH_EXEC} "${CMAKE_SOURCE_DIR}/regress/flawfinder.sh" ${CMAKE_SOURCE_DIR})
|
||||
|
||||
@@ -117,6 +117,20 @@ if(TARGET libged)
|
||||
|
||||
endif(TARGET libged)
|
||||
|
||||
if(TARGET librt AND FUZZER_SUPPORTED)
|
||||
set(RT_EDIT_FUZZ_DIR "${CMAKE_SOURCE_DIR}/src/librt/tests/edit")
|
||||
brlcad_addexec(
|
||||
fuzz_rt_edit_test
|
||||
"${RT_EDIT_FUZZ_DIR}/fuzz.cpp;${RT_EDIT_FUZZ_DIR}/ars.cpp;${RT_EDIT_FUZZ_DIR}/bot.cpp"
|
||||
"libbu;librt;libnmg;libwdb;libbrep"
|
||||
TEST
|
||||
)
|
||||
set_target_properties(fuzz_rt_edit_test PROPERTIES UNITY_BUILD OFF)
|
||||
target_compile_options(fuzz_rt_edit_test PRIVATE ${FUZZER_EXE_COMPILE_OPTIONS})
|
||||
target_link_options(fuzz_rt_edit_test PRIVATE ${FUZZER_LINK_OPTIONS})
|
||||
BRLCAD_ADD_TEST(NAME fuzz_rt_edit_smoke COMMAND fuzz_rt_edit_test -runs=1 -max_total_time=5)
|
||||
endif()
|
||||
|
||||
cmakefiles(
|
||||
CMakeLists.txt
|
||||
fuzz_ged.cpp
|
||||
|
||||
@@ -37,6 +37,14 @@ if(TARGET libged)
|
||||
endif(TARGET regress_lint_raytrace)
|
||||
brlcad_regression_test(regress-ged_lint_raytrace "regress_lint_raytrace;ged_exec" EXEC regress_lint_raytrace)
|
||||
distclean("${CMAKE_CURRENT_BINARY_DIR}/regress-ged_lint_raytrace.log")
|
||||
|
||||
# Bounded B-Rep geometry diagnostics used by lint -I brep:*.
|
||||
brlcad_addexec(regress_lint_brep regress_lint_brep.cpp "libged;libwdb;librt;libbu" TEST_USESDATA)
|
||||
if(TARGET regress_lint_brep)
|
||||
add_dependencies(regress_lint_brep ged_plugins)
|
||||
endif(TARGET regress_lint_brep)
|
||||
brlcad_regression_test(regress-ged_lint_brep "regress_lint_brep;ged_exec" EXEC regress_lint_brep)
|
||||
distclean("${CMAKE_CURRENT_BINARY_DIR}/regress-ged_lint_brep.log")
|
||||
endif(TARGET libged)
|
||||
|
||||
cmakefiles(
|
||||
@@ -46,6 +54,7 @@ cmakefiles(
|
||||
push.cpp
|
||||
regress_facetize.cpp
|
||||
regress_lint_raytrace.cpp
|
||||
regress_lint_brep.cpp
|
||||
d2push_tests.g
|
||||
dpush_tests.g
|
||||
dxpush_tests.g
|
||||
@@ -56,6 +65,7 @@ cmakefiles(
|
||||
regress-ged_push.cmake.in
|
||||
regress-ged_facetize.cmake.in
|
||||
regress-ged_lint_raytrace.cmake.in
|
||||
regress-ged_lint_brep.cmake.in
|
||||
rpush_tests.g
|
||||
xpush_tests.g
|
||||
)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Values set at CMake configure time
|
||||
set(CBDIR "@CMAKE_CURRENT_BINARY_DIR@")
|
||||
set(LOGFILE "${CBDIR}/regress-ged_lint_brep.log")
|
||||
|
||||
set(BU_DIR_CACHE ${CBDIR}/cache_lint_brep)
|
||||
set(LIBRT_CACHE ${CBDIR}/rtcache_lint_brep)
|
||||
set(ENV{BU_DIR_CACHE} ${BU_DIR_CACHE})
|
||||
set(ENV{LIBRT_CACHE} ${LIBRT_CACHE})
|
||||
file(REMOVE_RECURSE "${BU_DIR_CACHE}")
|
||||
file(REMOVE_RECURSE "${LIBRT_CACHE}")
|
||||
file(MAKE_DIRECTORY "${BU_DIR_CACHE}")
|
||||
file(MAKE_DIRECTORY "${LIBRT_CACHE}")
|
||||
|
||||
set(TMPDIR "${CBDIR}/lint_brep_regress_tmp")
|
||||
file(REMOVE_RECURSE "${TMPDIR}")
|
||||
file(MAKE_DIRECTORY "${TMPDIR}")
|
||||
file(WRITE "${LOGFILE}" "Starting B-Rep lint regression test run\n")
|
||||
|
||||
string(REPLACE "\\" "" LINT_BREP_EXEC "${EXEC}")
|
||||
if(NOT EXISTS "${LINT_BREP_EXEC}")
|
||||
file(READ "${LOGFILE}" LOG)
|
||||
message(FATAL_ERROR "B-Rep lint regression program not found.\n${LOG}")
|
||||
endif(NOT EXISTS "${LINT_BREP_EXEC}")
|
||||
|
||||
execute_process(
|
||||
COMMAND "${LINT_BREP_EXEC}"
|
||||
RESULT_VARIABLE lint_brep_result
|
||||
OUTPUT_VARIABLE lint_brep_log
|
||||
ERROR_VARIABLE lint_brep_log
|
||||
WORKING_DIRECTORY "${TMPDIR}"
|
||||
)
|
||||
file(APPEND "${LOGFILE}" "${lint_brep_log}")
|
||||
if(lint_brep_result)
|
||||
file(READ "${LOGFILE}" LOG)
|
||||
message(FATAL_ERROR "[regress-ged_lint_brep] Failure: ${lint_brep_result}.\n${LOG}")
|
||||
endif(lint_brep_result)
|
||||
|
||||
file(REMOVE_RECURSE "${TMPDIR}")
|
||||
execute_process(COMMAND "@CMAKE_COMMAND@" -E rm -rf "${BU_DIR_CACHE}")
|
||||
execute_process(COMMAND "@CMAKE_COMMAND@" -E rm -rf "${LIBRT_CACHE}")
|
||||
|
||||
# Local Variables:
|
||||
# tab-width: 8
|
||||
# mode: cmake
|
||||
# indent-tabs-mode: t
|
||||
# End:
|
||||
# ex: shiftwidth=2 tabstop=8
|
||||
@@ -68,6 +68,13 @@
|
||||
* ("/tc6_reg.c/sub6.s#sub"), so the lookup missed and no variant was
|
||||
* applied, leaving the phantom face. After the fix the plan is built
|
||||
* from region roots so keys match and the phantom face is eliminated.
|
||||
*
|
||||
* TC7 - NMG Boolean isolation and output modes: verifies that an interrupted
|
||||
* worker cannot commit output, then exercises BoT, NMG, region, and
|
||||
* in-place output paths.
|
||||
*
|
||||
* TC8 - implicit solid roots survive the working-database close and remain
|
||||
* valid members of the imported output hierarchy.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
@@ -78,7 +85,9 @@
|
||||
#include <string>
|
||||
|
||||
#include "bu/app.h"
|
||||
#include "bu/env.h"
|
||||
#include "bu/file.h"
|
||||
#include "bu/log.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "bu/str.h"
|
||||
#include "bu/vls.h"
|
||||
@@ -142,6 +151,36 @@ write_comb2(struct rt_wdb *wdbp,
|
||||
0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
write_comb1(struct rt_wdb *wdbp, const char *comb_name, const char *member, int is_region)
|
||||
{
|
||||
struct bu_list head;
|
||||
BU_LIST_INIT(&head);
|
||||
mk_addmember(member, &head, NULL, WMOP_UNION);
|
||||
return mk_comb(wdbp, comb_name, &head, is_region, NULL, NULL, NULL,
|
||||
0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
static int
|
||||
run_facetize_command(const char *gfile, int argc, const char **argv, int verbose)
|
||||
{
|
||||
struct ged *gedp = ged_open("db", gfile, 1);
|
||||
if (!gedp) {
|
||||
bu_log("[regress_facetize] ged_open(%s) failed\n", gfile);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
|
||||
int ret = ged_exec(gedp, argc, argv);
|
||||
if (verbose || ret != BRLCAD_OK) {
|
||||
const char *log = bu_vls_cstr(gedp->ged_result_str);
|
||||
if (log && log[0])
|
||||
bu_log("[facetize] %s\n", log);
|
||||
}
|
||||
|
||||
ged_close(gedp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open @a gfile with ged_open and run "facetize @a input @a output".
|
||||
* Returns BRLCAD_OK on success, BRLCAD_ERROR otherwise.
|
||||
@@ -149,23 +188,8 @@ write_comb2(struct rt_wdb *wdbp,
|
||||
static int
|
||||
run_facetize(const char *gfile, const char *input, const char *output, int verbose)
|
||||
{
|
||||
struct ged *gedp = ged_open("db", gfile, 1);
|
||||
if (!gedp) {
|
||||
bu_log("[regress_facetize] ged_open(%s) failed\n", gfile);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
|
||||
const char *av[4] = {"facetize", input, output, NULL};
|
||||
int ret = ged_exec(gedp, 3, av);
|
||||
|
||||
if (verbose || ret != BRLCAD_OK) {
|
||||
const char *log = bu_vls_cstr(gedp->ged_result_str);
|
||||
if (log && log[0])
|
||||
bu_log("[facetize] %s\n", log);
|
||||
}
|
||||
|
||||
ged_close(gedp);
|
||||
return ret;
|
||||
return run_facetize_command(gfile, 3, av, verbose);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -212,6 +236,46 @@ bu_log("[regress_facetize] check_bot: '%s' type %d is not a BoT\n",
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int
|
||||
check_object_type(const char *gfile, const char *object_name, int expected_type)
|
||||
{
|
||||
struct db_i *dbip = db_open(gfile, DB_OPEN_READONLY);
|
||||
if (!dbip || db_dirbuild(dbip) < 0) {
|
||||
if (dbip)
|
||||
db_close(dbip);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
|
||||
struct directory *dp = db_lookup(dbip, object_name, LOOKUP_QUIET);
|
||||
if (!dp) {
|
||||
bu_log("[regress_facetize] '%s' does not exist\n", object_name);
|
||||
db_close(dbip);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
|
||||
struct rt_db_internal intern;
|
||||
RT_DB_INTERNAL_INIT(&intern);
|
||||
if (rt_db_get_internal(&intern, dp, dbip, NULL) < 0) {
|
||||
db_close(dbip);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
int ret = (intern.idb_minor_type == expected_type) ? BRLCAD_OK : BRLCAD_ERROR;
|
||||
if (ret != BRLCAD_OK)
|
||||
bu_log("[regress_facetize] '%s' type %d, expected %d\n",
|
||||
object_name, intern.idb_minor_type, expected_type);
|
||||
rt_db_free_internal(&intern);
|
||||
db_close(dbip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
count_log_hook(void *data, void *UNUSED(message))
|
||||
{
|
||||
int *count = (int *)data;
|
||||
(*count)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run rt_crofton_shoot on @a obj_name in @a gfile.
|
||||
* Stores SA (mm2) and VOL (mm3) in *sa / *vol.
|
||||
@@ -1120,6 +1184,183 @@ tc6_havoc_wind9_pattern(const char *tmpdir, int verbose)
|
||||
return BRLCAD_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
tc7_nmg_modes(const char *tmpdir, int verbose)
|
||||
{
|
||||
bu_log("[regress_facetize] TC7: NMG Boolean and output modes...\n");
|
||||
|
||||
struct bu_vls gpath = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&gpath, "%s/tc7_nmg.g", tmpdir);
|
||||
const char *gfile = bu_vls_cstr(&gpath);
|
||||
if (bu_file_exists(gfile, NULL))
|
||||
bu_file_delete(gfile);
|
||||
|
||||
struct db_i *dbip = db_create(gfile, 5);
|
||||
if (!dbip) {
|
||||
bu_vls_free(&gpath);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DEFAULT);
|
||||
point_t box_pts[8] = {
|
||||
{0.0, 0.0, 0.0}, {10.0, 0.0, 0.0},
|
||||
{10.0, 10.0, 0.0}, {0.0, 10.0, 0.0},
|
||||
{0.0, 0.0, 10.0}, {10.0, 0.0, 10.0},
|
||||
{10.0, 10.0, 10.0}, {0.0, 10.0, 10.0}
|
||||
};
|
||||
if (write_arb8(wdbp, "box.s", box_pts) < 0 ||
|
||||
write_comb1(wdbp, "box.r", "box.s", 1) < 0 ||
|
||||
write_comb1(wdbp, "box.c", "box.s", 0) < 0) {
|
||||
wdb_close(wdbp);
|
||||
bu_vls_free(&gpath);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
wdb_close(wdbp);
|
||||
|
||||
const char *bot_av[] = {"facetize", "--nmg-booleval", "box.r", "box.bot", NULL};
|
||||
const char *fault_av[] = {"facetize", "--nmg-booleval", "box.r", "fault.bot", NULL};
|
||||
const char *nmg_av[] = {"facetize", "-n", "box.r", "box.nmg", NULL};
|
||||
const char *regions_av[] = {"facetize", "-r", "--nmg-booleval", "box.r", "box.regions", NULL};
|
||||
const char *fallback_av[] = {"facetize", "-r", "--nmg-booleval", "box.c", "box-fallback.bot", NULL};
|
||||
const char *object_in_place_av[] = {"facetize", "--in-place", "--nmg-booleval", "box.c", NULL};
|
||||
const char *in_place_av[] = {"facetize", "-r", "--in-place", "--nmg-booleval", "box.r", NULL};
|
||||
|
||||
int ret = BRLCAD_OK;
|
||||
auto check_tc7 = [&ret](bool passed, const char *description) {
|
||||
if (passed)
|
||||
return;
|
||||
bu_log("[regress_facetize] TC7: FAIL - %s\n", description);
|
||||
ret = BRLCAD_ERROR;
|
||||
};
|
||||
|
||||
struct bu_vls fault_path = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&fault_path, "%s/tc7_nmg_worker.fault", tmpdir);
|
||||
(void)bu_file_delete(bu_vls_cstr(&fault_path));
|
||||
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT",
|
||||
"nmg-before-ready:fault.bot", 1);
|
||||
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT_FILE",
|
||||
bu_vls_cstr(&fault_path), 1);
|
||||
int fault_ret = run_facetize_command(gfile, 4, fault_av, verbose);
|
||||
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT", "", 1);
|
||||
(void)bu_setenv("LIBGED_FACETIZE_TEST_FAULT_FILE", "", 1);
|
||||
check_tc7(fault_ret == BRLCAD_ERROR,
|
||||
"an interrupted NMG Boolean worker did not report failure");
|
||||
check_tc7(bu_file_exists(bu_vls_cstr(&fault_path), NULL),
|
||||
"NMG Boolean worker fault did not trigger");
|
||||
check_tc7(check_object_type(gfile, "fault.bot", ID_BOT) != BRLCAD_OK,
|
||||
"an interrupted NMG Boolean worker committed an output");
|
||||
(void)bu_file_delete(bu_vls_cstr(&fault_path));
|
||||
bu_vls_free(&fault_path);
|
||||
|
||||
int hook_calls = 0;
|
||||
bu_log_add_hook(count_log_hook, &hook_calls);
|
||||
int bot_ret = run_facetize_command(gfile, 4, bot_av, verbose);
|
||||
int hook_calls_before_probe = hook_calls;
|
||||
bu_log("facetize NMG hook restoration probe\n");
|
||||
bu_log_delete_hook(count_log_hook, &hook_calls);
|
||||
|
||||
check_tc7(bot_ret == BRLCAD_OK, "NMG Boolean BoT command failed");
|
||||
check_tc7(hook_calls == hook_calls_before_probe + 1,
|
||||
"temporary log hook was not restored");
|
||||
check_tc7(check_bot_exists(gfile, "box.bot") == BRLCAD_OK,
|
||||
"NMG Boolean BoT output is missing");
|
||||
|
||||
int nmg_ret = run_facetize_command(gfile, 4, nmg_av, verbose);
|
||||
check_tc7(nmg_ret == BRLCAD_OK, "NMG output command failed");
|
||||
check_tc7(check_object_type(gfile, "box.nmg", ID_NMG) == BRLCAD_OK,
|
||||
"NMG output has the wrong type");
|
||||
|
||||
int regions_ret = run_facetize_command(gfile, 5, regions_av, verbose);
|
||||
check_tc7(regions_ret == BRLCAD_OK, "NMG Boolean region command failed");
|
||||
check_tc7(check_object_type(gfile, "box.regions", ID_COMBINATION) == BRLCAD_OK,
|
||||
"NMG Boolean region output is missing");
|
||||
|
||||
int fallback_ret = run_facetize_command(gfile, 5, fallback_av, verbose);
|
||||
check_tc7(fallback_ret == BRLCAD_OK,
|
||||
"region mode object fallback command failed");
|
||||
check_tc7(check_bot_exists(gfile, "box-fallback.bot") == BRLCAD_OK,
|
||||
"region mode object fallback output is missing");
|
||||
|
||||
int object_in_place_ret = run_facetize_command(gfile, 4,
|
||||
object_in_place_av, verbose);
|
||||
check_tc7(object_in_place_ret == BRLCAD_OK,
|
||||
"in-place NMG Boolean object command failed");
|
||||
check_tc7(check_object_type(gfile, "box.c", ID_BOT) == BRLCAD_OK,
|
||||
"in-place NMG Boolean object has the wrong type");
|
||||
|
||||
int in_place_ret = run_facetize_command(gfile, 5, in_place_av, verbose);
|
||||
check_tc7(in_place_ret == BRLCAD_OK,
|
||||
"in-place NMG Boolean region command failed");
|
||||
check_tc7(check_object_type(gfile, "box.r", ID_COMBINATION) == BRLCAD_OK,
|
||||
"in-place region root has the wrong type");
|
||||
check_tc7(check_bot_exists(gfile, "box.r.bot") == BRLCAD_OK,
|
||||
"in-place region BoT output is missing");
|
||||
|
||||
bu_log("[regress_facetize] TC7: %s\n", ret == BRLCAD_OK ? "PASS" : "FAIL");
|
||||
bu_vls_free(&gpath);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* TC8: implicit root replacement persists across database reopen */
|
||||
/* ------------------------------------------------------------------ */
|
||||
static int
|
||||
tc8_implicit_root_persistence(const char *tmpdir, int verbose)
|
||||
{
|
||||
bu_log("[regress_facetize] TC8: implicit root persistence...\n");
|
||||
|
||||
struct bu_vls gpath = BU_VLS_INIT_ZERO;
|
||||
bu_vls_printf(&gpath, "%s/tc8_implicit_root.g", tmpdir);
|
||||
const char *gfile = bu_vls_cstr(&gpath);
|
||||
if (bu_file_exists(gfile, NULL))
|
||||
bu_file_delete(gfile);
|
||||
|
||||
struct db_i *dbip = db_create(gfile, 5);
|
||||
if (!dbip) {
|
||||
bu_vls_free(&gpath);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DEFAULT);
|
||||
point_t region_points[8] = {
|
||||
{0.0, 0.0, 0.0}, {10.0, 0.0, 0.0},
|
||||
{10.0, 10.0, 0.0}, {0.0, 10.0, 0.0},
|
||||
{0.0, 0.0, 10.0}, {10.0, 0.0, 10.0},
|
||||
{10.0, 10.0, 10.0}, {0.0, 10.0, 10.0}
|
||||
};
|
||||
point_t loose_points[8] = {
|
||||
{20.0, 0.0, 0.0}, {30.0, 0.0, 0.0},
|
||||
{30.0, 10.0, 0.0}, {20.0, 10.0, 0.0},
|
||||
{20.0, 0.0, 10.0}, {30.0, 0.0, 10.0},
|
||||
{30.0, 10.0, 10.0}, {20.0, 10.0, 10.0}
|
||||
};
|
||||
if (!wdbp || write_arb8(wdbp, "region.s", region_points) < 0 ||
|
||||
write_arb8(wdbp, "loose.s", loose_points) < 0 ||
|
||||
write_comb1(wdbp, "box.r", "region.s", 1) < 0 ||
|
||||
write_comb2(wdbp, "assembly.c", "box.r", 'u',
|
||||
"loose.s", 'u') < 0) {
|
||||
if (wdbp)
|
||||
wdb_close(wdbp);
|
||||
bu_vls_free(&gpath);
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
wdb_close(wdbp);
|
||||
|
||||
const char *facetize_args[] = {
|
||||
"facetize", "-r", "assembly.c", "assembly.regions", NULL
|
||||
};
|
||||
int ret = run_facetize_command(gfile, 4, facetize_args, verbose);
|
||||
if (ret == BRLCAD_OK &&
|
||||
(check_object_type(gfile, "assembly.regions", ID_COMBINATION) != BRLCAD_OK ||
|
||||
check_object_type(gfile, "facetize_assembly.c", ID_COMBINATION) != BRLCAD_OK ||
|
||||
check_bot_exists(gfile, "box.r.bot") != BRLCAD_OK ||
|
||||
check_bot_exists(gfile, "facetize_loose.s") != BRLCAD_OK))
|
||||
ret = BRLCAD_ERROR;
|
||||
|
||||
bu_log("[regress_facetize] TC8: %s\n",
|
||||
ret == BRLCAD_OK ? "PASS" : "FAIL");
|
||||
bu_vls_free(&gpath);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* main */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -1151,6 +1392,8 @@ return 1;
|
||||
if (tc4_havoc_reuse(tmpdir, verbose) != BRLCAD_OK) ret = 1;
|
||||
if (tc5_near_coplanar_subTOL(tmpdir, verbose) != BRLCAD_OK) ret = 1;
|
||||
if (tc6_havoc_wind9_pattern(tmpdir, verbose) != BRLCAD_OK) ret = 1;
|
||||
if (tc7_nmg_modes(tmpdir, verbose) != BRLCAD_OK) ret = 1;
|
||||
if (tc8_implicit_root_persistence(tmpdir, verbose) != BRLCAD_OK) ret = 1;
|
||||
|
||||
if (ret == 0)
|
||||
bu_log("[regress_facetize] All tests PASSED\n");
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
/* R E G R E S S _ L I N T _ B R E P . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
/** @file regress_lint_brep.cpp
|
||||
*
|
||||
* Regression tests for the bounded B-Rep geometry lint checks.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "bu/app.h"
|
||||
#include "bu/file.h"
|
||||
#include "bu/vls.h"
|
||||
#include "ged.h"
|
||||
#include "raytrace.h"
|
||||
#include "wdb.h"
|
||||
|
||||
|
||||
static std::string
|
||||
read_file(const char *path)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (!f.is_open())
|
||||
return std::string();
|
||||
return std::string((std::istreambuf_iterator<char>(f)),
|
||||
std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
|
||||
static std::string
|
||||
temporary_path(const char *suffix)
|
||||
{
|
||||
char path[MAXPATHLEN] = {0};
|
||||
FILE *fp = bu_temp_file(path, MAXPATHLEN);
|
||||
if (fp)
|
||||
fclose(fp);
|
||||
struct bu_vls result = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&result, "%s_%s", path, suffix);
|
||||
std::string ret(bu_vls_cstr(&result));
|
||||
bu_vls_free(&result);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static ON_Brep *
|
||||
box_brep()
|
||||
{
|
||||
ON_3dPoint corners[8] = {
|
||||
ON_3dPoint(0.0, 0.0, 0.0), ON_3dPoint(10.0, 0.0, 0.0),
|
||||
ON_3dPoint(10.0, 10.0, 0.0), ON_3dPoint(0.0, 10.0, 0.0),
|
||||
ON_3dPoint(0.0, 0.0, 10.0), ON_3dPoint(10.0, 0.0, 10.0),
|
||||
ON_3dPoint(10.0, 10.0, 10.0), ON_3dPoint(0.0, 10.0, 10.0)
|
||||
};
|
||||
return ON_BrepBox(corners);
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
make_database(const char *path)
|
||||
{
|
||||
struct rt_wdb *wdbp = wdb_fopen(path);
|
||||
if (!wdbp)
|
||||
return false;
|
||||
|
||||
ON_Brep *good = box_brep();
|
||||
ON_Brep *bad = box_brep();
|
||||
ON_Brep *shading_failure = box_brep();
|
||||
if (!good || !bad || !shading_failure) {
|
||||
delete good;
|
||||
delete bad;
|
||||
delete shading_failure;
|
||||
db_close(wdbp->dbip);
|
||||
return false;
|
||||
}
|
||||
|
||||
ON_BrepLoop *failed_loop = shading_failure->m_F[0].OuterLoop();
|
||||
if (!failed_loop) {
|
||||
delete good;
|
||||
delete bad;
|
||||
delete shading_failure;
|
||||
db_close(wdbp->dbip);
|
||||
return false;
|
||||
}
|
||||
shading_failure->FlipLoop(*failed_loop);
|
||||
failed_loop->m_type = ON_BrepLoop::inner;
|
||||
|
||||
ON_BrepEdge &edge = bad->m_E[0];
|
||||
ON_Curve *curve = (edge.m_c3i >= 0 && edge.m_c3i < bad->m_C3.Count()) ?
|
||||
bad->m_C3[edge.m_c3i] : NULL;
|
||||
const ON_Xform shift = ON_Xform::TranslationTransformation(
|
||||
ON_3dVector(0.0, 0.0, 1.0));
|
||||
if (!curve || !curve->Transform(shift)) {
|
||||
delete good;
|
||||
delete bad;
|
||||
db_close(wdbp->dbip);
|
||||
return false;
|
||||
}
|
||||
edge.m_tolerance = 5.0;
|
||||
for (int vi = 0; vi < 2; ++vi) {
|
||||
ON_BrepVertex *vertex = edge.Vertex(vi);
|
||||
if (vertex)
|
||||
vertex->m_tolerance = 5.0;
|
||||
}
|
||||
for (int eti = 0; eti < edge.m_ti.Count(); ++eti) {
|
||||
const int ti = edge.m_ti[eti];
|
||||
if (ti >= 0 && ti < bad->m_T.Count()) {
|
||||
bad->m_T[ti].m_tolerance[0] = 5.0;
|
||||
bad->m_T[ti].m_tolerance[1] = 5.0;
|
||||
}
|
||||
}
|
||||
|
||||
ON_TextLog text_log(stderr);
|
||||
const bool valid = good->IsValid(&text_log) && bad->IsValid(&text_log);
|
||||
const bool written = valid && mk_brep(wdbp, "good.s", good) == 0 &&
|
||||
mk_brep(wdbp, "masked_mismatch.s", bad) == 0 &&
|
||||
mk_brep(wdbp, "shading_failure.s", shading_failure) == 0;
|
||||
delete good;
|
||||
delete bad;
|
||||
delete shading_failure;
|
||||
db_close(wdbp->dbip);
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
run_lint(const char *gfile, const char *object, const char *json_file,
|
||||
const char *techniques)
|
||||
{
|
||||
struct ged *gedp = ged_open("db", gfile, 1);
|
||||
if (!gedp)
|
||||
return BRLCAD_ERROR;
|
||||
const char *argv[] = {
|
||||
"lint", "-I", techniques,
|
||||
"-j", json_file, object
|
||||
};
|
||||
const int ret = ged_exec(gedp, 6, argv);
|
||||
ged_close(gedp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
contains_problem(const std::string &json, const char *problem)
|
||||
{
|
||||
struct bu_vls pattern = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&pattern, "\"problem_type\": \"%s\"", problem);
|
||||
const bool found = json.find(bu_vls_cstr(&pattern)) != std::string::npos;
|
||||
bu_vls_free(&pattern);
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
if (argc == 4) {
|
||||
const char *checks = "brep:edge_surface_mismatch brep:large_tolerance "
|
||||
"brep:singular_boundary brep:trim_loop_crossing";
|
||||
return run_lint(argv[1], argv[2], argv[3], checks);
|
||||
}
|
||||
if (argc != 1)
|
||||
return 1;
|
||||
|
||||
const std::string gfile = temporary_path("lint_brep.g");
|
||||
const std::string good_json = temporary_path("lint_brep_good.json");
|
||||
const std::string bad_json = temporary_path("lint_brep_bad.json");
|
||||
const std::string shading_json = temporary_path("lint_brep_shading.json");
|
||||
bool pass = make_database(gfile.c_str());
|
||||
|
||||
const char *checks = "brep:edge_surface_mismatch brep:large_tolerance "
|
||||
"brep:singular_boundary brep:trim_loop_crossing";
|
||||
if (pass)
|
||||
pass = run_lint(gfile.c_str(), "good.s", good_json.c_str(), checks) ==
|
||||
BRLCAD_OK;
|
||||
const std::string good_result = read_file(good_json.c_str());
|
||||
if (pass)
|
||||
pass = !contains_problem(good_result, "edge_surface_mismatch") &&
|
||||
!contains_problem(good_result, "large_tolerance") &&
|
||||
!contains_problem(good_result, "singular_boundary") &&
|
||||
!contains_problem(good_result, "trim_loop_crossing");
|
||||
|
||||
if (pass)
|
||||
pass = run_lint(gfile.c_str(), "masked_mismatch.s", bad_json.c_str(),
|
||||
checks) == BRLCAD_OK;
|
||||
const std::string bad_result = read_file(bad_json.c_str());
|
||||
if (pass)
|
||||
pass = contains_problem(bad_result, "edge_surface_mismatch") &&
|
||||
contains_problem(bad_result, "large_tolerance") &&
|
||||
bad_result.find("\"declared_tolerance_masks\": true") !=
|
||||
std::string::npos;
|
||||
|
||||
if (pass)
|
||||
pass = run_lint(gfile.c_str(), "good.s", shading_json.c_str(),
|
||||
"brep:fast_shading") == BRLCAD_OK;
|
||||
std::string shading_result = read_file(shading_json.c_str());
|
||||
if (pass)
|
||||
pass = !contains_problem(shading_result, "fast_shading_failure") &&
|
||||
!contains_problem(shading_result, "fast_shading_incomplete");
|
||||
|
||||
if (pass)
|
||||
pass = run_lint(gfile.c_str(), "shading_failure.s",
|
||||
shading_json.c_str(), "brep:opennurbs") == BRLCAD_OK;
|
||||
shading_result = read_file(shading_json.c_str());
|
||||
if (pass)
|
||||
pass = contains_problem(shading_result, "opennurbs_invalid") &&
|
||||
!contains_problem(shading_result, "fast_shading_failure") &&
|
||||
!contains_problem(shading_result, "fast_shading_incomplete");
|
||||
|
||||
if (pass)
|
||||
pass = run_lint(gfile.c_str(), "shading_failure.s",
|
||||
shading_json.c_str(), "brep:fast_shading") == BRLCAD_OK;
|
||||
shading_result = read_file(shading_json.c_str());
|
||||
if (pass)
|
||||
pass = contains_problem(shading_result, "fast_shading_failure") &&
|
||||
shading_result.find("\"face\": 0") != std::string::npos &&
|
||||
shading_result.find("\"stage_name\": \"pslg_validation\"") !=
|
||||
std::string::npos;
|
||||
|
||||
bu_file_delete(gfile.c_str());
|
||||
bu_file_delete(good_json.c_str());
|
||||
bu_file_delete(bad_json.c_str());
|
||||
bu_file_delete(shading_json.c_str());
|
||||
if (!pass)
|
||||
bu_log("B-Rep lint regression failed\n");
|
||||
return pass ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
// Local Variables:
|
||||
// tab-width: 8
|
||||
// mode: C++
|
||||
// c-basic-offset: 4
|
||||
// indent-tabs-mode: t
|
||||
// c-file-style: "stroustrup"
|
||||
// End:
|
||||
// ex: shiftwidth=4 tabstop=8
|
||||
@@ -100,6 +100,7 @@ if(TARGET mged)
|
||||
continue()
|
||||
endif(TNAME STREQUAL "")
|
||||
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${testfile}")
|
||||
file(READ "${CMAKE_CURRENT_SOURCE_DIR}/${testfile}" TEST_CONTENTS)
|
||||
if(TNAME STREQUAL "search_exec")
|
||||
string(CONFIGURE "${TEST_CONTENTS}" TEST_CONTENTS @ONLY)
|
||||
@@ -126,6 +127,12 @@ if(TARGET mged)
|
||||
elseif(TNAME STREQUAL "arb")
|
||||
set(TEST_EXPECT "'arb' testing completed")
|
||||
set(TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/regress_${testfile}")
|
||||
elseif(TNAME MATCHES "^(extrude|facedef|mirface|permute)$")
|
||||
set(TEST_EXPECT "'${TNAME}' testing completed")
|
||||
set(TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/regress_${testfile}")
|
||||
elseif(TNAME STREQUAL "edgedir")
|
||||
set(TEST_EXPECT "'edarb edgedir' testing completed")
|
||||
set(TEST_SOURCE "${CMAKE_CURRENT_BINARY_DIR}/regress_${testfile}")
|
||||
elseif(TNAME STREQUAL "killtree")
|
||||
set(TEST_EXPECT "KILL Solid: killtree_dup.s")
|
||||
set(TEST_REJECT "db_lookup(killtree_dup.s) failed")
|
||||
@@ -187,6 +194,7 @@ distclean(${mged_outfiles})
|
||||
|
||||
cmakefiles(
|
||||
CMakeLists.txt
|
||||
csg_edit.mged
|
||||
mged.tcl
|
||||
regression_resources.tcl
|
||||
search_exec_procs.tcl
|
||||
|
||||
@@ -962,8 +962,140 @@ foreach test [list \
|
||||
csg_matrix_mouse_check {*}$test
|
||||
}
|
||||
|
||||
puts "--- Repeated graphical object transforms ---"
|
||||
proc csg_matrix_repeated_mouse_check {} {
|
||||
global orig_gui transform csg_mouse_dm_pixels csg_mouse_view_size
|
||||
global csg_drag_scale
|
||||
set prim csg_matrix_repeat.s
|
||||
set inner csg_matrix_repeat_inner.c
|
||||
set outer csg_matrix_repeat_outer.c
|
||||
set saved_orig_gui $orig_gui
|
||||
set expected [csg_edit_identity_matrix]
|
||||
set failed [catch {
|
||||
in $prim sph 0 0 0 10
|
||||
comb $inner u $prim
|
||||
comb $outer u $inner
|
||||
set orig_gui 0
|
||||
for {set step 0} {$step < 16} {incr step} {
|
||||
e $outer
|
||||
center 0 0 0
|
||||
size $csg_mouse_view_size
|
||||
setview 0 0 0
|
||||
dm size $csg_mouse_dm_pixels $csg_mouse_dm_pixels
|
||||
_mged_press oill
|
||||
_mged_ill -e -i 1 /$outer/$inner/$prim
|
||||
_mged_matpick 2
|
||||
if {[status state] ne "OBJ EDIT" || $transform ne "e"} {
|
||||
error "step $step did not enter object editing"
|
||||
}
|
||||
set view_before [csg_edit_view_state]
|
||||
if {$step % 2 == 0} {
|
||||
press Rotate
|
||||
dm con r z 100 100
|
||||
set operation [csg_matrix_axis_rotation z 10]
|
||||
set motion {dm motion 140 100}
|
||||
} else {
|
||||
press Scale
|
||||
dm am s 100 100
|
||||
set operation [csg_matrix_scale all $csg_drag_scale]
|
||||
set motion {dm motion 164 100}
|
||||
}
|
||||
set motion_code [catch {uplevel #0 $motion} motion_result]
|
||||
if {$motion_code != 0 && $motion_code != 2} {
|
||||
error "step $step mouse motion failed: $motion_result"
|
||||
}
|
||||
dm idle
|
||||
press accept
|
||||
set expected [csg_matrix_multiply $operation $expected]
|
||||
csg_edit_comb_leaf_matrix_at $inner $prim $expected
|
||||
csg_edit_view_state_at $view_before
|
||||
d $outer
|
||||
}
|
||||
} failure]
|
||||
set orig_gui $saved_orig_gui
|
||||
catch {press reject}
|
||||
catch {Z}
|
||||
foreach object [list $outer $inner $prim] {
|
||||
catch {kill $object}
|
||||
}
|
||||
if {$failed} {
|
||||
puts " FAIL: repeated object mouse rotations/scales ($failure)"
|
||||
} else {
|
||||
puts " PASS: repeated object mouse rotations/scales"
|
||||
}
|
||||
}
|
||||
csg_matrix_repeated_mouse_check
|
||||
|
||||
puts "--- All ARB point, edge, and face states ---"
|
||||
|
||||
proc csg_edit_sed_point {attribute} {
|
||||
set serialized [get_sed]
|
||||
set index [lsearch -exact $serialized $attribute]
|
||||
if {$index < 0 || $index + 1 >= [llength $serialized]} {
|
||||
error "get_sed has no $attribute: $serialized"
|
||||
}
|
||||
return [lindex $serialized [expr {$index + 1}]]
|
||||
}
|
||||
|
||||
proc csg_arb_repeated_mouse_check {} {
|
||||
global orig_gui csg_bv_coord_half_range csg_mouse_dm_pixels
|
||||
global csg_mouse_view_size
|
||||
set source csg_lifecycle_arb5.s
|
||||
set prim csg_arb_repeat.s
|
||||
set saved_orig_gui $orig_gui
|
||||
set expected {}
|
||||
set failed [catch {
|
||||
cp $source $prim
|
||||
e $prim
|
||||
center 0 0 0
|
||||
size $csg_mouse_view_size
|
||||
setview 0 0 0
|
||||
dm size $csg_mouse_dm_pixels $csg_mouse_dm_pixels
|
||||
set orig_gui 0
|
||||
sed $prim
|
||||
press {Move Edges}
|
||||
press {Move Point 5}
|
||||
set original_z [lindex [db get $source V5] 2]
|
||||
for {set step 0} {$step < 64} {incr step} {
|
||||
if {$step > 0 && $step % 8 == 0} {
|
||||
press sedit
|
||||
press {Move Edges}
|
||||
press {Move Point 5}
|
||||
}
|
||||
set mouse_x [expr {150 + 2 * ($step % 5)}]
|
||||
set mouse_y [expr {150 + 2 * (($step * 3) % 5)}]
|
||||
M 1 $mouse_x $mouse_y
|
||||
set expected [list \
|
||||
[expr {$mouse_x / $csg_bv_coord_half_range * $csg_mouse_view_size / 2.0}] \
|
||||
[expr {$mouse_y / $csg_bv_coord_half_range * $csg_mouse_view_size / 2.0}] \
|
||||
$original_z]
|
||||
set actual [csg_edit_sed_point V5]
|
||||
if {[csg_edit_point_distance $actual $expected] > 0.000001} {
|
||||
error "step $step expected V5 $expected, got $actual"
|
||||
}
|
||||
}
|
||||
press accept
|
||||
d $prim
|
||||
if {[csg_edit_point_distance [db get $prim V5] $expected] > 0.000001} {
|
||||
error "accepted V5 differs from the final mouse target"
|
||||
}
|
||||
foreach vertex {V1 V2 V3 V4} {
|
||||
csg_edit_point_matches_source $source $vertex $prim
|
||||
}
|
||||
arb_standard_storage_check arb5 $prim
|
||||
} failure]
|
||||
set orig_gui $saved_orig_gui
|
||||
catch {press reject}
|
||||
catch {d $prim}
|
||||
catch {kill $prim}
|
||||
if {$failed} {
|
||||
puts " FAIL: repeated ARB5 mouse movement ($failure)"
|
||||
} else {
|
||||
puts " PASS: repeated ARB5 mouse movement"
|
||||
}
|
||||
}
|
||||
csg_arb_repeated_mouse_check
|
||||
|
||||
# Every ARB menu entry is checked independently. The variants do not share
|
||||
# menu indices, so sampling one edge or face would miss precisely the class of
|
||||
# integration failure this test is intended to catch. Move each selection a
|
||||
@@ -1322,6 +1454,280 @@ csg_edit_sequence_check csg_lifecycle_dsp.s "Toggle Smooth" {
|
||||
{press {Toggle Smooth}}
|
||||
} [list csg_edit_scalar_at sm 1]
|
||||
|
||||
puts "--- Specialized mouse editing and menu inventory ---"
|
||||
proc csg_edit_prepare_mouse_view {} {
|
||||
global csg_dispatch_view_size
|
||||
center 0 0 0
|
||||
size $csg_dispatch_view_size
|
||||
setview 0 0 0
|
||||
}
|
||||
array set csg_mouse_covered {}
|
||||
proc csg_mouse_case {source menu_items label commands validator} {
|
||||
global csg_mouse_covered
|
||||
csg_edit_sequence_check $source $label $commands $validator
|
||||
set type [lindex [db get $source] 0]
|
||||
if {![info exists csg_mouse_covered($type)]} {
|
||||
set csg_mouse_covered($type) {}
|
||||
}
|
||||
foreach item $menu_items {
|
||||
if {[lsearch -exact $csg_mouse_covered($type) $item] < 0} {
|
||||
lappend csg_mouse_covered($type) $item
|
||||
}
|
||||
}
|
||||
}
|
||||
proc csg_edit_menu_inventory_check {source ignored} {
|
||||
global csg_mouse_covered
|
||||
if {[catch {
|
||||
Z
|
||||
e $source
|
||||
sed $source
|
||||
set actual [mmenu_get 0]
|
||||
set type [lindex [db get $source] 0]
|
||||
set covered {}
|
||||
if {[info exists csg_mouse_covered($type)]} {
|
||||
set covered $csg_mouse_covered($type)
|
||||
}
|
||||
foreach item $actual {
|
||||
set tested [expr {[lsearch -exact $covered $item] >= 0}]
|
||||
set exempt [expr {[lsearch -exact $ignored $item] >= 0}]
|
||||
if {$tested == $exempt} {
|
||||
error "menu entry '$item' needs exactly one mouse-test or exclusion"
|
||||
}
|
||||
}
|
||||
foreach item [concat $covered $ignored] {
|
||||
if {[lsearch -exact $actual $item] < 0} {
|
||||
error "recorded menu entry '$item' is absent from $actual"
|
||||
}
|
||||
}
|
||||
press reject
|
||||
d $source
|
||||
} failure]} {
|
||||
puts " FAIL: menu inventory $source ($failure)"
|
||||
catch {press reject}
|
||||
catch {Z}
|
||||
} else {
|
||||
puts " PASS: menu inventory $source"
|
||||
}
|
||||
}
|
||||
|
||||
csg_edit_prepare_mouse_view
|
||||
set csg_special_mouse_target [list \
|
||||
[expr {512.0 / $csg_bv_coord_half_range * $csg_dispatch_view_size / 2.0}] \
|
||||
[expr {512.0 / $csg_bv_coord_half_range * $csg_dispatch_view_size / 2.0}] 0]
|
||||
set csg_special_mouse_scale [expr {1.0 + 2047.0 / ($csg_bv_coord_half_range * 4.0)}]
|
||||
set csg_special_mouse_shrink [expr {
|
||||
1.0 / (1.0 + 1024.0 / ($csg_bv_coord_half_range * 4.0))}]
|
||||
csg_mouse_case csg_lifecycle_pipe.s {{Move Point}} "mouse select and move point" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Select Point}}
|
||||
{p 0 0 0}
|
||||
{press {Move Point}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_point_at V0 $csg_special_mouse_target]
|
||||
in csg_mouse_pipe_topology.s pipe 4 \
|
||||
0 0 0 1 2 3 \
|
||||
0 0 30 1 2 3 \
|
||||
30 40 80 1 2 3 \
|
||||
80 80 100 1 2 3
|
||||
csg_mouse_case csg_mouse_pipe_topology.s {{Select Point} {Move Point}} "mouse select point" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Select Point}}
|
||||
{M 1 0 0}
|
||||
{press {Move Point}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_point_at V0 $csg_special_mouse_target]
|
||||
csg_mouse_case csg_mouse_pipe_topology.s {{Append Point}} "mouse append point" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Append Point}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_point_at V4 {5 5 0}]
|
||||
csg_mouse_case csg_mouse_pipe_topology.s {{Prepend Point}} "mouse prepend point" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Prepend Point}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_point_at V0 {5 5 0}]
|
||||
csg_mouse_case csg_mouse_pipe_topology.s {{Split Segment}} "mouse split segment" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Select Point}}
|
||||
{p 0 0 0}
|
||||
{press {Split Segment}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_point_at V1 {5 5 0}]
|
||||
foreach {item attribute initial} {
|
||||
{Set Point OD} O0 5
|
||||
{Set Point ID} I0 3
|
||||
{Set Point Bend} R0 6
|
||||
} {
|
||||
csg_mouse_case csg_lifecycle_pipe.s [list $item] "mouse $item" [list \
|
||||
{csg_edit_prepare_mouse_view} \
|
||||
{press {Select Point}} {p 0 0 0} [list press $item] \
|
||||
{M 1 0 -1024}] \
|
||||
[list csg_edit_scalar_at $attribute [expr {$initial * $csg_special_mouse_shrink}]]
|
||||
}
|
||||
foreach {item attributes initial} {
|
||||
{Set Pipe OD} {O0 O1 O2 O3} {5 5 6 6}
|
||||
{Set Pipe ID} {I0 I1 I2 I3} {3 3 2 0}
|
||||
{Set Pipe Bend} {R0 R1 R2 R3} {6 7 10 8}
|
||||
} {
|
||||
set expected {}
|
||||
foreach value $initial {
|
||||
lappend expected [expr {$value * $csg_special_mouse_shrink}]
|
||||
}
|
||||
csg_mouse_case csg_lifecycle_pipe.s [list $item] "mouse $item" [list \
|
||||
{csg_edit_prepare_mouse_view} \
|
||||
[list press $item] {M 1 0 -1024}] \
|
||||
[list csg_edit_scalar_values_at $attributes $expected]
|
||||
}
|
||||
proc csg_edit_bot_vertices_moved {source moved delta prim} {
|
||||
set vertices [db get $source V]
|
||||
for {set index 0} {$index < [llength $vertices]} {incr index} {
|
||||
set expected [lindex $vertices $index]
|
||||
if {[lsearch -exact $moved $index] >= 0} {
|
||||
set translated {}
|
||||
foreach value $expected shift $delta {
|
||||
lappend translated [expr {$value + $shift}]
|
||||
}
|
||||
set expected $translated
|
||||
}
|
||||
csg_edit_list_value_at V $index $expected $prim
|
||||
}
|
||||
}
|
||||
csg_mouse_case csg_lifecycle_bot.s {{Move Vertex}} "mouse move vertex" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Pick Vertex}}
|
||||
{p 0}
|
||||
{press {Move Vertex}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0} \
|
||||
$csg_special_mouse_target]
|
||||
csg_mouse_case csg_lifecycle_bot.s {{Pick Vertex} {Move Vertex}} "mouse pick and move vertex" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Pick Vertex}}
|
||||
{M 1 0 0}
|
||||
{press {Move Vertex}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0} \
|
||||
$csg_special_mouse_target]
|
||||
csg_mouse_case csg_lifecycle_bot.s {{Move Edge}} "mouse move edge" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Pick Edge}}
|
||||
{p 0 1}
|
||||
{press {Move Edge}}
|
||||
{M 1 1024 512}
|
||||
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0 1} {10 5 0}]
|
||||
csg_mouse_case csg_lifecycle_bot.s {{Pick Edge} {Move Edge}} "mouse pick and move edge" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Pick Edge}}
|
||||
{M 1 512 512}
|
||||
{press {Move Edge}}
|
||||
{M 1 1024 512}
|
||||
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0 1} {10 5 0}]
|
||||
csg_mouse_case csg_lifecycle_bot.s {{Move Triangle}} "mouse move triangle" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Pick Triangle}}
|
||||
{p 0 1 2}
|
||||
{press {Move Triangle}}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_bot_vertices_moved csg_lifecycle_bot.s {0 1 2} \
|
||||
$csg_special_mouse_target]
|
||||
csg_mouse_case csg_lifecycle_ebm.s {{Extrude Depth}} "mouse extrusion depth" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Extrude Depth}}
|
||||
{M 1 0 2047}
|
||||
} [list csg_edit_scalar_at H [expr {4.0 * $csg_special_mouse_scale}]]
|
||||
csg_mouse_case csg_lifecycle_vol.s {{Threshold (low)}} "mouse low threshold" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Threshold (low)}}
|
||||
{p 10}
|
||||
{M 1 0 2047}
|
||||
} [list csg_edit_scalar_at lo [expr {int(10.0 * $csg_special_mouse_scale)}]]
|
||||
csg_mouse_case csg_lifecycle_vol.s {{Voxel Size (X Y Z)}} "mouse voxel size" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Voxel Size (X Y Z)}}
|
||||
{M 1 0 2047}
|
||||
} [list csg_edit_point_at size [list \
|
||||
$csg_special_mouse_scale $csg_special_mouse_scale $csg_special_mouse_scale]]
|
||||
csg_mouse_case csg_lifecycle_vol.s {{Threshold (hi)}} "mouse high threshold" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Threshold (hi)}}
|
||||
{M 1 0 -1024}
|
||||
} [list csg_edit_scalar_at hi [expr {int(255.0 * $csg_special_mouse_shrink)}]]
|
||||
csg_mouse_case csg_lifecycle_dsp.s {{Set X}} "mouse X scale" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{press {Set X}}
|
||||
{M 1 0 2047}
|
||||
} [list csg_edit_matrix_component_at stom 0 $csg_special_mouse_scale]
|
||||
foreach {item index} {{Set Y} 5 {Set ALT} 10} {
|
||||
csg_mouse_case csg_lifecycle_dsp.s [list $item] "mouse $item" [list \
|
||||
{csg_edit_prepare_mouse_view} \
|
||||
[list press $item] {M 1 0 2047}] \
|
||||
[list csg_edit_matrix_component_at stom $index $csg_special_mouse_scale]
|
||||
}
|
||||
foreach type {arbn brep} {
|
||||
set source csg_lifecycle_${type}.s
|
||||
csg_edit_sequence_check $source "generic mouse translation" {
|
||||
{csg_edit_prepare_mouse_view}
|
||||
{keypoint 0 0 0}
|
||||
{press sxy}
|
||||
{M 1 512 512}
|
||||
} [list csg_edit_bbox_translated $source $csg_special_mouse_target]
|
||||
}
|
||||
|
||||
# Every specialized menu entry must be accounted for. Exclusions are menu
|
||||
# headings, non-positional actions/settings, or the BOT triangle picker (tested
|
||||
# under Xmin because it needs Tk). A new entry without a mouse case or
|
||||
# explicit exclusion fails here.
|
||||
foreach {source ignored} {
|
||||
csg_lifecycle_pipe.s {
|
||||
{PIPE MENU} {Next Point} {Previous Point} {Delete Point}
|
||||
}
|
||||
csg_lifecycle_bot.s {
|
||||
{BOT MENU} {Pick Triangle} {Move Vertex List}
|
||||
{Split Edge} {Split Face} {Fuse Vertices} {Fuse Faces}
|
||||
{Delete Triangle} {Select Mode} {Select Orientation}
|
||||
{Set flags} {Set Face Thickness} {Set Face Mode}
|
||||
}
|
||||
csg_lifecycle_dsp.s {
|
||||
{DSP MENU} Name {Toggle Smooth} {Set Data Source}
|
||||
}
|
||||
csg_lifecycle_ebm.s {
|
||||
{EBM MENU} {File Name} {File Size (W N)}
|
||||
}
|
||||
csg_lifecycle_vol.s {
|
||||
{VOL MENU} {File Name} {File Size (X Y Z)}
|
||||
}
|
||||
csg_lifecycle_arbn.s {}
|
||||
csg_lifecycle_brep.s {}
|
||||
} {
|
||||
csg_edit_menu_inventory_check $source $ignored
|
||||
}
|
||||
|
||||
# A view-space request must remain view-space even when the editor's current
|
||||
# coordinate system is object-relative. This is the fast path underlying the
|
||||
# corresponding real mouse drag in the Xmin GUI test.
|
||||
set csg_saved_coords [rset var coords]
|
||||
set csg_saved_transform [rset var transform]
|
||||
if {[catch {
|
||||
Z
|
||||
e csg_lifecycle_bot.s
|
||||
sed csg_lifecycle_bot.s
|
||||
center 0 0 0
|
||||
rset var coords o
|
||||
rset var transform v
|
||||
knob -i -v aX 1
|
||||
if {[csg_edit_point_distance [center] {-1 0 0}] > 0.000001} {
|
||||
error "explicit view knob left center at [center]"
|
||||
}
|
||||
} csg_view_override_error]} {
|
||||
puts " FAIL: view-space translation with object coordinates ($csg_view_override_error)"
|
||||
} else {
|
||||
puts " PASS: view-space translation with object coordinates"
|
||||
}
|
||||
catch {press reject}
|
||||
catch {Z}
|
||||
rset var coords $csg_saved_coords
|
||||
rset var transform $csg_saved_transform
|
||||
|
||||
file delete -force $csg_data_file
|
||||
file delete -force $csg_dsp_file
|
||||
|
||||
|
||||
@@ -43,4 +43,37 @@ foreach {arb_type face expected_type} $arb_extrude_cases {
|
||||
arb_standard_storage_check $expected_type $prim
|
||||
}
|
||||
|
||||
# A one-inch extrusion must extend a face by 25.4 base millimeters through
|
||||
# both the direct libged command and MGED's interactive solid editor.
|
||||
set inch_in_mm 25.4
|
||||
set tolerance 0.001
|
||||
foreach mode {direct interactive} {
|
||||
set suffix "_inch_$mode"
|
||||
in_arb8 extrude $suffix
|
||||
set prim "extrude_arb8${suffix}.s"
|
||||
set base_x [lindex [db get $prim V1] 0]
|
||||
|
||||
units in
|
||||
if {$mode eq "direct"} {
|
||||
if {[catch {edarb extrude $prim 1234 1} result]} {
|
||||
error "inch edarb extrusion failed: $result"
|
||||
}
|
||||
} else {
|
||||
Z
|
||||
e $prim
|
||||
sed $prim
|
||||
if {[catch {extrude 1234 1} result]} {
|
||||
error "inch MGED extrusion failed: $result"
|
||||
}
|
||||
accept
|
||||
Z
|
||||
}
|
||||
units mm
|
||||
|
||||
set new_x [lindex [db get $prim V5] 0]
|
||||
if {abs($new_x - $base_x - $inch_in_mm) > $tolerance} {
|
||||
error "inch $mode extrusion moved V5 by [expr {$new_x - $base_x}] mm"
|
||||
}
|
||||
}
|
||||
|
||||
puts "*** 'extrude' testing completed ***"
|
||||
|
||||
@@ -31,6 +31,7 @@ in tor_pe.s tor 0 0 0 0 0 1 20 2
|
||||
prim_edit_check tor_pe.s "Set Radius 1" 30 r_a 30 -1
|
||||
prim_edit_check tor_pe.s "Set Radius 2" 5 r_h 5 -1
|
||||
prim_edit_mouse_check tor_pe.s "Set Radius 1" r_a 30 -1
|
||||
prim_edit_mouse_check tor_pe.s "Set Radius 2" r_h 5 -1
|
||||
kill tor_pe.s
|
||||
|
||||
puts "--- ELL (ellipsoid: A, B, C) ---"
|
||||
@@ -39,6 +40,8 @@ prim_edit_check ell_pe.s "Set A" 8 A 8 0
|
||||
prim_edit_check ell_pe.s "Set B" 6 B 6 1
|
||||
prim_edit_check ell_pe.s "Set C" 4 C 4 2
|
||||
prim_edit_mouse_check ell_pe.s "Set A" A 8 0
|
||||
prim_edit_mouse_check ell_pe.s "Set B" B 6 1
|
||||
prim_edit_mouse_check ell_pe.s "Set C" C 4 2
|
||||
kill ell_pe.s
|
||||
|
||||
puts "--- RPC (right parabolic cylinder: H, r) ---"
|
||||
@@ -46,6 +49,7 @@ in rpc_pe.s rpc 0 0 0 0 0 10 0 5 0 3
|
||||
prim_edit_check rpc_pe.s "Set H" 20 H 20 2
|
||||
prim_edit_check rpc_pe.s "Set r" 6 r 6 -1
|
||||
prim_edit_mouse_check rpc_pe.s "Set H" H 20 2
|
||||
prim_edit_mouse_check rpc_pe.s "Set r" r 6 -1
|
||||
kill rpc_pe.s
|
||||
|
||||
puts "--- RHC (right hyperbolic cylinder: H, r, c) ---"
|
||||
@@ -54,6 +58,8 @@ prim_edit_check rhc_pe.s "Set H" 20 H 20 2
|
||||
prim_edit_check rhc_pe.s "Set r" 6 r 6 -1
|
||||
prim_edit_check rhc_pe.s "Set c" 3 c 3 -1
|
||||
prim_edit_mouse_check rhc_pe.s "Set r" r 6 -1
|
||||
prim_edit_mouse_check rhc_pe.s "Set H" H 20 2
|
||||
prim_edit_mouse_check rhc_pe.s "Set c" c 3 -1
|
||||
kill rhc_pe.s
|
||||
|
||||
puts "--- EPA (elliptical paraboloid: H, r_1, r_2) ---"
|
||||
@@ -62,6 +68,8 @@ prim_edit_check epa_pe.s "Set H" 20 H 20 2
|
||||
prim_edit_check epa_pe.s "Set A" 8 r_1 8 -1
|
||||
prim_edit_check epa_pe.s "Set B" 4 r_2 4 -1
|
||||
prim_edit_mouse_check epa_pe.s "Set H" H 20 2
|
||||
prim_edit_mouse_check epa_pe.s "Set A" r_1 8 -1
|
||||
prim_edit_mouse_check epa_pe.s "Set B" r_2 4 -1
|
||||
kill epa_pe.s
|
||||
|
||||
puts "--- EHY (elliptical hyperboloid: H, r_1, c) ---"
|
||||
@@ -70,6 +78,8 @@ prim_edit_check ehy_pe.s "Set H" 20 H 20 2
|
||||
prim_edit_check ehy_pe.s "Set A" 8 r_1 8 -1
|
||||
prim_edit_check ehy_pe.s "Set c" 3 c 3 -1
|
||||
prim_edit_mouse_check ehy_pe.s "Set A" r_1 8 -1
|
||||
prim_edit_mouse_check ehy_pe.s "Set H" H 20 2
|
||||
prim_edit_mouse_check ehy_pe.s "Set c" c 3 -1
|
||||
kill ehy_pe.s
|
||||
|
||||
puts "--- PART (particle: H, r_v, r_h) ---"
|
||||
@@ -78,6 +88,8 @@ prim_edit_check part_pe.s "Set H" 20 H 20 2
|
||||
prim_edit_check part_pe.s "Set v" 4 r_v 4 -1
|
||||
prim_edit_check part_pe.s "Set h" 3 r_h 3 -1
|
||||
prim_edit_mouse_check part_pe.s "Set v" r_v 4 -1
|
||||
prim_edit_mouse_check part_pe.s "Set H" H 20 2
|
||||
prim_edit_mouse_check part_pe.s "Set h" r_h 3 -1
|
||||
kill part_pe.s
|
||||
|
||||
puts "--- TGC (truncated general cone: H, A, B, C, D) ---"
|
||||
@@ -91,6 +103,10 @@ prim_edit_check tgc_pe.s "Set B" 3 B 3 1
|
||||
prim_edit_check tgc_pe.s "Set C" 3 C 3 0
|
||||
prim_edit_check tgc_pe.s "Set D" 4 D 4 1
|
||||
prim_edit_mouse_check tgc_pe.s "Set H" H 8 2
|
||||
prim_edit_mouse_check tgc_pe.s "Set A" A 4 0
|
||||
prim_edit_mouse_check tgc_pe.s "Set B" B 3 1
|
||||
prim_edit_mouse_check tgc_pe.s "Set C" C 3 0
|
||||
prim_edit_mouse_check tgc_pe.s "Set D" D 4 1
|
||||
kill tgc_pe.s
|
||||
|
||||
puts "--- ETO (elliptical torus: r, C, D) ---"
|
||||
@@ -100,6 +116,7 @@ prim_edit_check eto_pe.s "Set r" 8 r 8 -1
|
||||
prim_edit_check eto_pe.s "Set D" 1.5 r_d 1.5 -1
|
||||
prim_edit_mouse_check eto_pe.s "Set r" r 8 -1
|
||||
prim_edit_mouse_check eto_pe.s "Set C" C 5 0
|
||||
prim_edit_mouse_check eto_pe.s "Set D" r_d 1.5 -1
|
||||
kill eto_pe.s
|
||||
|
||||
puts "--- HYP (one-sheet hyperboloid: H, A, B, c) ---"
|
||||
@@ -111,6 +128,9 @@ prim_edit_check hyp_pe.s "Set A" 2 A 10 1
|
||||
prim_edit_check hyp_pe.s "Set B" 2 b 4 -1
|
||||
prim_edit_check hyp_pe.s "Set c" 0.5 bnr 0.25 -1
|
||||
prim_edit_mouse_check hyp_pe.s "Set H" H 20 2
|
||||
prim_edit_mouse_check hyp_pe.s "Set A" A 10 1
|
||||
prim_edit_mouse_check hyp_pe.s "Set B" b 4 -1
|
||||
prim_edit_mouse_check hyp_pe.s "Set c" bnr 0.25 -1
|
||||
kill hyp_pe.s
|
||||
|
||||
puts "--- SUPERELL (superellipsoid: A, B, C) ---"
|
||||
@@ -119,6 +139,8 @@ prim_edit_check superell_pe.s "Set A" 5 A 5 0
|
||||
prim_edit_check superell_pe.s "Set B" 4 B 4 1
|
||||
prim_edit_check superell_pe.s "Set C" 3 C 3 2
|
||||
prim_edit_mouse_check superell_pe.s "Set A" A 5 0
|
||||
prim_edit_mouse_check superell_pe.s "Set B" B 4 1
|
||||
prim_edit_mouse_check superell_pe.s "Set C" C 3 2
|
||||
kill superell_pe.s
|
||||
|
||||
puts "--- CLINE (cylinder line: H, R, plate thickness) ---"
|
||||
@@ -129,6 +151,7 @@ prim_edit_check cline_pe.s "Set R" 4 R 4 -1
|
||||
prim_edit_check cline_pe.s "Set plate thickness" 0.4 T 0.4 -1
|
||||
prim_edit_mouse_check cline_pe.s "Set H" H 8 2
|
||||
prim_edit_mouse_check cline_pe.s "Set R" R 4 -1
|
||||
prim_edit_mouse_check cline_pe.s "Set plate thickness" T 0.4 -1
|
||||
kill cline_pe.s
|
||||
|
||||
puts "--- NMG (non-manifold geometry: Pick Edge, Move Edge, Next EU) ---"
|
||||
|
||||
+7
-1
@@ -178,7 +178,13 @@ DEPREC=0
|
||||
workers=0
|
||||
pids=""
|
||||
dir="`dirname $RT`"
|
||||
for cmd in $dir/* ; do
|
||||
suffix=
|
||||
case "$RT" in
|
||||
*.exe) suffix=.exe ;;
|
||||
*) test -f "$RT.exe" && suffix=.exe ;;
|
||||
esac
|
||||
for cmd in "$dir"/*"$suffix" ; do
|
||||
test -f "$cmd" || continue
|
||||
test_usage "$cmd" &
|
||||
|
||||
pids="$pids $!"
|
||||
|
||||
@@ -93,6 +93,7 @@ if(BRLCAD_X11_PROVIDER_RESOLVED STREQUAL "XMIN" AND
|
||||
"PLOT3_ASC_BIN=$<TARGET_FILE:plot3-asc>"
|
||||
)
|
||||
mged_add_xmin_gui_fixture(help "mged;dm-tkswrast;m35.g")
|
||||
mged_add_xmin_gui_fixture(edit-interaction "mged;dm-tkswrast;m35.g")
|
||||
mged_add_xmin_gui_fixture(bot "mged;dm-tkswrast;m35.g")
|
||||
mged_add_xmin_gui_fixture(editors "mged;dm-tkswrast;m35.g")
|
||||
mged_add_xmin_gui_fixture(query-pattern "mged;dm-tkswrast;m35.g")
|
||||
@@ -212,18 +213,19 @@ cmakefiles(
|
||||
mged/mged_xmin_gui.sh
|
||||
mged/xmin_gui_bot.tcl
|
||||
mged/xmin_gui_controls.tcl
|
||||
mged/xmin_gui_edit.tcl
|
||||
mged/xmin_gui_edit_interaction.tcl
|
||||
mged/xmin_gui_editors.tcl
|
||||
mged/xmin_gui_help.tcl
|
||||
mged/xmin_gui_lod.tcl
|
||||
mged/xmin_gui_overlaps.tcl
|
||||
mged/xmin_gui_pipe_oracle.tcl
|
||||
mged/xmin_gui_query_pattern.tcl
|
||||
mged/xmin_gui_render_outputs.tcl
|
||||
mged/xmin_gui_search_exec.tcl
|
||||
mged/xmin_gui_shotvis.tcl
|
||||
mged/xmin_gui_test.tcl
|
||||
mged/xmin_gui_workflows.tcl
|
||||
mged/xmin_gui_edit.tcl
|
||||
mged/xmin_gui_pipe_oracle.tcl
|
||||
)
|
||||
|
||||
# Local Variables:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,12 +53,23 @@ endif(BRLCAD_ENABLE_TCL)
|
||||
# Archer plugins
|
||||
set(
|
||||
archer_utility_FILES
|
||||
plugins/Utility/attrGroupsDisplayUtilityP.tcl
|
||||
plugins/Utility/botUtilityP.tcl
|
||||
plugins/Utility/lodUtilityP.tcl
|
||||
plugins/Utility/README
|
||||
plugins/Utility/attrGroupsDisplayUtilityP.tcl
|
||||
plugins/Utility/attrGroupsDisplayUtilityP/AttrGroupsDisplayUtilityP.tcl
|
||||
plugins/Utility/botUtilityP.tcl
|
||||
plugins/Utility/botUtilityP/BotUtilityP.tcl
|
||||
plugins/Utility/lodUtilityP.tcl
|
||||
plugins/Utility/lodUtilityP/LODUtilityP.tcl
|
||||
)
|
||||
set(archer_wizard_FILES
|
||||
plugins/Wizards/humanwizard.tcl
|
||||
plugins/Wizards/humanwizard/HumanWizard.tcl
|
||||
plugins/Wizards/tankwizard.tcl
|
||||
plugins/Wizards/tankwizard/TankWizard.tcl
|
||||
plugins/Wizards/tankwizard/images/tank.png
|
||||
plugins/Wizards/tirewizard.tcl
|
||||
plugins/Wizards/tirewizard/TireWizard.tcl
|
||||
)
|
||||
set(archer_wizard_FILES plugins/Wizards/humanwizard.tcl plugins/Wizards/tankwizard.tcl plugins/Wizards/tirewizard.tcl)
|
||||
|
||||
if(BRLCAD_ENABLE_TCL)
|
||||
brlcad_adddata(archer_utility_FILES plugins/archer/Utility)
|
||||
@@ -77,6 +88,8 @@ endif(BRLCAD_ENABLE_TCL)
|
||||
set(
|
||||
archer_ignore_files
|
||||
CMakeLists.txt
|
||||
plugins/Commands/README
|
||||
plugins/Core/README
|
||||
${archer_utility_FILES}
|
||||
${archer_wizard_FILES}
|
||||
${archer_txt}
|
||||
|
||||
Vendored
+1
-1
@@ -752,7 +752,7 @@ tessellate_part(struct creo_conv_info *cinfo, ProMdl model, struct bu_vls **snam
|
||||
}
|
||||
|
||||
/* Check solidity */
|
||||
success = bg_trimesh_solid((int)vert_tree->curr_vert, (size_t)faces.size() / 3,
|
||||
success = !bg_trimesh_solid((int)vert_tree->curr_vert, (size_t)faces.size() / 3,
|
||||
vert_tree->the_array, &faces[0], NULL);
|
||||
|
||||
/* If it's not solid and we're testing solidity, keep trying... */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
brlcad_adddata(gtransfer.c sample_applications)
|
||||
|
||||
brlcad_addexec(brep-audit brep-audit.cpp "libbrep;libbv;librt;libbu")
|
||||
brlcad_addexec(ganalyze ganalyze.cpp "libanalyze;librt;libbu;libpkg;${LINENOISE_LIBRARIES}" NO_INSTALL)
|
||||
target_include_directories(ganalyze BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../libtermio)
|
||||
|
||||
@@ -20,6 +21,9 @@ add_subdirectory(gsh)
|
||||
add_subdirectory(tests)
|
||||
|
||||
cmakefiles(
|
||||
brep-audit-corpus.py
|
||||
brep-audit-compare.py
|
||||
brep-audit.cpp
|
||||
CMakeLists.txt
|
||||
)
|
||||
|
||||
|
||||
Executable
+489
@@ -0,0 +1,489 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Validate and summarize two BRep corpus audit result sets."""
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import statistics
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REALIZATION_FORMAT = "brlcad-brep-realization-audit-v1"
|
||||
MODES = ("wireframe", "shaded", "quality")
|
||||
EXCLUSION_MODES = {
|
||||
"wireframe_only": ("shaded", "quality"),
|
||||
"open_surface": ("quality",),
|
||||
}
|
||||
TOP_LEVEL_FIELDS = (
|
||||
"database", "object", "task_index", "status", "mode",
|
||||
"ratio_limits", "tessellation_tolerance", "memory_limit_mib",
|
||||
"input", "fast_options", "wire_options", "generators", "reference",
|
||||
"wireframe", "shaded", "quality", "images", "issues",
|
||||
)
|
||||
RESULT_FIELDS = (
|
||||
"return_code", "seconds", "vertices", "primitives", "commands",
|
||||
"peak_rss_bytes", "peak_working_bytes", "invalid_indices", "finite",
|
||||
"bbox_valid", "bbox_min", "bbox_max", "dimensions",
|
||||
"dimension_ratios", "limits", "solid_validation", "repair", "issues",
|
||||
)
|
||||
NONNEGATIVE_RESULT_FIELDS = (
|
||||
"seconds", "vertices", "primitives", "commands", "peak_rss_bytes",
|
||||
"peak_working_bytes", "invalid_indices",
|
||||
)
|
||||
PAIR_CONFIG_FIELDS = (
|
||||
("memory_limit_mib",),
|
||||
("ratio_limits",),
|
||||
("tessellation_tolerance",),
|
||||
("quality_options", "max_face_time_ms"),
|
||||
)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--baseline-name", default="main")
|
||||
parser.add_argument("--candidate-name", default="branch")
|
||||
parser.add_argument(
|
||||
"--baseline", nargs="+", type=Path, required=True,
|
||||
help="baseline JSONL files in chronological escalation order",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--candidate", nargs="+", type=Path, required=True,
|
||||
help="candidate JSONL files in chronological escalation order",
|
||||
)
|
||||
parser.add_argument("--output", type=Path)
|
||||
parser.add_argument(
|
||||
"--exclusions", type=Path,
|
||||
help="shared eligibility JSONL: database, object, reason, evidence",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-incomplete", action="store_true",
|
||||
help="report unequal task sets without returning an error",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def field(record, path):
|
||||
value = record
|
||||
for component in path:
|
||||
if not isinstance(value, dict) or component not in value:
|
||||
return None
|
||||
value = value[component]
|
||||
return value
|
||||
|
||||
|
||||
def finite_nonnegative(value):
|
||||
return isinstance(value, (int, float)) and not isinstance(value, bool) \
|
||||
and math.isfinite(value) and value >= 0
|
||||
|
||||
|
||||
def validate_vector(value):
|
||||
return isinstance(value, list) and len(value) == 3 and \
|
||||
all(isinstance(component, (int, float)) and
|
||||
not isinstance(component, bool) and math.isfinite(component)
|
||||
for component in value)
|
||||
|
||||
|
||||
def expected_empty_shading(result):
|
||||
counts = [result.get(name) for name in (
|
||||
"requested_items", "completed_items", "failed_items",
|
||||
"skipped_items", "tolerance_skipped_items",
|
||||
)]
|
||||
if not all(isinstance(count, int) and not isinstance(count, bool) and
|
||||
count >= 0 for count in counts):
|
||||
return False
|
||||
requested, completed, failed, skipped, tolerance_skipped = counts
|
||||
return result.get("return_code") == 0 and failed == 0 and \
|
||||
requested == completed == skipped + tolerance_skipped and \
|
||||
result.get("vertices") == 0 and result.get("primitives") == 0
|
||||
|
||||
|
||||
def validate_realization(record):
|
||||
errors = []
|
||||
for name in TOP_LEVEL_FIELDS:
|
||||
if name not in record:
|
||||
errors.append("missing top-level field '{}'".format(name))
|
||||
mode = record.get("mode")
|
||||
if mode not in MODES:
|
||||
errors.append("invalid mode {!r}".format(mode))
|
||||
return errors
|
||||
result = record.get(mode)
|
||||
if mode == "quality" and record.get("status") == "excluded" and \
|
||||
record.get("quality_exclusion_reason") == "wireframe_only" and \
|
||||
field(record, ("input", "loaded")) is True and \
|
||||
field(record, ("input", "wireframe_only")) is True and \
|
||||
result is None:
|
||||
if record.get("issues") != []:
|
||||
errors.append("wireframe-only quality exclusion contains issues")
|
||||
return errors
|
||||
if not isinstance(result, dict):
|
||||
errors.append("mode result '{}' is not an object".format(mode))
|
||||
return errors
|
||||
for name in RESULT_FIELDS:
|
||||
if name not in result:
|
||||
errors.append("missing {}.{}".format(mode, name))
|
||||
for name in NONNEGATIVE_RESULT_FIELDS:
|
||||
value = result.get(name)
|
||||
if not finite_nonnegative(value):
|
||||
errors.append("invalid {}.{} value {!r}".format(
|
||||
mode, name, value
|
||||
))
|
||||
if not isinstance(result.get("return_code"), int):
|
||||
errors.append("invalid {}.return_code".format(mode))
|
||||
for name in ("finite", "bbox_valid"):
|
||||
if not isinstance(result.get(name), bool):
|
||||
errors.append("invalid {}.{}".format(mode, name))
|
||||
if result.get("bbox_valid"):
|
||||
for name in ("bbox_min", "bbox_max", "dimensions"):
|
||||
if not validate_vector(result.get(name)):
|
||||
errors.append("invalid {}.{}".format(mode, name))
|
||||
if not isinstance(result.get("issues"), list):
|
||||
errors.append("invalid {}.issues".format(mode))
|
||||
if not isinstance(record.get("issues"), list):
|
||||
errors.append("invalid top-level issues")
|
||||
if record.get("status") == "ok":
|
||||
empty_shading = mode == "shaded" and expected_empty_shading(result)
|
||||
if result.get("issues") or record.get("issues"):
|
||||
errors.append("ok record contains issues")
|
||||
if not result.get("bbox_valid") and not empty_shading:
|
||||
errors.append("ok record has no bounding box")
|
||||
if (result.get("vertices", 0) == 0 or result.get("primitives", 0) == 0) \
|
||||
and not empty_shading:
|
||||
errors.append("ok record has empty geometry")
|
||||
if mode == "quality":
|
||||
solid = result.get("solid_validation")
|
||||
if not isinstance(solid, dict) or not solid.get("checked") or \
|
||||
not solid.get("solid"):
|
||||
errors.append("ok quality record lacks solid validation")
|
||||
return errors
|
||||
|
||||
|
||||
def file_digest(path):
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as source:
|
||||
for chunk in iter(lambda: source.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def load_exclusions(path):
|
||||
"""Load documented input exclusions, independently of meshing outcomes."""
|
||||
exclusions = {}
|
||||
if path is None:
|
||||
return exclusions
|
||||
with path.open(encoding="utf-8") as source:
|
||||
for line_number, line in enumerate(source, 1):
|
||||
try:
|
||||
row = json.loads(line)
|
||||
if not isinstance(row, dict) or not all(
|
||||
isinstance(row.get(name), str) and row[name]
|
||||
for name in ("database", "object", "reason")) or \
|
||||
row["reason"] not in EXCLUSION_MODES or \
|
||||
not isinstance(row.get("evidence"), dict) or \
|
||||
not row["evidence"]:
|
||||
raise ValueError("expected object identity, supported reason, and evidence")
|
||||
for mode in EXCLUSION_MODES[row["reason"]]:
|
||||
key = (row["database"], row["object"], mode)
|
||||
if key in exclusions:
|
||||
raise ValueError("duplicate exclusion for " + "/".join(key))
|
||||
exclusions[key] = row
|
||||
except ValueError as error:
|
||||
raise ValueError("{}:{}: {}".format(path, line_number, error)) from error
|
||||
return exclusions
|
||||
|
||||
|
||||
def shared_exclusions(baseline, candidate, declared=None):
|
||||
exclusions = dict(declared or {})
|
||||
for records in (baseline, candidate):
|
||||
for key, record in records.items():
|
||||
# A positive face-count classification transfers across revisions;
|
||||
# failed validity or solid checks alone do not establish intent.
|
||||
if record.get("format") != REALIZATION_FORMAT or \
|
||||
key[2] != "quality" or \
|
||||
field(record, ("input", "loaded")) is not True or \
|
||||
field(record, ("input", "wireframe_only")) is not True:
|
||||
continue
|
||||
row = {
|
||||
"database": key[0], "object": key[1],
|
||||
"reason": "wireframe_only", "evidence": {"input": record["input"]},
|
||||
}
|
||||
for mode in EXCLUSION_MODES["wireframe_only"]:
|
||||
exclusions.setdefault((key[0], key[1], mode), row)
|
||||
return exclusions
|
||||
|
||||
|
||||
def comparison_status(key, record, exclusions):
|
||||
return "excluded" if key in exclusions else record.get("status", "unknown")
|
||||
|
||||
|
||||
def load_records(paths):
|
||||
records = {}
|
||||
errors = []
|
||||
inputs = []
|
||||
for path in paths:
|
||||
inputs.append((path, file_digest(path)))
|
||||
with path.open("r", encoding="utf-8", errors="replace") as source:
|
||||
for line_number, line in enumerate(source, 1):
|
||||
try:
|
||||
record = json.loads(line)
|
||||
except json.JSONDecodeError as error:
|
||||
errors.append("{}:{}: invalid JSON: {}".format(
|
||||
path, line_number, error
|
||||
))
|
||||
continue
|
||||
database = record.get("database")
|
||||
object_name = record.get("object")
|
||||
mode = record.get("mode")
|
||||
if not database or not object_name or mode not in MODES:
|
||||
continue
|
||||
key = (database, object_name, mode)
|
||||
if record.get("format") == REALIZATION_FORMAT:
|
||||
for error in validate_realization(record):
|
||||
errors.append("{}:{}: {}: {}".format(
|
||||
path, line_number, "/".join(key), error
|
||||
))
|
||||
elif record.get("status") in (None, "ok"):
|
||||
errors.append("{}:{}: unrecognized result format".format(
|
||||
path, line_number
|
||||
))
|
||||
records[key] = record
|
||||
return records, errors, inputs
|
||||
|
||||
|
||||
def percentile(values, fraction):
|
||||
if not values:
|
||||
return None
|
||||
ordered = sorted(values)
|
||||
index = math.ceil(fraction * len(ordered)) - 1
|
||||
return ordered[max(0, min(index, len(ordered) - 1))]
|
||||
|
||||
|
||||
def fmt_number(value):
|
||||
if value is None:
|
||||
return "-"
|
||||
if abs(value) >= 1000:
|
||||
return "{:.0f}".format(value)
|
||||
return "{:.6g}".format(value)
|
||||
|
||||
|
||||
def timing_values(records, mode, exclusions):
|
||||
values = []
|
||||
for key, record in records.items():
|
||||
if record.get("mode") != mode or \
|
||||
record.get("format") != REALIZATION_FORMAT or \
|
||||
comparison_status(key, record, exclusions) == "excluded":
|
||||
continue
|
||||
seconds = field(record, (mode, "seconds"))
|
||||
if finite_nonnegative(seconds):
|
||||
values.append(seconds)
|
||||
return values
|
||||
|
||||
|
||||
def status_counts(records, mode, exclusions):
|
||||
counts = collections.Counter()
|
||||
for key, record in records.items():
|
||||
if record.get("mode") == mode:
|
||||
counts[comparison_status(key, record, exclusions)] += 1
|
||||
return counts
|
||||
|
||||
|
||||
def append_outcome_counts(lines, title, revisions, exclusions):
|
||||
lines.extend(("", title, "-" * len(title)))
|
||||
lines.append("revision mode total eligible ok fail timeout excluded other")
|
||||
for name, records in revisions:
|
||||
for mode in MODES:
|
||||
counts = status_counts(records, mode, exclusions)
|
||||
known = sum(counts[status] for status in
|
||||
("ok", "fail", "timeout", "excluded"))
|
||||
total = sum(counts.values())
|
||||
lines.append(
|
||||
"{:<14} {:<10} {:>5} {:>8} {:>7} {:>7} {:>8} {:>8} {:>5}".format(
|
||||
name, mode, total, total - counts["excluded"], counts["ok"],
|
||||
counts["fail"], counts["timeout"], counts["excluded"], total - known
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def failure_category(record):
|
||||
status = record.get("status", "unknown")
|
||||
if status in ("ok", "excluded"):
|
||||
return status
|
||||
mode = record.get("mode")
|
||||
mode_issues = field(record, (mode, "issues")) or []
|
||||
top_issues = record.get("issues") or []
|
||||
if mode_issues:
|
||||
return mode_issues[0]
|
||||
if top_issues:
|
||||
return top_issues[0]
|
||||
return status
|
||||
|
||||
|
||||
def nested_equal(left, right, path):
|
||||
return field(left, path) == field(right, path)
|
||||
|
||||
|
||||
def comparison_report(args, baseline, candidate, baseline_inputs,
|
||||
candidate_inputs, schema_errors, declared_exclusions=None):
|
||||
baseline_keys = set(baseline)
|
||||
candidate_keys = set(candidate)
|
||||
common_keys = baseline_keys & candidate_keys
|
||||
baseline_only = sorted(baseline_keys - candidate_keys)
|
||||
candidate_only = sorted(candidate_keys - baseline_keys)
|
||||
exclusions = shared_exclusions(baseline, candidate, declared_exclusions)
|
||||
revisions = ((args.baseline_name, baseline), (args.candidate_name, candidate))
|
||||
config_errors = []
|
||||
for key in sorted(common_keys):
|
||||
left = baseline[key]
|
||||
right = candidate[key]
|
||||
if left.get("format") != REALIZATION_FORMAT or \
|
||||
right.get("format") != REALIZATION_FORMAT:
|
||||
continue
|
||||
for path in PAIR_CONFIG_FIELDS:
|
||||
if not nested_equal(left, right, path):
|
||||
config_errors.append(
|
||||
"{}: {} differs: {!r} versus {!r}".format(
|
||||
"/".join(key), ".".join(path), field(left, path),
|
||||
field(right, path)
|
||||
)
|
||||
)
|
||||
|
||||
lines = []
|
||||
lines.append("BRep audit comparison")
|
||||
lines.append("=====================")
|
||||
lines.append("")
|
||||
lines.append("Inputs")
|
||||
lines.append("------")
|
||||
for name, inputs in (
|
||||
(args.baseline_name, baseline_inputs),
|
||||
(args.candidate_name, candidate_inputs)):
|
||||
for path, digest in inputs:
|
||||
lines.append("{} {} {}".format(name, digest, path))
|
||||
lines.append("")
|
||||
lines.append("Validation")
|
||||
lines.append("----------")
|
||||
lines.append("schema errors: {}".format(len(schema_errors)))
|
||||
lines.append("configuration mismatches: {}".format(len(config_errors)))
|
||||
lines.append("matched tasks: {}".format(len(common_keys)))
|
||||
lines.append("{}-only tasks: {}".format(
|
||||
args.baseline_name, len(baseline_only)
|
||||
))
|
||||
lines.append("{}-only tasks: {}".format(
|
||||
args.candidate_name, len(candidate_only)
|
||||
))
|
||||
lines.extend(("", "Shared eligibility", "------------------"))
|
||||
lines.append("No-face inputs are excluded from shaded and quality comparisons; "
|
||||
"documented open surfaces from quality only.")
|
||||
lines.append("Exclusions apply to both revisions, including successes, failures, "
|
||||
"timeouts, and errors. Wireframe remains eligible.")
|
||||
lines.append("Invalidity or an open boundary alone does not exclude a repair "
|
||||
"candidate. Raw records remain unchanged.")
|
||||
if getattr(args, "exclusions", None):
|
||||
lines.append("Exclusion evidence: {} {}".format(file_digest(args.exclusions), args.exclusions))
|
||||
exclusion_counts = collections.Counter(
|
||||
(key[2], exclusions[key]["reason"])
|
||||
for key in (baseline_keys | candidate_keys) & set(exclusions)
|
||||
)
|
||||
for (mode, reason), count in sorted(exclusion_counts.items()):
|
||||
lines.append("{} {}: {} shared tasks".format(mode, reason, count))
|
||||
append_outcome_counts(lines, "Outcome counts (shared eligibility)", revisions, exclusions)
|
||||
append_outcome_counts(lines, "Raw outcome counts (recorded)", revisions, {})
|
||||
lines.append("")
|
||||
lines.append("Generator timing (seconds, eligible completed process records)")
|
||||
lines.append("--------------------------------------------------------------")
|
||||
lines.append("revision mode n total median p95 max")
|
||||
for name, records in revisions:
|
||||
for mode in MODES:
|
||||
values = timing_values(records, mode, exclusions)
|
||||
lines.append(
|
||||
"{:<14} {:<10} {:>5} {:>11} {:>11} {:>11} {:>11}".format(
|
||||
name, mode, len(values), fmt_number(sum(values)),
|
||||
fmt_number(statistics.median(values) if values else None),
|
||||
fmt_number(percentile(values, 0.95)),
|
||||
fmt_number(max(values) if values else None),
|
||||
)
|
||||
)
|
||||
lines.append("")
|
||||
lines.append("Matched successful timing ratios")
|
||||
lines.append("---------------------------------")
|
||||
lines.append("mode n candidate/baseline median p95")
|
||||
for mode in MODES:
|
||||
ratios = []
|
||||
for key in common_keys:
|
||||
if key[2] != mode or key in exclusions:
|
||||
continue
|
||||
left = baseline[key]
|
||||
right = candidate[key]
|
||||
if left.get("status") != "ok" or right.get("status") != "ok":
|
||||
continue
|
||||
left_seconds = field(left, (mode, "seconds"))
|
||||
right_seconds = field(right, (mode, "seconds"))
|
||||
if finite_nonnegative(left_seconds) and left_seconds > 0 and \
|
||||
finite_nonnegative(right_seconds):
|
||||
ratios.append(right_seconds / left_seconds)
|
||||
lines.append("{:<10} {:>5} {:>26} {:>11}".format(
|
||||
mode, len(ratios),
|
||||
fmt_number(statistics.median(ratios) if ratios else None),
|
||||
fmt_number(percentile(ratios, 0.95)),
|
||||
))
|
||||
lines.append("")
|
||||
lines.append("Failure categories")
|
||||
lines.append("------------------")
|
||||
for name, records in revisions:
|
||||
counts = collections.Counter(
|
||||
(record.get("mode"), failure_category(record))
|
||||
for key, record in records.items()
|
||||
if comparison_status(key, record, exclusions) not in ("ok", "excluded")
|
||||
)
|
||||
if not counts:
|
||||
lines.append("{}: none".format(name))
|
||||
continue
|
||||
for (mode, category), count in sorted(counts.items()):
|
||||
lines.append("{} {:<10} {:>5} {}".format(
|
||||
name, mode, count, category
|
||||
))
|
||||
if schema_errors or config_errors or baseline_only or candidate_only:
|
||||
lines.append("")
|
||||
lines.append("Validation details")
|
||||
lines.append("------------------")
|
||||
lines.extend(schema_errors)
|
||||
lines.extend(config_errors)
|
||||
for key in baseline_only:
|
||||
lines.append("{} only: {}".format(
|
||||
args.baseline_name, "/".join(key)
|
||||
))
|
||||
for key in candidate_only:
|
||||
lines.append("{} only: {}".format(
|
||||
args.candidate_name, "/".join(key)
|
||||
))
|
||||
return "\n".join(lines) + "\n", bool(
|
||||
schema_errors or config_errors or baseline_only or candidate_only
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
try:
|
||||
exclusions = load_exclusions(args.exclusions)
|
||||
except (OSError, ValueError) as error:
|
||||
sys.stderr.write("Invalid exclusion manifest: {}\n".format(error))
|
||||
return 1
|
||||
baseline, baseline_errors, baseline_inputs = load_records(args.baseline)
|
||||
candidate, candidate_errors, candidate_inputs = load_records(
|
||||
args.candidate
|
||||
)
|
||||
report, invalid = comparison_report(
|
||||
args, baseline, candidate, baseline_inputs, candidate_inputs,
|
||||
baseline_errors + candidate_errors, exclusions,
|
||||
)
|
||||
if args.output:
|
||||
args.output.write_text(report, encoding="utf-8")
|
||||
else:
|
||||
sys.stdout.write(report)
|
||||
return 1 if invalid and not args.allow_incomplete else 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Executable
+1200
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,9 @@
|
||||
|
||||
// OpenCV header files
|
||||
#include <opencv2/opencv.hpp>
|
||||
#if CV_VERSION_MAJOR >= 5
|
||||
#include <opencv2/geometry.hpp>
|
||||
#endif
|
||||
#include <opencv2/core/core.hpp>
|
||||
#include "opencv2/highgui/highgui.hpp"
|
||||
|
||||
|
||||
@@ -10,7 +10,12 @@ if(TARGET slow-bigdb_5gb)
|
||||
set_property(TEST slow-bigdb_5gb PROPERTY SKIP_RETURN_CODE 123)
|
||||
endif(TARGET slow-bigdb_5gb)
|
||||
|
||||
cmakefiles(CMakeLists.txt)
|
||||
find_package(Python3 QUIET COMPONENTS Interpreter)
|
||||
if(Python3_Interpreter_FOUND)
|
||||
brlcad_add_test(NAME brep_audit_corpus COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/brep_audit_corpus.py")
|
||||
endif()
|
||||
|
||||
cmakefiles(CMakeLists.txt brep_audit_corpus.py)
|
||||
|
||||
# Local Variables:
|
||||
# tab-width: 8
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
# BRL-CAD
|
||||
# Copyright (c) 2026 United States Government as represented by
|
||||
# the U.S. Army Research Laboratory.
|
||||
# Distributed under the terms of the GNU Lesser General Public License
|
||||
# (LGPL), version 2.1.
|
||||
|
||||
"""Exercise bounded corpus retries with a deterministic audit subprocess."""
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def fixture():
|
||||
batch = "--batch" in sys.argv
|
||||
database = Path(sys.argv[-1] if batch else sys.argv[-2])
|
||||
scenario = json.loads(database.read_text())
|
||||
start = int(sys.argv[sys.argv.index("--batch-start") + 1]) if batch else 0
|
||||
with database.with_suffix(".calls").open("a") as calls:
|
||||
calls.write(json.dumps({"batch": batch, "start": start,
|
||||
"arguments": sys.argv[1:]}) + "\n")
|
||||
if not batch:
|
||||
previous_pid = database.with_suffix(".pid")
|
||||
if previous_pid.exists():
|
||||
try:
|
||||
os.kill(int(previous_pid.read_text()), 0)
|
||||
except ProcessLookupError:
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError("batch still running during isolated retry")
|
||||
objects = scenario["objects"]
|
||||
indices = range(start, len(objects)) if batch else [int(sys.argv[-1])]
|
||||
for index in indices:
|
||||
details = objects[index]
|
||||
record = {"format": "brlcad-brep-realization-audit-v1",
|
||||
"database": str(database), "object": str(index),
|
||||
"mode": "quality", "task_index": index if batch else -1,
|
||||
"status": "ok", "quality": {}}
|
||||
if batch or not details.get("recovers", True):
|
||||
record.update(details["result"])
|
||||
if batch:
|
||||
progress = dict(record, format="brlcad-brep-audit-progress-v1")
|
||||
print(json.dumps(progress), flush=True)
|
||||
should_wait = batch and index > start and details.get("retry", False)
|
||||
if should_wait:
|
||||
database.with_suffix(".pid").write_text(str(os.getpid()))
|
||||
print(json.dumps(record), flush=True)
|
||||
if should_wait:
|
||||
# The parent must stop this process before the isolated attempt.
|
||||
time.sleep(10)
|
||||
raise RuntimeError("parent did not stop the failed batch")
|
||||
if batch:
|
||||
print(json.dumps({"format": "brlcad-brep-audit-complete-v1",
|
||||
"task_count": len(objects)}), flush=True)
|
||||
|
||||
|
||||
@unittest.skipUnless(os.name == "posix", "executable subprocess fixture")
|
||||
class CorpusResourceRetry(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
source = Path(__file__).resolve().parent.parent / "brep-audit-corpus.py"
|
||||
spec = importlib.util.spec_from_file_location("brep_audit_corpus", source)
|
||||
cls.corpus = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(cls.corpus)
|
||||
|
||||
def check_retry(self, failure, recovers=True, previous=True, retry=True):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
audit = root / "audit"
|
||||
shutil.copyfile(__file__, audit)
|
||||
audit.chmod(0o755)
|
||||
objects = ([{"result": {}}] if previous else [])
|
||||
objects.extend([{"result": failure, "recovers": recovers,
|
||||
"retry": retry}, {"result": {}}])
|
||||
database = root / "fixture.g"
|
||||
database.write_text(json.dumps({"objects": objects}))
|
||||
with patch.object(sys, "argv", ["corpus", str(root),
|
||||
str(root / "results.jsonl"),
|
||||
"--modes", "quality", "--jobs", "1",
|
||||
"--object-timeout", "3"]):
|
||||
args = self.corpus.parse_args()
|
||||
records = []
|
||||
self.corpus.audit_database(
|
||||
str(audit), args, root, database, 0, records.append,
|
||||
tasks=[(str(i), "quality") for i in range(len(objects))],
|
||||
)
|
||||
self.assertEqual([r["task_index"] for r in records],
|
||||
list(range(len(objects))))
|
||||
self.assertEqual(records[-1]["status"], "ok")
|
||||
calls = [json.loads(line) for line in
|
||||
database.with_suffix(".calls").read_text().splitlines()]
|
||||
failed_index = int(previous)
|
||||
if previous and retry:
|
||||
self.assertEqual([c["batch"] for c in calls], [True, False, True])
|
||||
self.assertEqual(calls[-1]["start"], failed_index + 1)
|
||||
isolated = records[failed_index]
|
||||
self.assertEqual(isolated["status"], "ok" if recovers else "fail")
|
||||
self.assertEqual(isolated["batch_attempt"]["status"], "fail")
|
||||
self.assertIn("batch_fallback_reason", isolated)
|
||||
for option in ("--jobs", "--memory-limit-mib", "--max-time-ms"):
|
||||
values = [c["arguments"][c["arguments"].index(option) + 1]
|
||||
for c in calls]
|
||||
self.assertEqual(values, [values[0]] * len(values))
|
||||
else:
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertNotIn("batch_attempt", records[failed_index])
|
||||
self.assertEqual(records[failed_index]["status"], failure["status"])
|
||||
|
||||
def test_memory_retry_preserves_history_and_limits(self):
|
||||
self.check_retry({"status": "fail", "quality": {"limits": {"memory": True}}})
|
||||
|
||||
def test_repair_memory_history_triggers_retry(self):
|
||||
self.check_retry({"status": "fail", "quality": {"repair": {
|
||||
"resource_limits": self.corpus.REPAIR_LIMIT_MEMORY}}})
|
||||
|
||||
def test_load_failure_retries(self):
|
||||
self.check_retry({"status": "fail", "issues": ["database_internal_load_failed"]})
|
||||
|
||||
def test_fresh_process_failure_remains_a_failure(self):
|
||||
self.check_retry({"status": "fail", "quality": {"limits": {"memory": True}}},
|
||||
recovers=False)
|
||||
|
||||
def test_first_object_is_already_isolated(self):
|
||||
self.check_retry({"status": "fail", "quality": {"limits": {"memory": True}}},
|
||||
previous=False)
|
||||
|
||||
def test_geometry_failure_is_not_retried(self):
|
||||
self.check_retry({"status": "fail", "quality": {"issues": ["open_mesh"]}},
|
||||
retry=False)
|
||||
|
||||
def test_success_with_resource_history_is_not_retried(self):
|
||||
self.check_retry({"status": "ok", "quality": {"limits": {"memory": True}}},
|
||||
retry=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "--mode" in sys.argv:
|
||||
fixture()
|
||||
else:
|
||||
unittest.main()
|
||||
@@ -29,6 +29,7 @@ set(
|
||||
api.c
|
||||
centroid.c
|
||||
check_options.c
|
||||
contour.cpp
|
||||
density.cpp
|
||||
globals.c
|
||||
inside.c
|
||||
@@ -68,7 +69,7 @@ set(
|
||||
cmakefiles(${libanalyze_ignored_files})
|
||||
|
||||
# Note - libanalyze_deps is defined by ${BRLCAD_SOURCE_DIR}/src/source_dirs.cmake
|
||||
brlcad_addlib(libanalyze "${LIBANALYZE_SOURCES}" "" "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
brlcad_addlib(libanalyze "${LIBANALYZE_SOURCES}" "" "${CMAKE_CURRENT_BINARY_DIR};${EIGEN3_INCLUDE_DIR}"
|
||||
PUBLIC_LIBS ${libanalyze_deps}
|
||||
)
|
||||
set_target_properties(libanalyze PROPERTIES VERSION 20.0.1 SOVERSION 20)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+78
-908
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,6 @@
|
||||
brlcad_addexec(analyze_contour contour.cpp "libanalyze;libwdb;librt;libbg;libbu" TEST)
|
||||
brlcad_add_test(NAME analyze_contour COMMAND analyze_contour)
|
||||
|
||||
brlcad_addexec(analyze_raydiff raydiff.c "libanalyze;librt;libbu" TEST)
|
||||
brlcad_addexec(analyze_sp solid_partitions.c "libanalyze;librt;libbu" TEST)
|
||||
brlcad_addexec(analyze_nhit nhit.cpp "libanalyze;librt;libbu" TEST_USESDATA)
|
||||
@@ -13,6 +16,7 @@ brlcad_add_test(NAME analyze_densities_std COMMAND analyze_densities std)
|
||||
cmakefiles(
|
||||
CMakeLists.txt
|
||||
arbs.g
|
||||
contour.cpp
|
||||
raydiff.g
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
/* A N A L Y Z E _ C O N T O U R . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED.
|
||||
*/
|
||||
/** @file contour.cpp
|
||||
*
|
||||
* Basic topology and resource-limit tests for analyze_mdc().
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <set>
|
||||
#include <utility>
|
||||
|
||||
#include "bu/app.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "bg/trimesh.h"
|
||||
#include "analyze/contour.h"
|
||||
#include "analyze/polygonize.h"
|
||||
#include "raytrace.h"
|
||||
#include "wdb.h"
|
||||
|
||||
static constexpr double METRIC_RELATIVE_TOLERANCE = 0.10;
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
|
||||
static void
|
||||
expect(bool condition, const char *message)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
std::fprintf(stderr, "FAIL: %s\n", message);
|
||||
failures++;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
expect_metric(double actual, double expected, const char *object,
|
||||
const char *metric)
|
||||
{
|
||||
double relative_error = std::fabs(actual - expected) / expected;
|
||||
if (actual >= 0.0 && relative_error <= METRIC_RELATIVE_TOLERANCE)
|
||||
return;
|
||||
|
||||
std::fprintf(stderr,
|
||||
"FAIL: %s %s is %.17g, expected %.17g (relative error %.2f%%)\n",
|
||||
object, metric, actual, expected, 100.0 * relative_error);
|
||||
failures++;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
check_mesh(struct db_i *dbip, const char *object, int expected_components = 1,
|
||||
double expected_area = 0.0, double expected_volume = 0.0,
|
||||
size_t minimum_faces = 4, int expected_euler = INT_MAX)
|
||||
{
|
||||
int *faces = NULL;
|
||||
point_t *vertices = NULL;
|
||||
size_t face_count = 0;
|
||||
size_t vertex_count = 0;
|
||||
struct analyze_mdc_params params = ANALYZE_MDC_PARAMS_DEFAULT;
|
||||
params.min_depth = 4;
|
||||
params.max_depth = 8;
|
||||
params.max_rays = 0;
|
||||
params.minimum_free_mem = 0;
|
||||
params.max_time = 0;
|
||||
params.verbosity = 0;
|
||||
|
||||
enum analyze_mdc_status status = analyze_mdc(&faces, &face_count,
|
||||
&vertices, &vertex_count, object, dbip, ¶ms);
|
||||
expect(status == ANALYZE_MDC_OK, object);
|
||||
if (status == ANALYZE_MDC_OK) {
|
||||
expect(vertex_count >= 4, "contour has vertices");
|
||||
expect(face_count >= minimum_faces, "contour has expected resolution");
|
||||
expect(bg_trimesh_solid2(static_cast<int>(vertex_count),
|
||||
static_cast<int>(face_count),
|
||||
reinterpret_cast<fastf_t *>(vertices), faces, NULL) == 0,
|
||||
"contour is a closed oriented manifold");
|
||||
if (expected_euler != INT_MAX) {
|
||||
std::set<std::pair<int, int>> edges;
|
||||
for (size_t face = 0; face < face_count; face++) {
|
||||
for (size_t corner = 0; corner < 3; corner++) {
|
||||
int a = faces[3 * face + corner];
|
||||
int b = faces[3 * face + (corner + 1) % 3];
|
||||
edges.insert(std::minmax(a, b));
|
||||
}
|
||||
}
|
||||
long long euler = static_cast<long long>(vertex_count) -
|
||||
static_cast<long long>(edges.size()) +
|
||||
static_cast<long long>(face_count);
|
||||
expect(euler == expected_euler,
|
||||
"contour has the expected Euler characteristic");
|
||||
}
|
||||
if (expected_area > 0.0)
|
||||
expect_metric(bg_trimesh_area(faces, face_count, vertices,
|
||||
vertex_count), expected_area, object, "surface area");
|
||||
if (expected_volume > 0.0)
|
||||
expect_metric(bg_trimesh_volume(faces, face_count, vertices,
|
||||
vertex_count), expected_volume, object, "volume");
|
||||
|
||||
int *face_indices = NULL;
|
||||
int *component_offsets = NULL;
|
||||
int components = bg_trimesh_separate(&face_indices,
|
||||
&component_offsets, faces, static_cast<int>(face_count));
|
||||
expect(components == expected_components,
|
||||
"contour has the expected connected components");
|
||||
if (face_indices)
|
||||
bu_free(face_indices, "MDC test component faces");
|
||||
if (component_offsets)
|
||||
bu_free(component_offsets, "MDC test component offsets");
|
||||
}
|
||||
|
||||
if (faces)
|
||||
bu_free(faces, "MDC test faces");
|
||||
if (vertices)
|
||||
bu_free(vertices, "MDC test vertices");
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
make_region(struct rt_wdb *wdbp, const char *region, const char *solid)
|
||||
{
|
||||
struct wmember member;
|
||||
BU_LIST_INIT(&member.l);
|
||||
if (!mk_addmember(solid, &member.l, NULL, WMOP_UNION))
|
||||
return false;
|
||||
return mk_lcomb(wdbp, region, &member, 1, NULL, NULL, NULL, 0) == 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int UNUSED(argc), const char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
|
||||
struct db_i *dbip = db_create_inmem();
|
||||
expect(dbip != NULL, "create in-memory database");
|
||||
if (!dbip)
|
||||
return 1;
|
||||
|
||||
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_INMEM);
|
||||
expect(wdbp != NULL, "open in-memory database");
|
||||
if (!wdbp) {
|
||||
db_close(dbip);
|
||||
return 1;
|
||||
}
|
||||
|
||||
constexpr double sphere_radius = 10.0;
|
||||
constexpr double torus_major_radius = 10.0;
|
||||
constexpr double torus_minor_radius = 2.5;
|
||||
constexpr double slender_torus_major_radius = 100.0;
|
||||
constexpr double slender_torus_minor_radius = 2.0;
|
||||
constexpr double lower_radius = 1.5;
|
||||
constexpr double upper_radius = 1.0;
|
||||
|
||||
point_t center = VINIT_ZERO;
|
||||
point_t minimum = {-8.0, -6.0, -4.0};
|
||||
point_t maximum = {8.0, 6.0, 4.0};
|
||||
expect(mk_sph(wdbp, "sphere.s", center, sphere_radius) == 0,
|
||||
"create test sphere");
|
||||
expect(mk_rpp(wdbp, "box.s", minimum, maximum) == 0,
|
||||
"create test box");
|
||||
point_t thin_minimum = {-5.0, -2.5, -0.05};
|
||||
point_t thin_maximum = {5.0, 2.5, 0.05};
|
||||
expect(mk_rpp(wdbp, "thin.s", thin_minimum, thin_maximum) == 0,
|
||||
"create thin-wall test box");
|
||||
|
||||
point_t tunnel_minimum = {-10.0, -10.0, -10.0};
|
||||
point_t tunnel_maximum = {10.0, 10.0, 10.0};
|
||||
point_t tunnel_base = {0.0, 0.0, -11.0};
|
||||
vect_t tunnel_height = {0.0, 0.0, 22.0};
|
||||
expect(mk_rpp(wdbp, "tunnel_outer.s", tunnel_minimum,
|
||||
tunnel_maximum) == 0, "create tunnel test outer box");
|
||||
expect(mk_rcc(wdbp, "tunnel_void.s", tunnel_base, tunnel_height,
|
||||
1.0) == 0, "create tunnel test void");
|
||||
struct wmember tunnel_members;
|
||||
BU_LIST_INIT(&tunnel_members.l);
|
||||
expect(mk_addmember("tunnel_outer.s", &tunnel_members.l, NULL,
|
||||
WMOP_UNION) != NULL, "add tunnel outer box");
|
||||
expect(mk_addmember("tunnel_void.s", &tunnel_members.l, NULL,
|
||||
WMOP_SUBTRACT) != NULL, "subtract tunnel void");
|
||||
expect(mk_lcomb(wdbp, "tunnel.r", &tunnel_members, 1, NULL, NULL,
|
||||
NULL, 0) == 0, "create tunnel test region");
|
||||
vect_t normal = {0.0, 0.0, 1.0};
|
||||
expect(mk_tor(wdbp, "torus.s", center, normal, torus_major_radius,
|
||||
torus_minor_radius) == 0,
|
||||
"create test torus");
|
||||
expect(mk_tor(wdbp, "slender_torus.s", center, normal,
|
||||
slender_torus_major_radius, slender_torus_minor_radius) == 0,
|
||||
"create high-aspect torus");
|
||||
|
||||
point_t tgc_base = {-4.0, -3.0, -6.0};
|
||||
vect_t tgc_height = {2.0, 1.0, 12.0};
|
||||
vect_t tgc_a = {5.0, 0.0, 0.0};
|
||||
vect_t tgc_b = {0.0, 3.0, 0.0};
|
||||
vect_t tgc_c = {2.5, 0.0, 0.0};
|
||||
vect_t tgc_d = {0.0, 1.5, 0.0};
|
||||
expect(mk_tgc(wdbp, "tgc.s", tgc_base, tgc_height, tgc_a, tgc_b,
|
||||
tgc_c, tgc_d) == 0, "create test TGC");
|
||||
|
||||
point_t concave_arb[8] = {
|
||||
{500.0, -500.0, -500.0},
|
||||
{500.0, 500.0, -500.0},
|
||||
{500.0, -169.922, -304.688},
|
||||
{500.0, -500.0, 500.0},
|
||||
{400.0, -500.0, -500.0},
|
||||
{400.0, 500.0, -500.0},
|
||||
{400.0, -169.922, -304.688},
|
||||
{400.0, -500.0, 500.0}
|
||||
};
|
||||
expect(mk_arb8(wdbp, "concave.s",
|
||||
reinterpret_cast<const fastf_t *>(concave_arb)) == 0,
|
||||
"create sharp concave ARB");
|
||||
|
||||
point_t lower_center = {-8.0, -5.0, -3.0};
|
||||
point_t upper_center = {9.0, 4.0, 6.0};
|
||||
expect(mk_sph(wdbp, "lower.s", lower_center, lower_radius) == 0,
|
||||
"create lower disconnected sphere");
|
||||
expect(mk_sph(wdbp, "upper.s", upper_center, upper_radius) == 0,
|
||||
"create upper disconnected sphere");
|
||||
struct wmember members;
|
||||
BU_LIST_INIT(&members.l);
|
||||
expect(mk_addmember("lower.s", &members.l, NULL, WMOP_UNION) != NULL,
|
||||
"add lower disconnected sphere");
|
||||
expect(mk_addmember("upper.s", &members.l, NULL, WMOP_UNION) != NULL,
|
||||
"add upper disconnected sphere");
|
||||
expect(mk_lcomb(wdbp, "disconnected.r", &members, 1, NULL, NULL, NULL,
|
||||
0) == 0, "create disconnected region");
|
||||
|
||||
constexpr double overlap_radius = 5.0;
|
||||
constexpr double overlap_offset = 3.0;
|
||||
point_t overlap_left_center = {-overlap_offset, 0.0, 0.0};
|
||||
point_t overlap_right_center = {overlap_offset, 0.0, 0.0};
|
||||
expect(mk_sph(wdbp, "overlap_left.s", overlap_left_center,
|
||||
overlap_radius) == 0, "create left overlapping sphere");
|
||||
expect(mk_sph(wdbp, "overlap_right.s", overlap_right_center,
|
||||
overlap_radius) == 0, "create right overlapping sphere");
|
||||
expect(make_region(wdbp, "overlap_left.r", "overlap_left.s"),
|
||||
"create left overlapping region");
|
||||
expect(make_region(wdbp, "overlap_right.r", "overlap_right.s"),
|
||||
"create right overlapping region");
|
||||
struct wmember overlap_members;
|
||||
BU_LIST_INIT(&overlap_members.l);
|
||||
expect(mk_addmember("overlap_left.r", &overlap_members.l, NULL,
|
||||
WMOP_UNION) != NULL, "add left overlapping region");
|
||||
expect(mk_addmember("overlap_right.r", &overlap_members.l, NULL,
|
||||
WMOP_UNION) != NULL, "add right overlapping region");
|
||||
expect(mk_lcomb(wdbp, "overlap.c", &overlap_members, 0, NULL, NULL,
|
||||
NULL, 0) == 0, "create overlapping-region combination");
|
||||
|
||||
vect_t box_size;
|
||||
VSUB2(box_size, maximum, minimum);
|
||||
|
||||
const double sphere_area = 4.0 * M_PI * sphere_radius * sphere_radius;
|
||||
const double sphere_volume = 4.0 * M_PI * sphere_radius * sphere_radius *
|
||||
sphere_radius / 3.0;
|
||||
const double box_area = 2.0 * (box_size[X] * box_size[Y] +
|
||||
box_size[X] * box_size[Z] + box_size[Y] * box_size[Z]);
|
||||
const double box_volume = box_size[X] * box_size[Y] * box_size[Z];
|
||||
vect_t thin_size;
|
||||
VSUB2(thin_size, thin_maximum, thin_minimum);
|
||||
const double thin_area = 2.0 * (thin_size[X] * thin_size[Y] +
|
||||
thin_size[X] * thin_size[Z] + thin_size[Y] * thin_size[Z]);
|
||||
const double thin_volume =
|
||||
thin_size[X] * thin_size[Y] * thin_size[Z];
|
||||
const double torus_area = 4.0 * M_PI * M_PI * torus_major_radius *
|
||||
torus_minor_radius;
|
||||
const double torus_volume = 2.0 * M_PI * M_PI * torus_major_radius *
|
||||
torus_minor_radius * torus_minor_radius;
|
||||
const double slender_torus_area = 4.0 * M_PI * M_PI *
|
||||
slender_torus_major_radius * slender_torus_minor_radius;
|
||||
const double disconnected_area = 4.0 * M_PI *
|
||||
(lower_radius * lower_radius + upper_radius * upper_radius);
|
||||
const double disconnected_volume = 4.0 * M_PI *
|
||||
(lower_radius * lower_radius * lower_radius +
|
||||
upper_radius * upper_radius * upper_radius) / 3.0;
|
||||
const double overlap_distance = 2.0 * overlap_offset;
|
||||
const double overlap_cap_height = overlap_radius -
|
||||
0.5 * overlap_distance;
|
||||
const double overlap_intersection_volume = M_PI *
|
||||
(4.0 * overlap_radius + overlap_distance) *
|
||||
std::pow(2.0 * overlap_radius - overlap_distance, 2.0) / 12.0;
|
||||
const double overlap_area = 8.0 * M_PI * overlap_radius *
|
||||
overlap_radius - 4.0 * M_PI * overlap_radius * overlap_cap_height;
|
||||
const double overlap_volume = 8.0 * M_PI * overlap_radius *
|
||||
overlap_radius * overlap_radius / 3.0 -
|
||||
overlap_intersection_volume;
|
||||
|
||||
check_mesh(dbip, "sphere.s", 1, sphere_area, sphere_volume);
|
||||
check_mesh(dbip, "box.s", 1, box_area, box_volume);
|
||||
check_mesh(dbip, "thin.s", 1, thin_area, thin_volume, 10000);
|
||||
check_mesh(dbip, "torus.s", 1, torus_area, torus_volume, 4, 0);
|
||||
check_mesh(dbip, "slender_torus.s", 1, slender_torus_area,
|
||||
0.0, 4, 0);
|
||||
check_mesh(dbip, "tunnel.r", 1, 0.0, 0.0, 4, 0);
|
||||
check_mesh(dbip, "tgc.s");
|
||||
check_mesh(dbip, "concave.s");
|
||||
check_mesh(dbip, "disconnected.r", 2, disconnected_area,
|
||||
disconnected_volume);
|
||||
check_mesh(dbip, "overlap.c", 1, overlap_area, overlap_volume);
|
||||
|
||||
{
|
||||
int *coarse_faces = NULL;
|
||||
point_t *coarse_vertices = NULL;
|
||||
size_t coarse_face_count = 0;
|
||||
size_t coarse_vertex_count = 0;
|
||||
struct analyze_mdc_params coarse = ANALYZE_MDC_PARAMS_DEFAULT;
|
||||
coarse.min_depth = 3;
|
||||
coarse.max_depth = 3;
|
||||
coarse.max_rays = 50000;
|
||||
coarse.minimum_free_mem = 0;
|
||||
coarse.max_time = 0;
|
||||
enum analyze_mdc_status coarse_status = analyze_mdc(&coarse_faces,
|
||||
&coarse_face_count, &coarse_vertices, &coarse_vertex_count,
|
||||
"tunnel.r", dbip, &coarse);
|
||||
expect(coarse_status == ANALYZE_MDC_AMBIGUOUS,
|
||||
"under-resolved tunnel requests refinement");
|
||||
expect(!coarse_faces && !coarse_vertices && !coarse_face_count &&
|
||||
coarse_vertex_count == 0,
|
||||
"under-resolved tunnel leaves outputs empty");
|
||||
if (coarse_faces)
|
||||
bu_free(coarse_faces, "coarse MDC test faces");
|
||||
if (coarse_vertices)
|
||||
bu_free(coarse_vertices, "coarse MDC test vertices");
|
||||
}
|
||||
|
||||
{
|
||||
int *compatibility_faces = NULL;
|
||||
point_t *compatibility_vertices = NULL;
|
||||
int compatibility_face_count = 0;
|
||||
int compatibility_vertex_count = 0;
|
||||
point_t unused_seed = VINIT_ZERO;
|
||||
struct analyze_polygonize_params compatibility =
|
||||
ANALYZE_POLYGONIZE_PARAMS_DEFAULT;
|
||||
compatibility.max_time = 0;
|
||||
compatibility.max_cycle_time = 0;
|
||||
compatibility.minimum_free_mem = 0;
|
||||
|
||||
int status = analyze_polygonize(&compatibility_faces,
|
||||
&compatibility_face_count, &compatibility_vertices,
|
||||
&compatibility_vertex_count, 1.0, unused_seed, "sphere.s",
|
||||
dbip, &compatibility);
|
||||
expect(status == 0, "polygonize compatibility interface");
|
||||
if (status == 0) {
|
||||
expect(compatibility_face_count >= 4,
|
||||
"compatibility contour has faces");
|
||||
expect(compatibility_vertex_count >= 4,
|
||||
"compatibility contour has vertices");
|
||||
expect(bg_trimesh_solid2(compatibility_vertex_count,
|
||||
compatibility_face_count,
|
||||
reinterpret_cast<fastf_t *>(compatibility_vertices),
|
||||
compatibility_faces, NULL) == 0,
|
||||
"compatibility contour is a closed oriented manifold");
|
||||
}
|
||||
if (compatibility_faces)
|
||||
bu_free(compatibility_faces, "compatibility test faces");
|
||||
if (compatibility_vertices)
|
||||
bu_free(compatibility_vertices, "compatibility test vertices");
|
||||
|
||||
compatibility.max_time = 1;
|
||||
compatibility.time_offset = 1;
|
||||
expect(analyze_polygonize(&compatibility_faces,
|
||||
&compatibility_face_count, &compatibility_vertices,
|
||||
&compatibility_vertex_count, 1.0, unused_seed, "sphere.s",
|
||||
dbip, &compatibility) == 2,
|
||||
"compatibility interface preserves timeout status");
|
||||
expect(!compatibility_faces && !compatibility_vertices &&
|
||||
!compatibility_face_count && !compatibility_vertex_count,
|
||||
"compatibility timeout leaves outputs empty");
|
||||
}
|
||||
|
||||
int *faces = NULL;
|
||||
point_t *vertices = NULL;
|
||||
size_t face_count = 0;
|
||||
size_t vertex_count = 0;
|
||||
struct analyze_mdc_params limited = ANALYZE_MDC_PARAMS_DEFAULT;
|
||||
limited.min_depth = 4;
|
||||
limited.max_depth = 4;
|
||||
limited.max_rays = 1;
|
||||
limited.minimum_free_mem = 0;
|
||||
expect(analyze_mdc(&faces, &face_count, &vertices, &vertex_count,
|
||||
"sphere.s", dbip, &limited) == ANALYZE_MDC_RAY_LIMIT,
|
||||
"ray limit is enforced during sampling");
|
||||
expect(!faces && !vertices && !face_count && !vertex_count,
|
||||
"failure leaves outputs empty");
|
||||
|
||||
expect(analyze_mdc(NULL, &face_count, &vertices, &vertex_count,
|
||||
"sphere.s", dbip, NULL) == ANALYZE_MDC_INVALID_INPUT,
|
||||
"invalid output pointer is rejected");
|
||||
|
||||
db_close(dbip);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C++
|
||||
* tab-width: 8
|
||||
* c-basic-offset: 4
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -81,6 +81,7 @@ unset(_geo_flags)
|
||||
find_package_eigen(REQUIRED)
|
||||
find_package_gte(REQUIRED)
|
||||
find_package_spsr(REQUIRED)
|
||||
brlcad_find_package(manifold REQUIRED)
|
||||
|
||||
set(
|
||||
LIBBG_SOURCES
|
||||
@@ -124,7 +125,7 @@ set(
|
||||
|
||||
# Note - libbg_deps is defined by ${BRLCAD_SOURCE_DIR}/src/source_dirs.cmake
|
||||
set(BG_PUBLIC_LIBS ${libbg_deps})
|
||||
set(BG_PRIVATE_LIBS)
|
||||
set(BG_PRIVATE_LIBS manifold::manifold)
|
||||
if(UNIX)
|
||||
list(APPEND BG_PRIVATE_LIBS pthread dl)
|
||||
endif()
|
||||
@@ -169,6 +170,36 @@ foreach(_bg_target libbg libbg-obj libbg-static libbg-brlcad-obj)
|
||||
endif()
|
||||
endforeach()
|
||||
set_target_properties(libbg PROPERTIES VERSION 20.0.1 SOVERSION 20)
|
||||
|
||||
# Detria's adaptive predicates rely on the floating-point expression tree in
|
||||
# detria.hpp. Keep its single translation unit out of unity builds and forbid
|
||||
# contraction, reassociation, and unsafe finite-only assumptions.
|
||||
if(MSVC)
|
||||
set_source_files_properties(
|
||||
polygon_triangulate.cpp
|
||||
PROPERTIES COMPILE_OPTIONS "/fp:strict"
|
||||
)
|
||||
else()
|
||||
set(_detria_strict_options)
|
||||
foreach(_detria_flag
|
||||
-ffp-contract=off
|
||||
-frounding-math
|
||||
-fno-associative-math
|
||||
-fno-finite-math-only
|
||||
-fno-unsafe-math-optimizations)
|
||||
string(MAKE_C_IDENTIFIER "${_detria_flag}" _detria_flag_id)
|
||||
set(_detria_check_var "HAVE_${_detria_flag_id}")
|
||||
check_cxx_compiler_flag("${_detria_flag}" ${_detria_check_var})
|
||||
if(${_detria_check_var})
|
||||
list(APPEND _detria_strict_options "${_detria_flag}")
|
||||
endif()
|
||||
endforeach()
|
||||
set_source_files_properties(
|
||||
polygon_triangulate.cpp
|
||||
PROPERTIES COMPILE_OPTIONS "${_detria_strict_options}"
|
||||
)
|
||||
endif()
|
||||
|
||||
check_linker_flag("CXX" "-Wno-error=maybe-uninitialized" BG_LINKER_WNO_MAYBE_UNINITIALIZED)
|
||||
if(BG_LINKER_WNO_MAYBE_UNINITIALIZED)
|
||||
target_link_options(libbg PRIVATE -Wno-maybe-uninitialized)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -38,17 +39,17 @@
|
||||
// ported from Geogram. It detects boundary loops in a triangle mesh and
|
||||
// fills them with new triangles using GTE's robust triangulation algorithms.
|
||||
//
|
||||
// Supports two triangulation methods (applied as a fallback chain):
|
||||
// Supports four triangulation methods (applied as a fallback chain):
|
||||
// - PlanarProjection: preserves a nearly planar boundary's concave outline
|
||||
// - LSCM: Arc-length boundary-to-circle mapping + EC; always
|
||||
// succeeds for any simple closed 3D loop regardless
|
||||
// of planarity (primary method, default)
|
||||
// - EarClipping3D: Ear clipping directly in 3D (last resort fallback)
|
||||
// - SteinerFan: Adds one boundary-centroid vertex for a final bounded fan
|
||||
//
|
||||
// The former planar-projection methods (EarClipping 2D and CDT) have been
|
||||
// removed. Both require projecting the hole boundary onto a best-fit plane,
|
||||
// which fails for non-planar or highly curved holes — exactly the cases
|
||||
// common in engineering meshes (aircraft skins, compound curves). LSCM
|
||||
// handles all such cases correctly while being no slower in practice.
|
||||
// Planar projection is limited to boundaries whose vertices satisfy a strict
|
||||
// planarity test. LSCM and direct 3D ear clipping handle the non-planar and
|
||||
// highly curved boundaries common in engineering meshes.
|
||||
|
||||
namespace gte
|
||||
{
|
||||
@@ -56,8 +57,14 @@ namespace gte
|
||||
class MeshHoleFilling
|
||||
{
|
||||
public:
|
||||
using TriangleValidator = std::function<bool(
|
||||
std::array<int32_t, 3> const&,
|
||||
std::vector<std::array<int32_t, 3>> const&)>;
|
||||
|
||||
// Triangulation method for hole filling.
|
||||
// Only two methods remain after removal of the planar-projection paths:
|
||||
// Available methods:
|
||||
// PlanarProjection — orthographic projection of a nearly planar
|
||||
// boundary followed by exact 2D ear clipping.
|
||||
// LSCM — primary: maps boundary to circle via arc-length
|
||||
// parameterisation then ear-clips in 2D; always
|
||||
// succeeds for any topologically simple boundary loop.
|
||||
@@ -65,9 +72,11 @@ namespace gte
|
||||
// any projection; slower but always terminates.
|
||||
enum class TriangulationMethod
|
||||
{
|
||||
PlanarProjection,
|
||||
LSCM, // Arc-length circle map + EC in 2D; works for any simple
|
||||
// closed 3D boundary regardless of planarity (primary method)
|
||||
EarClipping3D // 3D ear clipping without any projection (last resort)
|
||||
EarClipping3D, // 3D ear clipping without any projection
|
||||
SteinerFan // One new centroid vertex with collision-gated fan triangles
|
||||
};
|
||||
|
||||
// Parameters for hole filling operations.
|
||||
@@ -81,6 +90,9 @@ namespace gte
|
||||
bool validateOutput; // Validate that output is manifold and non-self-intersecting
|
||||
bool requireManifold; // Fail if output is not manifold
|
||||
bool requireNoSelfIntersections; // Fail if output has self-intersections
|
||||
size_t maxValidatedEdges; // 0 disables per-ear validation
|
||||
size_t steinerAboveEdges; // 0 disables large-hole fan dispatch
|
||||
TriangleValidator triangleValidator;// Optional geometric ear gate
|
||||
|
||||
Parameters()
|
||||
: maxArea(static_cast<Real>(0))
|
||||
@@ -91,6 +103,9 @@ namespace gte
|
||||
, validateOutput(false) // No validation by default (Geogram-compatible)
|
||||
, requireManifold(false) // No manifold requirement by default
|
||||
, requireNoSelfIntersections(false) // Don't require (can be expensive)
|
||||
, maxValidatedEdges(0) // Collision-aware ears are opt-in
|
||||
, steinerAboveEdges(0) // Preserve requested method by default
|
||||
, triangleValidator()
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -201,21 +216,79 @@ namespace gte
|
||||
// The former planar-projection paths (EarClipping 2D and CDT) have been
|
||||
// removed — see class-level comment for rationale.
|
||||
std::vector<std::array<int32_t, 3>> newTriangles;
|
||||
size_t const holeVertexStart = vertices.size();
|
||||
bool success = false;
|
||||
|
||||
if (params.method == TriangulationMethod::EarClipping3D)
|
||||
if (params.steinerAboveEdges > 0 &&
|
||||
hole.vertices.size() > params.steinerAboveEdges)
|
||||
{
|
||||
success = TriangulateHole3D(vertices, hole, newTriangles, edgeToThirdVert);
|
||||
TriangleValidator validator;
|
||||
if (params.triangleValidator &&
|
||||
params.maxValidatedEdges > 0 &&
|
||||
hole.vertices.size() <= params.maxValidatedEdges)
|
||||
{
|
||||
validator = params.triangleValidator;
|
||||
}
|
||||
success = TriangulateHoleSteinerFan(vertices, hole,
|
||||
newTriangles, validator);
|
||||
}
|
||||
else if (params.method == TriangulationMethod::PlanarProjection)
|
||||
{
|
||||
success = TriangulateHolePlanar(vertices, hole, newTriangles);
|
||||
}
|
||||
else if (params.method == TriangulationMethod::EarClipping3D)
|
||||
{
|
||||
TriangleValidator validator;
|
||||
if (params.triangleValidator &&
|
||||
params.maxValidatedEdges > 0 &&
|
||||
hole.vertices.size() <= params.maxValidatedEdges)
|
||||
{
|
||||
validator = params.triangleValidator;
|
||||
}
|
||||
success = TriangulateHole3D(vertices, hole, newTriangles,
|
||||
edgeToThirdVert, validator);
|
||||
}
|
||||
else if (params.method == TriangulationMethod::SteinerFan)
|
||||
{
|
||||
TriangleValidator validator;
|
||||
if (params.triangleValidator &&
|
||||
params.maxValidatedEdges > 0 &&
|
||||
hole.vertices.size() <= params.maxValidatedEdges)
|
||||
{
|
||||
validator = params.triangleValidator;
|
||||
}
|
||||
success = TriangulateHoleSteinerFan(vertices, hole,
|
||||
newTriangles, validator);
|
||||
}
|
||||
else
|
||||
{
|
||||
// LSCM (default): arc-length circle mapping, always succeeds for any
|
||||
// simple closed 3D boundary loop regardless of planarity.
|
||||
success = TriangulateHoleLSCM(vertices, hole, newTriangles);
|
||||
// Fall back to 3D if LSCM somehow fails
|
||||
// The circle map is nondegenerate in parameter space, but
|
||||
// a B-Rep boundary commonly retains multiple collinear
|
||||
// samples on each straight edge. Ears which are valid on
|
||||
// the circle can therefore collapse after mapping back to
|
||||
// 3D. Treat that as a failed parameterization and let the
|
||||
// geometric ear selector preserve the same boundary with
|
||||
// nonzero-area triangles.
|
||||
if (success)
|
||||
{
|
||||
for (auto const& triangle : newTriangles)
|
||||
{
|
||||
if (IsGeometricallyDegenerate(vertices, triangle))
|
||||
{
|
||||
success = false;
|
||||
newTriangles.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall back to 3D if LSCM fails logically or geometrically.
|
||||
if (!success && params.autoFallback)
|
||||
{
|
||||
success = TriangulateHole3D(vertices, hole, newTriangles, edgeToThirdVert);
|
||||
success = TriangulateHole3D(vertices, hole,
|
||||
newTriangles, edgeToThirdVert);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +314,14 @@ namespace gte
|
||||
triangles.insert(triangles.end(), newTriangles.begin(), newTriangles.end());
|
||||
++numFilled;
|
||||
}
|
||||
else
|
||||
{
|
||||
vertices.resize(holeVertexStart);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
vertices.resize(holeVertexStart);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,6 +698,125 @@ namespace gte
|
||||
return true;
|
||||
}
|
||||
|
||||
// Preserve the actual outline of a nearly planar boundary. LSCM's
|
||||
// circle map deliberately discards concavity, which can restore to
|
||||
// diagonals outside the 3-D polygon. Dropping the dominant component
|
||||
// of Newell's normal gives a nondegenerate orthographic chart without
|
||||
// introducing new vertex positions.
|
||||
static bool TriangulateHolePlanar(
|
||||
std::vector<Vector3<Real>> const& vertices,
|
||||
HoleBoundary const& hole,
|
||||
std::vector<std::array<int32_t, 3>>& triangles)
|
||||
{
|
||||
if (hole.vertices.size() < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3<Real> normal{};
|
||||
Vector3<Real> minimum = vertices[hole.vertices[0]];
|
||||
Vector3<Real> maximum = minimum;
|
||||
for (size_t i = 0; i < hole.vertices.size(); ++i)
|
||||
{
|
||||
Vector3<Real> const& current = vertices[hole.vertices[i]];
|
||||
Vector3<Real> const& next =
|
||||
vertices[hole.vertices[(i + 1) % hole.vertices.size()]];
|
||||
normal[0] += (current[1] - next[1]) *
|
||||
(current[2] + next[2]);
|
||||
normal[1] += (current[2] - next[2]) *
|
||||
(current[0] + next[0]);
|
||||
normal[2] += (current[0] - next[0]) *
|
||||
(current[1] + next[1]);
|
||||
for (int axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
minimum[axis] = std::min(minimum[axis], current[axis]);
|
||||
maximum[axis] = std::max(maximum[axis], current[axis]);
|
||||
}
|
||||
}
|
||||
Real normalLength = Length(normal);
|
||||
if (!(normalLength > static_cast<Real>(0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
normal /= normalLength;
|
||||
Vector3<Real> diagonal = maximum - minimum;
|
||||
Real scale = Length(diagonal);
|
||||
if (!(scale > static_cast<Real>(0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Vector3<Real> const& origin = vertices[hole.vertices[0]];
|
||||
Real maxPlaneMiss = static_cast<Real>(0);
|
||||
for (int32_t index : hole.vertices)
|
||||
{
|
||||
maxPlaneMiss = std::max(maxPlaneMiss,
|
||||
std::fabs(Dot(vertices[index] - origin, normal)));
|
||||
}
|
||||
if (maxPlaneMiss > static_cast<Real>(1e-6) * scale)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int droppedAxis = 0;
|
||||
if (std::fabs(normal[1]) > std::fabs(normal[droppedAxis]))
|
||||
{
|
||||
droppedAxis = 1;
|
||||
}
|
||||
if (std::fabs(normal[2]) > std::fabs(normal[droppedAxis]))
|
||||
{
|
||||
droppedAxis = 2;
|
||||
}
|
||||
const int firstAxis = (droppedAxis + 1) % 3;
|
||||
const int secondAxis = (droppedAxis + 2) % 3;
|
||||
std::vector<Vector2<Real>> projected;
|
||||
projected.reserve(hole.vertices.size());
|
||||
for (int32_t index : hole.vertices)
|
||||
{
|
||||
projected.push_back({vertices[index][firstAxis],
|
||||
vertices[index][secondAxis]});
|
||||
}
|
||||
Real signedArea = static_cast<Real>(0);
|
||||
for (size_t i = 0; i < projected.size(); ++i)
|
||||
{
|
||||
Vector2<Real> const& current = projected[i];
|
||||
Vector2<Real> const& next =
|
||||
projected[(i + 1) % projected.size()];
|
||||
signedArea += current[0] * next[1] -
|
||||
next[0] * current[1];
|
||||
}
|
||||
if (!(std::fabs(signedArea) > static_cast<Real>(0)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (signedArea < static_cast<Real>(0))
|
||||
{
|
||||
for (auto& point : projected)
|
||||
{
|
||||
std::swap(point[0], point[1]);
|
||||
}
|
||||
}
|
||||
std::vector<std::array<int32_t, 3>> localTriangles;
|
||||
if (!TriangulateWithEC(projected, localTriangles))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (auto const& triangle : localTriangles)
|
||||
{
|
||||
std::array<int32_t, 3> restored = {
|
||||
hole.vertices[triangle[0]],
|
||||
hole.vertices[triangle[1]],
|
||||
hole.vertices[triangle[2]]
|
||||
};
|
||||
if (IsGeometricallyDegenerate(vertices, restored))
|
||||
{
|
||||
triangles.clear();
|
||||
return false;
|
||||
}
|
||||
triangles.push_back(restored);
|
||||
}
|
||||
return !triangles.empty();
|
||||
}
|
||||
|
||||
// Triangulate a hole working directly in 3D (no projection)
|
||||
// Ported from Geogram's ear cutting algorithm (triangulate_hole_ear_cutting)
|
||||
//
|
||||
@@ -635,7 +835,8 @@ namespace gte
|
||||
std::vector<Vector3<Real>> const& vertices,
|
||||
HoleBoundary const& hole,
|
||||
std::vector<std::array<int32_t, 3>>& triangles,
|
||||
std::unordered_map<int64_t, int32_t> const& edgeToThirdVert = {})
|
||||
std::unordered_map<int64_t, int32_t> const& edgeToThirdVert = {},
|
||||
TriangleValidator const& triangleValidator = {})
|
||||
{
|
||||
if (hole.vertices.size() < 3)
|
||||
{
|
||||
@@ -701,6 +902,20 @@ namespace gte
|
||||
for (size_t i = 0; i < workingHole.size(); ++i)
|
||||
{
|
||||
size_t nextIdx = (i + 1) % workingHole.size();
|
||||
std::array<int32_t, 3> candidate = {
|
||||
workingHole[i].v0,
|
||||
workingHole[i].v1,
|
||||
workingHole[nextIdx].v1
|
||||
};
|
||||
if (IsGeometricallyDegenerate(vertices, candidate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (triangleValidator &&
|
||||
!triangleValidator(candidate, triangles))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Real score = ComputeEarScore3D(vertices,
|
||||
workingHole[i], workingHole[nextIdx]);
|
||||
|
||||
@@ -721,8 +936,10 @@ namespace gte
|
||||
// Create triangle from best ear (matches geogram's trindex T)
|
||||
EdgeTriple const& t1 = workingHole[bestIdx];
|
||||
EdgeTriple const& t2 = workingHole[nextIdx];
|
||||
// Triangle = (T1[0], T2[1], T1[1]) in geogram notation
|
||||
triangles.push_back({t1.v0, t2.v1, t1.v1});
|
||||
// Follow the hole boundary through the clipped vertex. The
|
||||
// final triangle below uses this same winding, and the new
|
||||
// diagonal then opposes the adjacent fill triangle.
|
||||
triangles.push_back({t1.v0, t1.v1, t2.v1});
|
||||
|
||||
// Update working hole by replacing the clipped position with the
|
||||
// merged triple, then removing the now-redundant successor triple.
|
||||
@@ -730,7 +947,7 @@ namespace gte
|
||||
// After clipping ear vertex t1.v1:
|
||||
// - New boundary edge is (t1.v0, t2.v1)
|
||||
// - The new face adjacent to this edge is the triangle just created:
|
||||
// (t1.v0, t2.v1, t1.v1) — so its third vertex is t1.v1.
|
||||
// (t1.v0, t1.v1, t2.v1) — so its third vertex is t1.v1.
|
||||
// This matches geogram's: hole[best_i1] = T where T carries
|
||||
// indices = (T1[0], T2[1], T1[1]).
|
||||
EdgeTriple merged;
|
||||
@@ -749,15 +966,159 @@ namespace gte
|
||||
// Add final triangle
|
||||
if (workingHole.size() == 3)
|
||||
{
|
||||
triangles.push_back({
|
||||
std::array<int32_t, 3> finalTriangle = {
|
||||
workingHole[0].v0,
|
||||
workingHole[1].v0,
|
||||
workingHole[2].v0
|
||||
});
|
||||
};
|
||||
if (IsGeometricallyDegenerate(vertices, finalTriangle))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (triangleValidator &&
|
||||
!triangleValidator(finalTriangle, triangles))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
triangles.push_back(finalTriangle);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A non-planar boundary can have no collision-free triangulation using
|
||||
// boundary diagonals alone. Add one bounded interior candidate and
|
||||
// join it to each directed boundary edge. The caller's validator and
|
||||
// final mesh certification remain responsible for proving that the fan
|
||||
// is geometrically admissible.
|
||||
static bool TriangulateHoleSteinerFan(
|
||||
std::vector<Vector3<Real>>& vertices,
|
||||
HoleBoundary const& hole,
|
||||
std::vector<std::array<int32_t, 3>>& triangles,
|
||||
TriangleValidator const& triangleValidator = {})
|
||||
{
|
||||
if (hole.vertices.size() < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3<Real> center{};
|
||||
Vector3<Real> normal{};
|
||||
Vector3<Real> minimum = vertices[hole.vertices[0]];
|
||||
Vector3<Real> maximum = minimum;
|
||||
for (int32_t index : hole.vertices)
|
||||
{
|
||||
center += vertices[index];
|
||||
for (int axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
minimum[axis] = std::min(minimum[axis],
|
||||
vertices[index][axis]);
|
||||
maximum[axis] = std::max(maximum[axis],
|
||||
vertices[index][axis]);
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; index < hole.vertices.size(); ++index)
|
||||
{
|
||||
Vector3<Real> const& current =
|
||||
vertices[hole.vertices[index]];
|
||||
Vector3<Real> const& next = vertices[hole.vertices[
|
||||
(index + 1) % hole.vertices.size()]];
|
||||
normal[0] += (current[1] - next[1]) *
|
||||
(current[2] + next[2]);
|
||||
normal[1] += (current[2] - next[2]) *
|
||||
(current[0] + next[0]);
|
||||
normal[2] += (current[0] - next[0]) *
|
||||
(current[1] + next[1]);
|
||||
}
|
||||
center /= static_cast<Real>(hole.vertices.size());
|
||||
for (int axis = 0; axis < 3; ++axis)
|
||||
{
|
||||
if (!std::isfinite(center[axis]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t const vertexStart = vertices.size();
|
||||
int32_t const centerIndex = static_cast<int32_t>(vertexStart);
|
||||
if (static_cast<size_t>(centerIndex) != vertexStart)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Real const normalLength = Length(normal);
|
||||
Real const scale = Length(maximum - minimum);
|
||||
std::vector<Real> offsets = {static_cast<Real>(0)};
|
||||
if (triangleValidator && normalLength > static_cast<Real>(0) &&
|
||||
scale > static_cast<Real>(0))
|
||||
{
|
||||
normal /= normalLength;
|
||||
Real const fractions[] = {
|
||||
static_cast<Real>(1e-8), static_cast<Real>(1e-7),
|
||||
static_cast<Real>(1e-6), static_cast<Real>(1e-5),
|
||||
static_cast<Real>(1e-4), static_cast<Real>(1e-3),
|
||||
static_cast<Real>(1e-2)
|
||||
};
|
||||
for (Real fraction : fractions)
|
||||
{
|
||||
offsets.push_back(fraction);
|
||||
offsets.push_back(-fraction);
|
||||
}
|
||||
}
|
||||
|
||||
triangles.reserve(hole.vertices.size());
|
||||
for (Real offset : offsets)
|
||||
{
|
||||
vertices.resize(vertexStart);
|
||||
vertices.push_back(center + offset * scale * normal);
|
||||
triangles.clear();
|
||||
bool valid = true;
|
||||
for (size_t index = 0; index < hole.vertices.size(); ++index)
|
||||
{
|
||||
std::array<int32_t, 3> candidate = {
|
||||
hole.vertices[index],
|
||||
hole.vertices[(index + 1) % hole.vertices.size()],
|
||||
centerIndex
|
||||
};
|
||||
if (IsGeometricallyDegenerate(vertices, candidate) ||
|
||||
(triangleValidator &&
|
||||
!triangleValidator(candidate, triangles)))
|
||||
{
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
triangles.push_back(candidate);
|
||||
}
|
||||
if (valid)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
triangles.clear();
|
||||
vertices.resize(vertexStart);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IsGeometricallyDegenerate(
|
||||
std::vector<Vector3<Real>> const& vertices,
|
||||
std::array<int32_t, 3> const& triangle)
|
||||
{
|
||||
Vector3<Real> const& p0 = vertices[triangle[0]];
|
||||
Vector3<Real> const& p1 = vertices[triangle[1]];
|
||||
Vector3<Real> const& p2 = vertices[triangle[2]];
|
||||
Vector3<Real> e01 = p1 - p0;
|
||||
Vector3<Real> e02 = p2 - p0;
|
||||
Vector3<Real> e12 = p2 - p1;
|
||||
Real longestSquared = std::max(Dot(e01, e01),
|
||||
std::max(Dot(e02, e02), Dot(e12, e12)));
|
||||
if (!(longestSquared > static_cast<Real>(0)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
Real doubledArea = Length(Cross(e01, e02));
|
||||
return !(doubledArea > static_cast<Real>(64) *
|
||||
std::numeric_limits<Real>::epsilon() * longestSquared);
|
||||
}
|
||||
|
||||
// Compute ear quality score in 3D (ported from Geogram)
|
||||
// Higher score = better triangle
|
||||
|
||||
@@ -165,47 +165,22 @@ namespace gte
|
||||
return;
|
||||
}
|
||||
|
||||
// Build adjacency information
|
||||
std::map<std::pair<int32_t, int32_t>, std::vector<size_t>> edgeToTriangles;
|
||||
std::vector<int32_t> componentIds;
|
||||
int32_t numComponents = GetConnectedComponents(triangles, componentIds);
|
||||
std::vector<Real> signedVolumes(
|
||||
static_cast<size_t>(numComponents), static_cast<Real>(0));
|
||||
for (size_t i = 0; i < triangles.size(); ++i)
|
||||
{
|
||||
signedVolumes[static_cast<size_t>(componentIds[i])] +=
|
||||
ComputeSignedVolume(vertices, triangles[i]);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < triangles.size(); ++i)
|
||||
{
|
||||
auto const& tri = triangles[i];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
if (signedVolumes[static_cast<size_t>(componentIds[i])] <
|
||||
static_cast<Real>(0))
|
||||
{
|
||||
int32_t v0 = tri[j];
|
||||
int32_t v1 = tri[(j + 1) % 3];
|
||||
auto edge = std::make_pair(std::min(v0, v1), std::max(v0, v1));
|
||||
edgeToTriangles[edge].push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Process each connected component
|
||||
std::vector<int32_t> componentIds;
|
||||
int32_t numComponents = GetConnectedComponents(triangles, componentIds);
|
||||
|
||||
for (int32_t compId = 0; compId < numComponents; ++compId)
|
||||
{
|
||||
// Determine if component should be flipped based on signed volume
|
||||
Real signedVolume = static_cast<Real>(0);
|
||||
for (size_t i = 0; i < triangles.size(); ++i)
|
||||
{
|
||||
if (componentIds[i] == compId)
|
||||
{
|
||||
signedVolume += ComputeSignedVolume(vertices, triangles[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// If signed volume is negative, flip all triangles in component
|
||||
if (signedVolume < static_cast<Real>(0))
|
||||
{
|
||||
for (size_t i = 0; i < triangles.size(); ++i)
|
||||
{
|
||||
if (componentIds[i] == compId)
|
||||
{
|
||||
std::swap(triangles[i][1], triangles[i][2]);
|
||||
}
|
||||
}
|
||||
std::swap(triangles[i][1], triangles[i][2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-13
@@ -389,8 +389,6 @@ protected:
|
||||
|
||||
Branch m_branchBuf[_MaxNodeCount + 1];
|
||||
int m_branchCount = 0;
|
||||
Rect m_coverSplit;
|
||||
ElementTypeReal m_coverSplitArea = kElementTypeRealZero;
|
||||
};
|
||||
|
||||
Node* AllocNode();
|
||||
@@ -1275,13 +1273,6 @@ void RTREE_QUAL::GetBranches(Node* a_node, const Branch* a_branch, PartitionVars
|
||||
}
|
||||
a_parVars->m_branchBuf[_MaxNodeCount] = *a_branch;
|
||||
a_parVars->m_branchCount = _MaxNodeCount + 1;
|
||||
|
||||
// Calculate rect containing all in the set
|
||||
a_parVars->m_coverSplit = a_parVars->m_branchBuf[0].m_rect;
|
||||
for (int index = 1; index < _MaxNodeCount + 1; ++index) {
|
||||
a_parVars->m_coverSplit = CombineRect(&a_parVars->m_coverSplit, &a_parVars->m_branchBuf[index].m_rect);
|
||||
}
|
||||
a_parVars->m_coverSplitArea = CalcRectVolume(&a_parVars->m_coverSplit);
|
||||
}
|
||||
|
||||
|
||||
@@ -1398,20 +1389,22 @@ void RTREE_QUAL::InitParVars(PartitionVars* a_parVars, int a_maxRects, int a_min
|
||||
RTREE_TEMPLATE
|
||||
void RTREE_QUAL::PickSeeds(PartitionVars* a_parVars)
|
||||
{
|
||||
int seed0 = 0, seed1 = 0;
|
||||
int seed0 = 0, seed1 = 1;
|
||||
bool firstPair = true;
|
||||
_ElementTypeReal worst{ 0.0 }, waste{ 0.0 };
|
||||
_ElementTypeReal area[_MaxNodeCount + 1] = { 0.0, };
|
||||
|
||||
for (int index = 0; index < a_parVars->m_total; ++index) {
|
||||
area[index] = CalcRectVolume(&a_parVars->m_branchBuf[index].m_rect);
|
||||
}
|
||||
|
||||
worst = -a_parVars->m_coverSplitArea - 1;
|
||||
for (int indexA = 0; indexA < a_parVars->m_total - 1; ++indexA) {
|
||||
for (int indexB = indexA + 1; indexB < a_parVars->m_total; ++indexB) {
|
||||
auto oneRect = CombineRect(&a_parVars->m_branchBuf[indexA].m_rect, &a_parVars->m_branchBuf[indexB].m_rect);
|
||||
waste = CalcRectVolume(&oneRect) - area[indexA] - area[indexB];
|
||||
if (waste > worst) {
|
||||
// Always accept the first pair. Deriving a sentinel from the
|
||||
// cover volume is unsafe when subtracting one is below its ulp.
|
||||
if (firstPair || waste > worst) {
|
||||
firstPair = false;
|
||||
worst = waste;
|
||||
seed0 = indexA;
|
||||
seed1 = indexB;
|
||||
|
||||
+4
-4
@@ -113,10 +113,10 @@ bg_lseg_clip(fastf_t *xp1, fastf_t *yp1, fastf_t *xp2, fastf_t *yp2, fastf_t cli
|
||||
int
|
||||
bg_ray_vclip(point_t a, point_t b, const fastf_t *min_pt, const fastf_t *max_pt)
|
||||
{
|
||||
static vect_t diff;
|
||||
static double sv;
|
||||
static double st;
|
||||
static double mindist, maxdist;
|
||||
vect_t diff;
|
||||
double sv;
|
||||
double st;
|
||||
double mindist, maxdist;
|
||||
fastf_t *pt = &a[0];
|
||||
fastf_t *dir = &diff[0];
|
||||
int i;
|
||||
|
||||
+195
-233
@@ -1,6 +1,6 @@
|
||||
// detria - a delaunay triangulation library
|
||||
//
|
||||
// Copyright (c) 2024 Kimbatt (https://github.com/Kimbatt)
|
||||
// Copyright (c) 2025 Kimbatt (https://github.com/Kimbatt)
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
@@ -71,6 +71,14 @@ if (success)
|
||||
#ifndef DETRIA_HPP_INCLUDED
|
||||
#define DETRIA_HPP_INCLUDED
|
||||
|
||||
#ifdef DETRIA_CXXMODULE
|
||||
|
||||
// Using C++20 modules - headers are included by the module file
|
||||
// Also define the export macro
|
||||
#define DETRIA_EXPORT export
|
||||
|
||||
#else
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
@@ -96,6 +104,11 @@ if (success)
|
||||
|
||||
#endif
|
||||
|
||||
// Not using C++20 modules - define an empty export macro
|
||||
#define DETRIA_EXPORT
|
||||
|
||||
#endif
|
||||
|
||||
namespace detria
|
||||
{
|
||||
template <typename T>
|
||||
@@ -105,10 +118,10 @@ namespace detria
|
||||
T y;
|
||||
};
|
||||
|
||||
using PointF = Vec2<float>;
|
||||
using PointD = Vec2<double>;
|
||||
DETRIA_EXPORT using PointF = Vec2<float>;
|
||||
DETRIA_EXPORT using PointD = Vec2<double>;
|
||||
|
||||
template <typename Idx>
|
||||
DETRIA_EXPORT template <typename Idx>
|
||||
struct Triangle
|
||||
{
|
||||
Idx x;
|
||||
@@ -345,12 +358,20 @@ namespace detria
|
||||
if ((fnow > enow) == (fnow > -enow))
|
||||
{
|
||||
DETRIA_Two_Sum(Q, enow, Qnew, hh);
|
||||
enow = e[++eindex];
|
||||
|
||||
if (++eindex < elen)
|
||||
{
|
||||
enow = e[eindex];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DETRIA_Two_Sum(Q, fnow, Qnew, hh);
|
||||
fnow = f[++findex];
|
||||
|
||||
if (++findex < flen)
|
||||
{
|
||||
fnow = f[findex];
|
||||
}
|
||||
}
|
||||
Q = Qnew;
|
||||
if (hh != Scalar(0))
|
||||
@@ -362,7 +383,12 @@ namespace detria
|
||||
while (eindex < elen)
|
||||
{
|
||||
DETRIA_Two_Sum(Q, enow, Qnew, hh);
|
||||
enow = e[++eindex];
|
||||
|
||||
if (++eindex < elen)
|
||||
{
|
||||
enow = e[eindex];
|
||||
}
|
||||
|
||||
Q = Qnew;
|
||||
if (hh != Scalar(0))
|
||||
{
|
||||
@@ -372,7 +398,12 @@ namespace detria
|
||||
while (findex < flen)
|
||||
{
|
||||
DETRIA_Two_Sum(Q, fnow, Qnew, hh);
|
||||
fnow = f[++findex];
|
||||
|
||||
if (++findex < flen)
|
||||
{
|
||||
fnow = f[findex];
|
||||
}
|
||||
|
||||
Q = Qnew;
|
||||
if (hh != Scalar(0))
|
||||
{
|
||||
@@ -1428,6 +1459,27 @@ namespace detria
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
inline bool detriaRecoverableAssert(bool condition, const char* message)
|
||||
{
|
||||
if (condition) DETRIA_LIKELY
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef NDEBUG
|
||||
(void)message;
|
||||
#else
|
||||
std::cerr << "Triangulation check failed";
|
||||
if (message)
|
||||
{
|
||||
std::cerr << ": " << message;
|
||||
}
|
||||
|
||||
std::cerr << std::endl;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -1444,8 +1496,8 @@ namespace detria
|
||||
{
|
||||
}
|
||||
|
||||
template <template <typename, typename> typename Collection, typename Allocator>
|
||||
ReadonlySpan(const Collection<T, Allocator>& collection) : _ptr(collection.data()), _count(collection.size())
|
||||
template <typename Collection>
|
||||
ReadonlySpan(const Collection& collection) : _ptr(collection.data()), _count(collection.size())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2002,17 +2054,6 @@ namespace detria
|
||||
return newEdgeIndexA;
|
||||
}
|
||||
|
||||
inline HalfEdgeIndex createOrGetEdge(VertexIndex a, VertexIndex b, HalfEdgeIndex afterEdgeA, HalfEdgeIndex afterEdgeB)
|
||||
{
|
||||
HalfEdgeIndex existingEdge = getEdgeBetween(a, b);
|
||||
if (existingEdge.isValid())
|
||||
{
|
||||
return *existingEdge;
|
||||
}
|
||||
|
||||
return createNewEdge(a, b, afterEdgeA, afterEdgeB);
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
inline void forEachEdgeOfVertex(VertexIndex idx, Callback callback) const
|
||||
{
|
||||
@@ -2053,8 +2094,8 @@ namespace detria
|
||||
// Assuming the vertices of this edge have at least one other edge
|
||||
// The first edge of the vertices will not be set correctly otherwise
|
||||
|
||||
HalfEdge& edge = getEdge(edgeIndex);
|
||||
HalfEdge& opposite = getEdge(getOpposite(edgeIndex));
|
||||
const HalfEdge& edge = getEdge(edgeIndex);
|
||||
const HalfEdge& opposite = getEdge(getOpposite(edgeIndex));
|
||||
|
||||
Vertex& v0 = getVertex(edge.vertex);
|
||||
Vertex& v1 = getVertex(opposite.vertex);
|
||||
@@ -2138,7 +2179,7 @@ namespace detria
|
||||
};
|
||||
}
|
||||
|
||||
enum class TriangleLocation : uint8_t
|
||||
DETRIA_EXPORT enum class TriangleLocation : uint8_t
|
||||
{
|
||||
// Inside an outline
|
||||
Interior = 0,
|
||||
@@ -2150,7 +2191,7 @@ namespace detria
|
||||
ConvexHull = 2,
|
||||
};
|
||||
|
||||
enum class TriangleLocationMask : uint8_t
|
||||
DETRIA_EXPORT enum class TriangleLocationMask : uint8_t
|
||||
{
|
||||
Interior = 1 << uint8_t(TriangleLocation::Interior),
|
||||
Hole = 1 << uint8_t(TriangleLocation::Hole),
|
||||
@@ -2158,7 +2199,7 @@ namespace detria
|
||||
All = Interior | Hole | ConvexHull
|
||||
};
|
||||
|
||||
enum class TriangulationError
|
||||
DETRIA_EXPORT enum class TriangulationError
|
||||
{
|
||||
// Triangulation was successful
|
||||
NoError,
|
||||
@@ -2216,7 +2257,7 @@ namespace detria
|
||||
AssertionFailed
|
||||
};
|
||||
|
||||
#define DETRIA_ASSERT_MSG(cond, msg) do { if (!detail::detriaAssert((cond), msg)) DETRIA_UNLIKELY { return fail(TE_AssertionFailed{ }); } } while(0)
|
||||
#define DETRIA_ASSERT_MSG(cond, msg) do { if (!detail::detriaRecoverableAssert((cond), msg)) DETRIA_UNLIKELY { return fail(TE_AssertionFailed{ }); } } while(0)
|
||||
#define DETRIA_ASSERT(cond) DETRIA_ASSERT_MSG(cond, nullptr)
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -2227,7 +2268,7 @@ namespace detria
|
||||
|
||||
#define DETRIA_CHECK(cond) do { if (!(cond)) DETRIA_UNLIKELY { return false; } } while (0)
|
||||
|
||||
template <typename Point, typename Idx>
|
||||
DETRIA_EXPORT template <typename Point, typename Idx>
|
||||
struct DefaultTriangulationConfig
|
||||
{
|
||||
// Default configuration for a triangulation.
|
||||
@@ -2284,7 +2325,7 @@ namespace detria
|
||||
constexpr static bool NaNChecks = true;
|
||||
};
|
||||
|
||||
template <
|
||||
DETRIA_EXPORT template <
|
||||
typename Point = PointD,
|
||||
typename Idx = uint32_t,
|
||||
typename Config = DefaultTriangulationConfig<Point, Idx>
|
||||
@@ -2340,7 +2381,7 @@ namespace detria
|
||||
{
|
||||
None = 0,
|
||||
Delaunay = 1,
|
||||
Boundary = 2
|
||||
Boundary = 2,
|
||||
};
|
||||
|
||||
inline bool isConstrained() const
|
||||
@@ -2725,7 +2766,7 @@ namespace detria
|
||||
|
||||
std::string getErrorMessage() const
|
||||
{
|
||||
return "Internal error, this is a bug in the code";
|
||||
return "Internal error, this indicates a bug in detria's code";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2875,9 +2916,9 @@ namespace detria
|
||||
}
|
||||
|
||||
// Set a single constrained edge, which will be part of the final triangulation
|
||||
void setConstrainedEdge(const Idx& idxA, const Idx& idxB)
|
||||
void setConstrainedEdge(const Idx& vertexIndexA, const Idx& vertexIndexB)
|
||||
{
|
||||
_manuallyConstrainedEdges.push_back({ idxA, idxB });
|
||||
_manuallyConstrainedEdges.push_back({ vertexIndexA, vertexIndexB });
|
||||
}
|
||||
|
||||
// Perform the triangulation
|
||||
@@ -3163,34 +3204,13 @@ namespace detria
|
||||
_resultTriangles.reserve(numTriangles);
|
||||
}
|
||||
|
||||
// Only create the vertices for now
|
||||
for (size_t i = 0; i < _numPoints; ++i)
|
||||
{
|
||||
_topology.createVertex();
|
||||
}
|
||||
|
||||
// Initial triangulation, will add the edges
|
||||
DETRIA_CHECK(createInitialTriangulation(delaunay));
|
||||
|
||||
// Add constrained edges, which ensures that all required vertices have edges between them
|
||||
DETRIA_CHECK(createConstrainedEdges(delaunay));
|
||||
|
||||
// Go through all triangles, and decide if they are inside or outside
|
||||
DETRIA_CHECK(classifyTriangles());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool createInitialTriangulation(bool delaunay)
|
||||
{
|
||||
// Initial triangulation - just a valid triangulation that includes all points
|
||||
|
||||
// Initialize point data
|
||||
CollectionWithAllocator<Idx>& sortedPoints = _initialTriangulation_SortedPoints;
|
||||
sortedPoints.resize(_numPoints);
|
||||
for (size_t i = 0; i < _numPoints; ++i)
|
||||
{
|
||||
sortedPoints[i] = Idx(i);
|
||||
_topology.createVertex();
|
||||
}
|
||||
|
||||
// Sort all points by x coordinates; for points with the same x coordinate, sort by y
|
||||
@@ -3229,154 +3249,70 @@ namespace detria
|
||||
// Set initial convex hull vertex - the first sorted point is guaranteed to be part of the convex hull
|
||||
_convexHullInitialVertex = TVertex(sortedPoints[0]);
|
||||
|
||||
// Find an initial triangle
|
||||
// Since we have no duplicate points, we can always use the first two points as the triangle's points
|
||||
// For the third point, we need to find one so that the first three points are not collinear
|
||||
bool allPointsAreCollinear = true;
|
||||
|
||||
const Idx& p0Idx = sortedPoints[0];
|
||||
const Idx& p1Idx = sortedPoints[1];
|
||||
Vector2 p0Position = getPoint(p0Idx);
|
||||
Vector2 p1Position = getPoint(p1Idx);
|
||||
// Initial triangulation, will add the edges
|
||||
DETRIA_CHECK(createInitialTriangulation(delaunay, sortedPoints, 0, sortedPoints.size(), allPointsAreCollinear));
|
||||
|
||||
size_t firstNonCollinearIndex = 0; // Index to the list of sorted points, not the original points
|
||||
math::Orientation triangleOrientation = math::Orientation::Collinear;
|
||||
for (size_t i = 2; i < sortedPoints.size(); ++i)
|
||||
if (allPointsAreCollinear)
|
||||
{
|
||||
const Idx& currentIdx = sortedPoints[i];
|
||||
Vector2 currentPosition = getPoint(currentIdx);
|
||||
|
||||
triangleOrientation = orient2d(p0Position, p1Position, currentPosition);
|
||||
if (triangleOrientation != math::Orientation::Collinear)
|
||||
{
|
||||
firstNonCollinearIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (triangleOrientation == math::Orientation::Collinear)
|
||||
{
|
||||
// All points are collinear, and cannot be triangulated
|
||||
return fail(TE_AllPointsAreCollinear{ });
|
||||
}
|
||||
|
||||
const Idx& p2Idx = sortedPoints[firstNonCollinearIndex];
|
||||
// Add constrained edges, which ensures that all required vertices have edges between them
|
||||
DETRIA_CHECK(createConstrainedEdges(delaunay));
|
||||
|
||||
// We have a triangle now, so start adding the remaining points to the triangulation
|
||||
// Go through all triangles, and decide if they are inside or outside
|
||||
DETRIA_CHECK(classifyTriangles());
|
||||
|
||||
// Make sure that the triangles are clockwise
|
||||
return true;
|
||||
}
|
||||
|
||||
TVertex v0{ };
|
||||
TVertex v1{ };
|
||||
TVertex v2(p2Idx);
|
||||
bool createInitialTriangulation(bool delaunay, const CollectionWithAllocator<Idx>& sortedPoints, size_t startIndex, size_t endIndex, bool& allCollinear)
|
||||
{
|
||||
// Initial triangulation - just a valid triangulation that includes all points
|
||||
|
||||
if (triangleOrientation == math::Orientation::CW)
|
||||
{
|
||||
v0 = TVertex(p0Idx);
|
||||
v1 = TVertex(p1Idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
v0 = TVertex(p1Idx);
|
||||
v1 = TVertex(p0Idx);
|
||||
}
|
||||
// Find an initial edge
|
||||
// Since we have no duplicate points, we can always use the first two points
|
||||
|
||||
const Idx& p0Idx = sortedPoints[startIndex];
|
||||
const Idx& p1Idx = sortedPoints[startIndex + 1];
|
||||
|
||||
TVertex v0 = TVertex(p0Idx);
|
||||
TVertex v1 = TVertex(p1Idx);
|
||||
|
||||
THalfEdge lastEdge{ };
|
||||
|
||||
// Create initial half-edges
|
||||
auto markInitialBoundaryEdge = [&](THalfEdge edge)
|
||||
{
|
||||
// v0
|
||||
// *
|
||||
//
|
||||
//
|
||||
//
|
||||
// v2 * * v1
|
||||
THalfEdge opposite = _topology.getOpposite(edge);
|
||||
|
||||
THalfEdge e01 = _topology.createNewEdge(v0, v1, { }, { });
|
||||
THalfEdge e10 = _topology.getOpposite(e01);
|
||||
EdgeData& edgeData = _topology.getEdgeData(edge);
|
||||
EdgeData& oppositeData = _topology.getEdgeData(opposite);
|
||||
|
||||
// v0
|
||||
// *
|
||||
// \ e01
|
||||
//
|
||||
// \ e10
|
||||
// v2 * * v1
|
||||
|
||||
THalfEdge e02 = _topology.createNewEdge(v0, v2, e01, { });
|
||||
THalfEdge e20 = _topology.getOpposite(e02);
|
||||
|
||||
// v0
|
||||
// *
|
||||
// e02 / \ e01
|
||||
//
|
||||
// e20 / \ e10
|
||||
// v2 * * v1
|
||||
|
||||
THalfEdge e12 = _topology.createNewEdge(v1, v2, e10, e20);
|
||||
THalfEdge e21 = _topology.getOpposite(e12);
|
||||
|
||||
// v0
|
||||
// *
|
||||
// e02 / \ e01
|
||||
//
|
||||
// e20 / \ e10
|
||||
// v2 *--- ---* v1
|
||||
// e21 e12
|
||||
edgeData.setBoundary(true);
|
||||
oppositeData.setBoundary(true);
|
||||
|
||||
if (delaunay)
|
||||
{
|
||||
// Mark initial edges as delaunay
|
||||
// Mark outer part of half-edges as boundary
|
||||
|
||||
auto markEdge = [&](THalfEdge edge, bool isBoundary)
|
||||
{
|
||||
EdgeData& edgeData = _topology.getEdgeData(edge);
|
||||
edgeData.setDelaunay(true);
|
||||
edgeData.setBoundary(isBoundary);
|
||||
};
|
||||
|
||||
markEdge(e01, false);
|
||||
markEdge(e10, true);
|
||||
markEdge(e02, true);
|
||||
markEdge(e20, false);
|
||||
markEdge(e12, false);
|
||||
markEdge(e21, true);
|
||||
edgeData.setDelaunay(true);
|
||||
oppositeData.setDelaunay(true);
|
||||
}
|
||||
};
|
||||
|
||||
lastEdge = e20;
|
||||
}
|
||||
|
||||
bool addNextEdgeForFirstCollinearPoints{ };
|
||||
if (firstNonCollinearIndex != 2)
|
||||
// Create initial edge
|
||||
{
|
||||
// In case there are multiple collinear points at the start, we need to store a different edge after adding a vertex
|
||||
// Depending on the orientation of an edge of the initial triangle and the first (or any other) collinear point,
|
||||
// we need to get the next/previous edge after adding the point
|
||||
// All of those collinear points will be on the same side of the edge, so this only needs to be calculated once
|
||||
|
||||
TVertex firstNonCollinearVertexAtStart = v2;
|
||||
Vector2 firstNonCollinearVertexPositionAtStart = getPoint(firstNonCollinearVertexAtStart.index);
|
||||
|
||||
Vector2 collinearPoint = getPoint(sortedPoints[2]);
|
||||
|
||||
math::Orientation orientation = orient2d(
|
||||
getPoint(v0.index),
|
||||
firstNonCollinearVertexPositionAtStart,
|
||||
collinearPoint
|
||||
);
|
||||
|
||||
addNextEdgeForFirstCollinearPoints = orientation == detria::math::Orientation::CCW;
|
||||
THalfEdge e10 = _topology.createNewEdge(v1, v0, { }, { });
|
||||
markInitialBoundaryEdge(e10);
|
||||
lastEdge = e10;
|
||||
}
|
||||
|
||||
// Add points
|
||||
for (size_t i = 2; i < _numPoints; ++i)
|
||||
for (size_t i = startIndex + 2; i < endIndex; ++i)
|
||||
{
|
||||
if (i == firstNonCollinearIndex) DETRIA_UNLIKELY
|
||||
{
|
||||
// Vertex is part of first triangle, which was already added at the start
|
||||
continue;
|
||||
}
|
||||
|
||||
const Idx& originalIndex = sortedPoints[i];
|
||||
TVertex vertex(originalIndex);
|
||||
|
||||
Vector2 position = getPoint(originalIndex);
|
||||
|
||||
auto isEdgeVisible = [&](const THalfEdge& edge)
|
||||
@@ -3396,23 +3332,12 @@ namespace detria
|
||||
return orientation == math::Orientation::CCW;
|
||||
};
|
||||
|
||||
auto getNextEdgeAlongConvexHull = [&](THalfEdge halfEdge)
|
||||
{
|
||||
const HalfEdge& opposite = _topology.getEdge(_topology.getOpposite(halfEdge));
|
||||
return opposite.nextEdge;
|
||||
};
|
||||
|
||||
auto getPrevEdgeAlongConvexHull = [&](THalfEdge halfEdge)
|
||||
{
|
||||
THalfEdge prevEdge = _topology.getEdge(halfEdge).prevEdge;
|
||||
return _topology.getOpposite(prevEdge);
|
||||
};
|
||||
|
||||
// Start checking edges, and find the first and last one that is visible from the current point
|
||||
// The vertex of `lastEdge` is guaranteed to be visible, so it's a good starting point
|
||||
|
||||
THalfEdge lastVisibleForwards = lastEdge;
|
||||
THalfEdge lastVisibleBackwards = getPrevEdgeAlongConvexHull(lastEdge);
|
||||
THalfEdge prevVisibleBackwards = lastEdge;
|
||||
|
||||
// Check forwards
|
||||
while (true)
|
||||
@@ -3432,22 +3357,30 @@ namespace detria
|
||||
{
|
||||
if (isEdgeVisible(lastVisibleBackwards))
|
||||
{
|
||||
prevVisibleBackwards = lastVisibleBackwards;
|
||||
lastVisibleBackwards = getPrevEdgeAlongConvexHull(lastVisibleBackwards);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastVisibleBackwards = getNextEdgeAlongConvexHull(lastVisibleBackwards);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DETRIA_ASSERT_MSG(lastVisibleForwards != lastVisibleBackwards, "No visible edges found");
|
||||
if (lastVisibleForwards == prevVisibleBackwards) DETRIA_UNLIKELY
|
||||
{
|
||||
// All points are collinear so far, so only add an edge
|
||||
|
||||
TVertex pVertex(originalIndex);
|
||||
TVertex prevVertex(sortedPoints[i - 1]);
|
||||
lastEdge = _topology.createNewEdge(vertex, prevVertex, { }, lastEdge);
|
||||
markInitialBoundaryEdge(lastEdge);
|
||||
continue;
|
||||
}
|
||||
|
||||
allCollinear = false;
|
||||
|
||||
// Add new edges
|
||||
// If delaunay, then add edges that we are about to remove to the list of edges to check
|
||||
THalfEdge current = lastVisibleBackwards;
|
||||
THalfEdge current = prevVisibleBackwards;
|
||||
THalfEdge lastAddedEdge{ };
|
||||
|
||||
while (current != lastVisibleForwards)
|
||||
@@ -3461,11 +3394,11 @@ namespace detria
|
||||
|
||||
THalfEdge edge1i = lastAddedEdge.isValid()
|
||||
? lastAddedEdge
|
||||
: _topology.createNewEdge(pVertex, currentVertex, { }, edge0.prevEdge);
|
||||
: _topology.createNewEdge(vertex, currentVertex, { }, edge0.prevEdge);
|
||||
HalfEdge& edge1 = _topology.getEdge(edge1i);
|
||||
|
||||
THalfEdge oppositeEdge0i = _topology.getOpposite(current);
|
||||
THalfEdge edge2i = _topology.createNewEdge(pVertex, nextVertex, edge1.prevEdge, oppositeEdge0i);
|
||||
THalfEdge edge2i = _topology.createNewEdge(vertex, nextVertex, edge1.prevEdge, oppositeEdge0i);
|
||||
|
||||
// Update boundary status
|
||||
_topology.getEdgeData(oppositeEdge0i).setBoundary(false); // Previously boundary edge, but it became interior now
|
||||
@@ -3481,55 +3414,67 @@ namespace detria
|
||||
if (delaunay)
|
||||
{
|
||||
// Make sure the newly added triangle meets the delaunay criteria
|
||||
DETRIA_CHECK(delaunayEdgeFlip(pVertex, edge0i));
|
||||
addEdgeToFlipCheck(edge0i, vertex);
|
||||
}
|
||||
|
||||
current = nextEdge;
|
||||
}
|
||||
|
||||
// Finally, mark lastAddedEdge as boundary
|
||||
{
|
||||
_topology.getEdgeData(_topology.getOpposite(lastAddedEdge)).setBoundary(true);
|
||||
}
|
||||
_topology.getEdgeData(_topology.getOpposite(lastAddedEdge)).setBoundary(true);
|
||||
|
||||
if (i < firstNonCollinearIndex)
|
||||
lastEdge = lastAddedEdge;
|
||||
|
||||
if (delaunay)
|
||||
{
|
||||
lastEdge = addNextEdgeForFirstCollinearPoints
|
||||
? getNextEdgeAlongConvexHull(lastAddedEdge)
|
||||
: getPrevEdgeAlongConvexHull(lastAddedEdge);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastEdge = lastAddedEdge;
|
||||
delaunayEdgeFlip();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use `_delaunayCheckStack` in this function
|
||||
// It will always be empty when this function returns
|
||||
inline THalfEdge getNextEdgeAlongConvexHull(THalfEdge halfEdge) const
|
||||
{
|
||||
const HalfEdge& opposite = _topology.getEdge(_topology.getOpposite(halfEdge));
|
||||
return opposite.nextEdge;
|
||||
}
|
||||
|
||||
inline THalfEdge getPrevEdgeAlongConvexHull(THalfEdge halfEdge) const
|
||||
{
|
||||
THalfEdge prevEdge = _topology.getEdge(halfEdge).prevEdge;
|
||||
return _topology.getOpposite(prevEdge);
|
||||
}
|
||||
|
||||
// Add an edge which should be checked for delaunay edge flip
|
||||
void addEdgeToFlipCheck(THalfEdge e, TVertex oppositeVertex)
|
||||
{
|
||||
HalfEdge& edge = _topology.getEdge(e);
|
||||
HalfEdge& oppositeEdge = _topology.getEdge(_topology.getOpposite(e));
|
||||
|
||||
edge.data.setDelaunay(false);
|
||||
oppositeEdge.data.setDelaunay(false);
|
||||
|
||||
_delaunayCheckStack.emplace_back(TopologyEdgeWithVertices
|
||||
{
|
||||
edge.vertex,
|
||||
oppositeEdge.vertex,
|
||||
e,
|
||||
oppositeVertex
|
||||
});
|
||||
}
|
||||
|
||||
bool delaunayEdgeFlip(const TVertex& justAddedVertex, const THalfEdge& oppositeEdge)
|
||||
{
|
||||
auto addEdgeToCheck = [&](THalfEdge e)
|
||||
{
|
||||
HalfEdge& edge = _topology.getEdge(e);
|
||||
HalfEdge& opposite = _topology.getEdge(_topology.getOpposite(e));
|
||||
addEdgeToFlipCheck(oppositeEdge, justAddedVertex);
|
||||
return delaunayEdgeFlip();
|
||||
}
|
||||
|
||||
edge.data.setDelaunay(false);
|
||||
opposite.data.setDelaunay(false);
|
||||
|
||||
_delaunayCheckStack.emplace_back(TopologyEdgeWithVertices
|
||||
{
|
||||
edge.vertex,
|
||||
opposite.vertex,
|
||||
e
|
||||
});
|
||||
};
|
||||
|
||||
addEdgeToCheck(oppositeEdge);
|
||||
|
||||
// Flip edges
|
||||
// Use `_delaunayCheckStack` in this function
|
||||
// It will always be empty when this function returns
|
||||
bool delaunayEdgeFlip()
|
||||
{
|
||||
// Flip edges if needed
|
||||
while (!_delaunayCheckStack.empty())
|
||||
{
|
||||
TopologyEdgeWithVertices edgeWithVertices = _delaunayCheckStack.back();
|
||||
@@ -3594,6 +3539,18 @@ namespace detria
|
||||
Vector2 otherVertex0Position = getPoint(otherVertex0.index);
|
||||
Vector2 otherVertex1Position = getPoint(otherVertex1.index);
|
||||
|
||||
// A diagonal of a non-convex or degenerate quadrilateral
|
||||
// cannot be flipped. This can occur while re-triangulating
|
||||
// around a constrained edge, and the incircle predicate
|
||||
// requires its first three points to be counterclockwise.
|
||||
if (orient2d(vertex0Position, vertex1Position,
|
||||
otherVertex1Position) != math::Orientation::CCW)
|
||||
{
|
||||
edgeData.setDelaunay(true);
|
||||
oppositeEdgeData.setDelaunay(true);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO?: maybe we could allow user-defined functions to decide if an edge should be flipped
|
||||
// That would enable other metrics, e.g. minimize edge length, flip based on the aspect ratio of the triangles, etc.
|
||||
// https://people.eecs.berkeley.edu/~jrs/papers/elemj.pdf
|
||||
@@ -3605,20 +3562,21 @@ namespace detria
|
||||
// Flip edge
|
||||
// The edge is always flippable if we get here, no need to do orientation checks
|
||||
|
||||
TVertex oppositeVertex = edgeWithVertices.oppositeVertex;
|
||||
_topology.flipEdge(e01);
|
||||
|
||||
// Flipping an edge might require other edges to be flipped too
|
||||
// Only check edges which can be non-delaunay
|
||||
if (justAddedVertex.index == otherVertex0.index)
|
||||
if (oppositeVertex.index == otherVertex0.index)
|
||||
{
|
||||
addEdgeToCheck(edge01.prevEdge);
|
||||
addEdgeToCheck(edge01.nextEdge);
|
||||
addEdgeToFlipCheck(edge01.prevEdge, oppositeVertex);
|
||||
addEdgeToFlipCheck(edge01.nextEdge, oppositeVertex);
|
||||
}
|
||||
else
|
||||
{
|
||||
DETRIA_DEBUG_ASSERT(justAddedVertex.index == otherVertex1.index);
|
||||
addEdgeToCheck(edge10.nextEdge);
|
||||
addEdgeToCheck(edge10.prevEdge);
|
||||
DETRIA_DEBUG_ASSERT(oppositeVertex.index == otherVertex1.index);
|
||||
addEdgeToFlipCheck(edge10.nextEdge, oppositeVertex);
|
||||
addEdgeToFlipCheck(edge10.prevEdge, oppositeVertex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4309,6 +4267,7 @@ namespace detria
|
||||
checkedTriangles[size_t(startingTriangle.index)] = true;
|
||||
|
||||
CollectionWithAllocator<TriangleIndex>& trianglesToCheck = _classifyTriangles_TrianglesToCheck;
|
||||
trianglesToCheck.clear();
|
||||
trianglesToCheck.push_back(startingTriangle);
|
||||
|
||||
if (startingEdgeType == EdgeType::AutoDetect)
|
||||
@@ -4424,7 +4383,8 @@ namespace detria
|
||||
else
|
||||
{
|
||||
// We are outside and crossing a hole, or we are inside and crossing an outline
|
||||
if (currentTriangleLocationData->parentPolylineIndex == *polylineIndex) DETRIA_LIKELY
|
||||
if (currentTriangleLocationData->parentPolylineIndex.has_value() &&
|
||||
*currentTriangleLocationData->parentPolylineIndex == *polylineIndex) DETRIA_LIKELY
|
||||
{
|
||||
// Valid case, the neighbor triangle is the opposite location
|
||||
// Set its polyline index to the current polyline's parent
|
||||
@@ -4592,6 +4552,7 @@ namespace detria
|
||||
TVertex v0;
|
||||
TVertex v1;
|
||||
THalfEdge edge;
|
||||
TVertex oppositeVertex;
|
||||
};
|
||||
|
||||
CollectionWithAllocator<TopologyEdgeWithVertices> _delaunayCheckStack;
|
||||
@@ -4607,6 +4568,7 @@ namespace detria
|
||||
|
||||
#undef DETRIA_LIKELY
|
||||
#undef DETRIA_UNLIKELY
|
||||
#undef DETRIA_EXPORT
|
||||
}
|
||||
|
||||
#endif // DETRIA_HPP_INCLUDED
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@
|
||||
/** @file libbg/pca.cpp
|
||||
*
|
||||
* @brief
|
||||
* Principle Component Analysis.
|
||||
* Principal Component Analysis.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -74,7 +74,7 @@ bg_pca(point_t *c, vect_t *xa, vect_t *ya, vect_t *za, size_t npnts, const point
|
||||
}
|
||||
|
||||
// 3. Perform SVD
|
||||
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeThinU);
|
||||
Eigen::JacobiSVD<Eigen::MatrixXd> svd(A, Eigen::ComputeFullU);
|
||||
|
||||
// 4. Extract the vectors from the U matrix
|
||||
vect_t xaxis, yaxis, zaxis;
|
||||
|
||||
+44
-73
@@ -2620,20 +2620,50 @@ bg_lseg3_lseg3_parallel(const point_t sg1pt1, const point_t sg1pt2,
|
||||
}
|
||||
|
||||
extern "C" int
|
||||
bg_plane_pt_nrml(plane_t *p, point_t pt, vect_t nrml)
|
||||
bg_plane_pt_nrml(plane_t *p, const point_t pt, const vect_t nrml)
|
||||
{
|
||||
if (!p)
|
||||
vect_t normal;
|
||||
fastf_t normal_length;
|
||||
|
||||
if (!p || !pt || !nrml)
|
||||
return -1;
|
||||
vect_t unrml;
|
||||
VMOVE(unrml, nrml);
|
||||
VUNITIZE(nrml);
|
||||
(*p)[0] = unrml[0];
|
||||
(*p)[1] = unrml[1];
|
||||
(*p)[2] = unrml[2];
|
||||
(*p)[3] = unrml[0]*pt[0] + unrml[1]*pt[1] + unrml[2]*pt[2];
|
||||
|
||||
VMOVE(normal, nrml);
|
||||
normal_length = MAGNITUDE(normal);
|
||||
if (normal_length <= SMALL_FASTF)
|
||||
return -1;
|
||||
|
||||
VSCALE(normal, normal, 1.0 / normal_length);
|
||||
(*p)[X] = normal[X];
|
||||
(*p)[Y] = normal[Y];
|
||||
(*p)[Z] = normal[Z];
|
||||
(*p)[H] = VDOT(normal, pt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
plane_coordinate_frame(point_t origin, vect_t xaxis, vect_t yaxis, const plane_t *p)
|
||||
{
|
||||
vect_t normal;
|
||||
fastf_t normal_length;
|
||||
|
||||
VSET(normal, (*p)[X], (*p)[Y], (*p)[Z]);
|
||||
normal_length = MAGNITUDE(normal);
|
||||
if (normal_length <= SMALL_FASTF)
|
||||
return -1;
|
||||
|
||||
VSCALE(normal, normal, 1.0 / normal_length);
|
||||
VSCALE(origin, normal, (*p)[H] / normal_length);
|
||||
bn_vec_perp(xaxis, normal);
|
||||
VUNITIZE(xaxis);
|
||||
VCROSS(yaxis, normal, xaxis);
|
||||
VUNITIZE(yaxis);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Use PCA fit a plane to vertex points
|
||||
extern "C" int
|
||||
bg_fit_plane(point_t *c, vect_t *n, size_t npnts, point_t *pnts)
|
||||
@@ -2664,40 +2694,11 @@ bg_plane_closest_pt(fastf_t *u, fastf_t *v, plane_t *p, point_t *pt)
|
||||
return -1;
|
||||
|
||||
point_t origin;
|
||||
vect_t xaxis, yaxis, zaxis;
|
||||
VSET(zaxis, (*p)[0], (*p)[1], (*p)[2]);
|
||||
fastf_t x, y, z, d;
|
||||
x = fabs((*p)[0]);
|
||||
y = fabs((*p)[1]);
|
||||
z = fabs((*p)[2]);
|
||||
d = 0.0;
|
||||
if ( y >= x && y >= z ) {
|
||||
d = x;
|
||||
x = y;
|
||||
y = d;
|
||||
} else if ( z >= x && z >= y ) {
|
||||
d = x;
|
||||
x = z;
|
||||
z = d;
|
||||
}
|
||||
if ( x > SMALL_FASTF ) {
|
||||
y /= x;
|
||||
z /= x;
|
||||
d = x*sqrt(1.0 + y*y + z*z);
|
||||
} else if ( x > 0.0 && x < MAX_FASTF ) {
|
||||
d = x;
|
||||
}
|
||||
vect_t xaxis, yaxis;
|
||||
|
||||
if (d < 0 || NEAR_ZERO(d, SMALL_FASTF))
|
||||
if (plane_coordinate_frame(origin, xaxis, yaxis, p))
|
||||
return -1;
|
||||
|
||||
VMOVE(origin, zaxis);
|
||||
VSCALE(origin, origin, 1/d*(*p)[3]);
|
||||
VUNITIZE(zaxis);
|
||||
bn_vec_perp(xaxis, zaxis);
|
||||
VUNITIZE(xaxis);
|
||||
VCROSS(yaxis, zaxis, xaxis);
|
||||
VUNITIZE(yaxis);
|
||||
vect_t vc;
|
||||
VSUB2(vc, *pt, origin);
|
||||
|
||||
@@ -2711,45 +2712,15 @@ bg_plane_closest_pt(fastf_t *u, fastf_t *v, plane_t *p, point_t *pt)
|
||||
extern "C" int
|
||||
bg_plane_pt_at(point_t *pt, plane_t *p, fastf_t u, fastf_t v)
|
||||
{
|
||||
if (!pt)
|
||||
if (!pt || !p)
|
||||
return -1;
|
||||
|
||||
point_t origin;
|
||||
vect_t xaxis, yaxis, zaxis;
|
||||
VSET(zaxis, (*p)[0], (*p)[1], (*p)[2]);
|
||||
fastf_t x, y, z, d;
|
||||
x = fabs((*p)[0]);
|
||||
y = fabs((*p)[1]);
|
||||
z = fabs((*p)[2]);
|
||||
d = 0.0;
|
||||
if ( y >= x && y >= z ) {
|
||||
d = x;
|
||||
x = y;
|
||||
y = d;
|
||||
} else if ( z >= x && z >= y ) {
|
||||
d = x;
|
||||
x = z;
|
||||
z = d;
|
||||
}
|
||||
if ( x > SMALL_FASTF ) {
|
||||
y /= x;
|
||||
z /= x;
|
||||
d = x*sqrt(1.0 + y*y + z*z);
|
||||
} else if ( x > 0.0 && x < MAX_FASTF ) {
|
||||
d = x;
|
||||
}
|
||||
vect_t xaxis, yaxis;
|
||||
|
||||
if (d < 0 || NEAR_ZERO(d, SMALL_FASTF))
|
||||
if (plane_coordinate_frame(origin, xaxis, yaxis, p))
|
||||
return -1;
|
||||
|
||||
VMOVE(origin, zaxis);
|
||||
VSCALE(origin, origin, 1/d*(*p)[3]);
|
||||
VUNITIZE(zaxis);
|
||||
bn_vec_perp(xaxis, zaxis);
|
||||
VUNITIZE(xaxis);
|
||||
VCROSS(yaxis, zaxis, xaxis);
|
||||
VUNITIZE(yaxis);
|
||||
|
||||
VSCALE(xaxis, xaxis, u);
|
||||
VSCALE(yaxis, yaxis, v);
|
||||
VADD2(origin, origin, xaxis);
|
||||
|
||||
+51
-72
@@ -83,7 +83,7 @@ bg_polygon_view_bbox(point2d_t *bmin, point2d_t *bmax, struct bg_polygon *p, mat
|
||||
// contours. ONLY considering positive contour points.
|
||||
for (size_t i = 0; i < p->num_contours; i++) {
|
||||
struct bg_poly_contour *c = &p->contour[i];
|
||||
if (!c->num_points)
|
||||
if ((p->hole && p->hole[i]) || !c->num_points)
|
||||
continue;
|
||||
for (size_t j = 0; j < c->num_points; j++) {
|
||||
point_t vpoint;
|
||||
@@ -143,64 +143,45 @@ bg_3d_polygon_area(fastf_t *area, size_t npts, const point_t *pts)
|
||||
int
|
||||
bg_3d_polygon_centroid(point_t *cent, size_t npts, const point_t *pts)
|
||||
{
|
||||
size_t i;
|
||||
fastf_t x_0 = 0.0;
|
||||
fastf_t x_1 = 0.0;
|
||||
fastf_t y_0 = 0.0;
|
||||
fastf_t y_1 = 0.0;
|
||||
fastf_t z_0 = 0.0;
|
||||
fastf_t z_1 = 0.0;
|
||||
fastf_t a = 0.0;
|
||||
fastf_t signedArea = 0.0;
|
||||
point_t centroid = VINIT_ZERO;
|
||||
vect_t normal = VINIT_ZERO;
|
||||
vect_t edge0, edge1, cross;
|
||||
fastf_t signed_area = 0.0;
|
||||
|
||||
if (!pts || !cent || npts < 3)
|
||||
return 1;
|
||||
/* Calculate Centroid projection for face for x-y-plane */
|
||||
for (i = 0; i < npts-1; i++) {
|
||||
x_0 = pts[i][0];
|
||||
y_0 = pts[i][1];
|
||||
x_1 = pts[i+1][0];
|
||||
y_1 = pts[i+1][1];
|
||||
a = x_0 *y_1 - x_1*y_0;
|
||||
signedArea += a;
|
||||
*cent[0] += (x_0 + x_1)*a;
|
||||
*cent[1] += (y_0 + y_1)*a;
|
||||
|
||||
for (size_t i = 1; i + 1 < npts; i++) {
|
||||
VSUB2(edge0, pts[i], pts[0]);
|
||||
VSUB2(edge1, pts[i + 1], pts[0]);
|
||||
VCROSS(cross, edge0, edge1);
|
||||
VADD2(normal, normal, cross);
|
||||
}
|
||||
x_0 = pts[i][0];
|
||||
y_0 = pts[i][1];
|
||||
x_1 = pts[0][0];
|
||||
y_1 = pts[0][1];
|
||||
a = x_0 *y_1 - x_1*y_0;
|
||||
signedArea += a;
|
||||
*cent[0] += (x_0 + x_1)*a;
|
||||
*cent[1] += (y_0 + y_1)*a;
|
||||
if (MAGNITUDE(normal) <= SMALL_FASTF)
|
||||
return 1;
|
||||
VUNITIZE(normal);
|
||||
|
||||
signedArea *= 0.5;
|
||||
*cent[0] /= (6.0*signedArea);
|
||||
*cent[1] /= (6.0*signedArea);
|
||||
for (size_t i = 1; i + 1 < npts; i++) {
|
||||
point_t triangle_centroid;
|
||||
fastf_t triangle_area;
|
||||
|
||||
/* calculate Centroid projection for face for x-z-plane */
|
||||
|
||||
signedArea = 0.0;
|
||||
for (i = 0; i < npts-1; i++) {
|
||||
x_0 = pts[i][0];
|
||||
z_0 = pts[i][2];
|
||||
x_1 = pts[i+1][0];
|
||||
z_1 = pts[i+1][2];
|
||||
a = x_0 *z_1 - x_1*z_0;
|
||||
signedArea += a;
|
||||
*cent[2] += (z_0 + z_1)*a;
|
||||
VSUB2(edge0, pts[i], pts[0]);
|
||||
VSUB2(edge1, pts[i + 1], pts[0]);
|
||||
VCROSS(cross, edge0, edge1);
|
||||
triangle_area = VDOT(cross, normal);
|
||||
signed_area += triangle_area;
|
||||
VADD2(triangle_centroid, pts[0], pts[i]);
|
||||
VADD2(triangle_centroid, triangle_centroid, pts[i + 1]);
|
||||
VSCALE(triangle_centroid, triangle_centroid, triangle_area / 3.0);
|
||||
VADD2(centroid, centroid, triangle_centroid);
|
||||
}
|
||||
x_0 = pts[i][0];
|
||||
z_0 = pts[i][2];
|
||||
x_1 = pts[0][0];
|
||||
z_1 = pts[0][2];
|
||||
a = x_0 *z_1 - x_1*z_0;
|
||||
signedArea += a;
|
||||
*cent[2] += (z_0 + z_1)*a;
|
||||
|
||||
signedArea *= 0.5;
|
||||
*cent[2] /= (6.0*signedArea);
|
||||
if (NEAR_ZERO(signed_area, SMALL_FASTF))
|
||||
return 1;
|
||||
|
||||
VSCALE(centroid, centroid, 1.0 / signed_area);
|
||||
VMOVE(*cent, centroid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -263,6 +244,7 @@ bg_3d_polygon_make_pnts_planes(size_t *npts, point_t **pts, size_t neqs, const p
|
||||
|
||||
|
||||
struct sort_ccw_data {
|
||||
point_t centroid;
|
||||
vect_t x_axis;
|
||||
vect_t y_axis;
|
||||
};
|
||||
@@ -272,20 +254,26 @@ static int
|
||||
sort_ccw_3d(const void *left, const void *right, void *context)
|
||||
{
|
||||
const struct sort_ccw_data *data = (const struct sort_ccw_data *)context;
|
||||
const fastf_t *left_point = (const fastf_t *)left;
|
||||
const fastf_t *right_point = (const fastf_t *)right;
|
||||
double left_angle = atan2(VDOT(left_point, data->y_axis),
|
||||
VDOT(left_point, data->x_axis));
|
||||
double right_angle = atan2(VDOT(right_point, data->y_axis),
|
||||
VDOT(right_point, data->x_axis));
|
||||
const point_t *left_point = (const point_t *)left;
|
||||
const point_t *right_point = (const point_t *)right;
|
||||
vect_t left_offset, right_offset;
|
||||
double left_angle, right_angle;
|
||||
double left_radius, right_radius;
|
||||
|
||||
VSUB2(left_offset, *left_point, data->centroid);
|
||||
VSUB2(right_offset, *right_point, data->centroid);
|
||||
left_angle = atan2(VDOT(left_offset, data->y_axis),
|
||||
VDOT(left_offset, data->x_axis));
|
||||
right_angle = atan2(VDOT(right_offset, data->y_axis),
|
||||
VDOT(right_offset, data->x_axis));
|
||||
|
||||
if (left_angle < right_angle)
|
||||
return -1;
|
||||
if (left_angle > right_angle)
|
||||
return 1;
|
||||
|
||||
double left_radius = MAGSQ(left_point);
|
||||
double right_radius = MAGSQ(right_point);
|
||||
left_radius = MAGSQ(left_offset);
|
||||
right_radius = MAGSQ(right_offset);
|
||||
if (left_radius < right_radius)
|
||||
return -1;
|
||||
if (left_radius > right_radius)
|
||||
@@ -298,11 +286,10 @@ int
|
||||
bg_3d_polygon_sort_ccw(size_t npts, point_t *pts, plane_t cmp)
|
||||
{
|
||||
size_t i;
|
||||
point_t centroid;
|
||||
vect_t normal;
|
||||
struct sort_ccw_data data;
|
||||
|
||||
if (!pts || npts < 3)
|
||||
if (!pts || !cmp || npts < 3)
|
||||
return 1;
|
||||
if (MAGNITUDE(cmp) < VDIVIDE_TOL)
|
||||
return 1;
|
||||
@@ -312,21 +299,13 @@ bg_3d_polygon_sort_ccw(size_t npts, point_t *pts, plane_t cmp)
|
||||
bn_vec_ortho(data.x_axis, normal);
|
||||
VCROSS(data.y_axis, normal, data.x_axis);
|
||||
|
||||
/* Angular ordering is about the polygon center, not the model origin.
|
||||
* Translate temporarily so the comparator only needs the plane axes. */
|
||||
VSETALL(centroid, 0.0);
|
||||
VSETALL(data.centroid, 0.0);
|
||||
for (i = 0; i < npts; i++)
|
||||
VADD2(centroid, centroid, pts[i]);
|
||||
VSCALE(centroid, centroid, 1.0 / (double)npts);
|
||||
|
||||
for (i = 0; i < npts; i++)
|
||||
VSUB2(pts[i], pts[i], centroid);
|
||||
VADD2(data.centroid, data.centroid, pts[i]);
|
||||
VSCALE(data.centroid, data.centroid, 1.0 / (fastf_t)npts);
|
||||
|
||||
bu_sort(pts, npts, sizeof(point_t), sort_ccw_3d, &data);
|
||||
|
||||
for (i = 0; i < npts; i++)
|
||||
VADD2(pts[i], pts[i], centroid);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+1732
-534
File diff suppressed because it is too large
Load Diff
+858
-134
File diff suppressed because it is too large
Load Diff
@@ -33,6 +33,10 @@ brlcad_addexec(bg_aabb_ray aabb_ray.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_aabb_ray COMMAND bg_aabb_ray)
|
||||
|
||||
brlcad_addexec(bg_spsr spsr.cpp "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_spsr COMMAND bg_spsr)
|
||||
|
||||
brlcad_addexec(bg_polygon_sort polygon_sort.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_polygon_sort COMMAND bg_polygon_sort)
|
||||
@@ -142,7 +146,7 @@ brlcad_add_test(NAME bg_plane_mk_plane_3pts_succeed COMMAND bg_test pla
|
||||
#
|
||||
# A-D are plane equation parameters, and P is the test point.
|
||||
brlcad_add_test(NAME bg_plane_pt_project_1 COMMAND bg_test plane_pt 6 0,0,1,1 2,2,1)
|
||||
brlcad_add_test(NAME bg_plane_pt_project_2 COMMAND bg_test plane_pt 6 0.737397,0.461098,0.493593,236.3 77.526016919831704,185.674010580054585,189.465011325663767)
|
||||
brlcad_add_test(NAME bg_plane_pt_project_2 COMMAND bg_test plane_pt 6 0.737397,0.461098,0.493593,236.3 77.526016919831704,185.674010580054585,189.465070961146)
|
||||
|
||||
# For function #7 (bg_coplanar_pts), the <args> format is as follows:
|
||||
#
|
||||
@@ -306,10 +310,36 @@ brlcad_addexec(bg_line_distance line_distance.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_line_distance COMMAND bg_line_distance)
|
||||
|
||||
# ************ plane coordinate tests ***********
|
||||
|
||||
brlcad_addexec(bg_plane_coordinates plane_coordinates.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_plane_coordinates COMMAND bg_plane_coordinates)
|
||||
|
||||
# ************ pca.c tests ***********
|
||||
|
||||
brlcad_addexec(bg_pca pca.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_pca COMMAND bg_pca)
|
||||
|
||||
# ************ polygon.c tests ***********
|
||||
|
||||
brlcad_addexec(bg_polygon polygon.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_polygon COMMAND bg_polygon)
|
||||
|
||||
brlcad_addexec(bg_polygon_triangulate polygon_triangulate.c "${BG_TEST_LIBS}" TEST)
|
||||
brlcad_addexec(bg_polygon_triangulate_clean polygon_triangulate_clean.c "${BG_TEST_LIBS}" TEST)
|
||||
brlcad_addexec(bg_polygon_triangulate_strict polygon_triangulate_strict.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_polygon_triangulate COMMAND bg_polygon_triangulate)
|
||||
brlcad_add_test(NAME bg_polygon_triangulate_clean COMMAND bg_polygon_triangulate_clean)
|
||||
brlcad_add_test(NAME bg_polygon_triangulate_strict COMMAND bg_polygon_triangulate_strict)
|
||||
|
||||
# ************ RTree tests *************
|
||||
brlcad_addexec(bg_rtree rtree.cpp "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_rtree_large_identical_rects COMMAND bg_rtree)
|
||||
|
||||
# ************ chull.c tests ***********
|
||||
# 2D Convex Hull tests
|
||||
@@ -448,6 +478,10 @@ brlcad_addexec(bg_trimesh_area trimesh_area.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
#BRLCAD_ADD_TEST(NAME bg_trimesh_area COMMAND bg_trimesh_area)
|
||||
|
||||
brlcad_addexec(bg_trimesh_solid trimesh_solid.c "${BG_TEST_LIBS}" TEST)
|
||||
|
||||
brlcad_add_test(NAME bg_trimesh_solid COMMAND bg_trimesh_solid)
|
||||
|
||||
# ************ trimesh_repair tests ***********
|
||||
|
||||
brlcad_addexec(bg_trimesh_repair trimesh_repair.cpp "${BG_TEST_LIBS}" TEST)
|
||||
@@ -495,6 +529,8 @@ cmakefiles(
|
||||
plane_dist.c
|
||||
plane_isect.c
|
||||
plane_pt.c
|
||||
polygon_triangulate_strict.c
|
||||
rtree.cpp
|
||||
CMakeLists.txt
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/* P C A . C
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "bn.h"
|
||||
#include "bu.h"
|
||||
#include "bg.h"
|
||||
|
||||
|
||||
static int
|
||||
orthonormal_axes(const vect_t xaxis, const vect_t yaxis, const vect_t zaxis)
|
||||
{
|
||||
return !NEAR_EQUAL(MAGSQ(xaxis), 1.0, BN_TOL_DIST)
|
||||
|| !NEAR_EQUAL(MAGSQ(yaxis), 1.0, BN_TOL_DIST)
|
||||
|| !NEAR_EQUAL(MAGSQ(zaxis), 1.0, BN_TOL_DIST)
|
||||
|| !NEAR_ZERO(VDOT(xaxis, yaxis), BN_TOL_DIST)
|
||||
|| !NEAR_ZERO(VDOT(xaxis, zaxis), BN_TOL_DIST)
|
||||
|| !NEAR_ZERO(VDOT(yaxis, zaxis), BN_TOL_DIST);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const point_t one_point[] = {{1.0, 2.0, 3.0}};
|
||||
const point_t two_points[] = {{1.0, 2.0, 3.0}, {5.0, 2.0, 3.0}};
|
||||
const point_t one_center = {1.0, 2.0, 3.0};
|
||||
const point_t two_center = {3.0, 2.0, 3.0};
|
||||
const vect_t xaxis_expected = {1.0, 0.0, 0.0};
|
||||
point_t center;
|
||||
vect_t xaxis, yaxis, zaxis;
|
||||
fastf_t alignment;
|
||||
int failed = 0;
|
||||
|
||||
bu_setprogname(argv[0]);
|
||||
if (argc != 1)
|
||||
return 1;
|
||||
|
||||
if (bg_pca(¢er, &xaxis, &yaxis, &zaxis, 1, one_point) != BRLCAD_OK ||
|
||||
!VNEAR_EQUAL(center, one_center, BN_TOL_DIST) ||
|
||||
orthonormal_axes(xaxis, yaxis, zaxis)) {
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
if (bg_pca(¢er, &xaxis, &yaxis, &zaxis, 2, two_points) != BRLCAD_OK ||
|
||||
!VNEAR_EQUAL(center, two_center, BN_TOL_DIST) ||
|
||||
orthonormal_axes(xaxis, yaxis, zaxis)) {
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
alignment = VDOT(xaxis, xaxis_expected);
|
||||
if (!NEAR_EQUAL(alignment * alignment, 1.0, BN_TOL_DIST))
|
||||
failed = 1;
|
||||
|
||||
return failed;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* P L A N E _ C O O R D I N A T E S . C
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "bn.h"
|
||||
#include "bu.h"
|
||||
#include "bg.h"
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const point_t plane_point = {0.0, 0.0, 3.0};
|
||||
const vect_t original_normal = {0.0, 0.0, 2.0};
|
||||
const vect_t expected_normal = {0.0, 0.0, 1.0};
|
||||
vect_t normal;
|
||||
vect_t zero_normal = VINIT_ZERO;
|
||||
plane_t plane;
|
||||
plane_t unnormalized_plane = {0.0, 0.0, 2.0, 6.0};
|
||||
point_t query = {2.0, 2.0, 3.0};
|
||||
point_t off_plane_query = {2.0, 2.0, 5.0};
|
||||
const point_t expected_projection = {2.0, 2.0, 3.0};
|
||||
point_t reconstructed = VINIT_ZERO;
|
||||
fastf_t u, v;
|
||||
int failed = 0;
|
||||
|
||||
bu_setprogname(argv[0]);
|
||||
if (argc != 1)
|
||||
return 1;
|
||||
|
||||
VMOVE(normal, original_normal);
|
||||
if (bg_plane_pt_nrml(&plane, plane_point, normal) ||
|
||||
!VNEAR_EQUAL(plane, expected_normal, BN_TOL_DIST) ||
|
||||
!NEAR_EQUAL(plane[H], 3.0, BN_TOL_DIST) ||
|
||||
!VNEAR_EQUAL(normal, original_normal, BN_TOL_DIST)) {
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
if (bg_plane_pt_nrml(&plane, plane_point, zero_normal) != -1)
|
||||
failed = 1;
|
||||
|
||||
if (bg_plane_closest_pt(&u, &v, &unnormalized_plane, &query) ||
|
||||
bg_plane_pt_at(&reconstructed, &unnormalized_plane, u, v) ||
|
||||
!VNEAR_EQUAL(reconstructed, query, BN_TOL_DIST)) {
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
if (bg_plane_closest_pt(&u, &v, &unnormalized_plane, &off_plane_query) ||
|
||||
bg_plane_pt_at(&reconstructed, &unnormalized_plane, u, v) ||
|
||||
!VNEAR_EQUAL(reconstructed, expected_projection, BN_TOL_DIST)) {
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
return failed;
|
||||
}
|
||||
@@ -68,6 +68,8 @@ main(int argc, const char **argv)
|
||||
size_t num_points = 0;
|
||||
int num_faces = 0;
|
||||
int *faces = NULL;
|
||||
point2d_t *out_points = NULL;
|
||||
int out_point_count = 0;
|
||||
|
||||
/* 44 point polygon derived from NIST MBE PMI sample 1 shape */
|
||||
point2d_t points[44] = {{0}};
|
||||
@@ -127,13 +129,20 @@ main(int argc, const char **argv)
|
||||
verts_ind[i] = (int)i;
|
||||
}
|
||||
|
||||
ret = bg_poly2tri_test(&faces, &num_faces, NULL, NULL, verts_ind, 44, NULL, NULL, 0, NULL, 0, (const point2d_t *)points);
|
||||
ret = bg_nested_poly_triangulate_clean(&faces, &num_faces,
|
||||
&out_points, &out_point_count, verts_ind, 44, NULL, NULL,
|
||||
0, NULL, 0, NULL, 0, (const point2d_t *)points, num_points,
|
||||
NULL);
|
||||
if (ret) {
|
||||
bu_log("test 1 ear clipping failure!\n");
|
||||
bu_log("test 1 cleanup failure!\n");
|
||||
return 1;
|
||||
} else {
|
||||
_tess_report(faces, num_faces, (const point2d_t *)points, 1, 1);
|
||||
_tess_report(faces, num_faces,
|
||||
(const point2d_t *)out_points, 1, 1);
|
||||
}
|
||||
bu_free(faces, "test 1 faces");
|
||||
bu_free(out_points, "test 1 points");
|
||||
bu_free(verts_ind, "test 1 indices");
|
||||
}
|
||||
|
||||
{
|
||||
@@ -177,14 +186,23 @@ main(int argc, const char **argv)
|
||||
|
||||
point2d_t *opnts = NULL;
|
||||
int noutpnts = 0;
|
||||
ret = bg_poly2tri_test(&faces, &num_faces, &opnts, &noutpnts, (const int *)outer, 4, (const int **)hole_array, (const size_t *)hcnts, hole_cnt, NULL, 0, (const point2d_t *)points);
|
||||
ret = bg_nested_poly_triangulate_clean(&faces, &num_faces, &opnts,
|
||||
&noutpnts, (const int *)outer, 4,
|
||||
(const int **)hole_array, (const size_t *)hcnts, hole_cnt,
|
||||
NULL, 0, NULL, 0, (const point2d_t *)points, num_points, NULL);
|
||||
if (ret) {
|
||||
bu_log("multiple hole splitting failure!\n");
|
||||
return 1;
|
||||
} else {
|
||||
bg_trimesh_2d_plot3("poly2tri_new_pnts.plot3", faces, num_faces, (const point2d_t *)opnts, noutpnts);
|
||||
if (plot_files)
|
||||
bg_trimesh_2d_plot3("poly2tri_new_pnts.plot3", faces,
|
||||
num_faces, (const point2d_t *)opnts, noutpnts);
|
||||
_tess_report(faces, num_faces, (const point2d_t *)opnts, 1, 1);
|
||||
}
|
||||
bu_free(faces, "multiple hole faces");
|
||||
bu_free(opnts, "multiple hole points");
|
||||
bu_free(hole_array, "multiple hole array");
|
||||
bu_free(hcnts, "multiple hole counts");
|
||||
}
|
||||
|
||||
/* Nested test case 1 */
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/* P O L Y G O N . C
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "bn.h"
|
||||
#include "bu.h"
|
||||
#include "bg.h"
|
||||
|
||||
|
||||
static int
|
||||
test_view_bbox(void)
|
||||
{
|
||||
point_t outer[] = {
|
||||
{0.0, 0.0, 0.0},
|
||||
{2.0, 0.0, 0.0},
|
||||
{2.0, 2.0, 0.0},
|
||||
{0.0, 2.0, 0.0}
|
||||
};
|
||||
point_t hole[] = {
|
||||
{100.0, 100.0, 0.0},
|
||||
{101.0, 100.0, 0.0},
|
||||
{101.0, 101.0, 0.0},
|
||||
{100.0, 101.0, 0.0}
|
||||
};
|
||||
struct bg_poly_contour contours[] = {
|
||||
{4, outer, 0},
|
||||
{4, hole, 0}
|
||||
};
|
||||
int holes[] = {0, 1};
|
||||
struct bg_polygon polygon = BG_POLYGON_NULL;
|
||||
mat_t model2view;
|
||||
point2d_t bmin, bmax;
|
||||
|
||||
polygon.num_contours = 2;
|
||||
polygon.hole = holes;
|
||||
polygon.contour = contours;
|
||||
MAT_IDN(model2view);
|
||||
bg_polygon_view_bbox(&bmin, &bmax, &polygon, model2view);
|
||||
|
||||
return !NEAR_EQUAL(bmin[X], 0.0, BN_TOL_DIST)
|
||||
|| !NEAR_EQUAL(bmin[Y], 0.0, BN_TOL_DIST)
|
||||
|| !NEAR_EQUAL(bmax[X], 2.0, BN_TOL_DIST)
|
||||
|| !NEAR_EQUAL(bmax[Y], 2.0, BN_TOL_DIST);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_centroid(void)
|
||||
{
|
||||
const point_t rectangle[] = {
|
||||
{5.0, 0.0, 0.0},
|
||||
{5.0, 4.0, 0.0},
|
||||
{5.0, 4.0, 2.0},
|
||||
{5.0, 0.0, 2.0}
|
||||
};
|
||||
const point_t expected = {5.0, 2.0, 1.0};
|
||||
point_t output[] = {
|
||||
{11.0, 12.0, 13.0},
|
||||
{0.0, 0.0, 0.0},
|
||||
{17.0, 18.0, 19.0},
|
||||
{23.0, 24.0, 25.0}
|
||||
};
|
||||
const point_t before = {11.0, 12.0, 13.0};
|
||||
const point_t after = {17.0, 18.0, 19.0};
|
||||
const point_t farther_after = {23.0, 24.0, 25.0};
|
||||
|
||||
return bg_3d_polygon_centroid(&output[1], 4, rectangle)
|
||||
|| !VNEAR_EQUAL(output[1], expected, BN_TOL_DIST)
|
||||
|| !VNEAR_EQUAL(output[0], before, BN_TOL_DIST)
|
||||
|| !VNEAR_EQUAL(output[2], after, BN_TOL_DIST)
|
||||
|| !VNEAR_EQUAL(output[3], farther_after, BN_TOL_DIST);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_ccw_sort(void)
|
||||
{
|
||||
point_t points[] = {
|
||||
{10.0, 10.0, 0.0},
|
||||
{10.1, 10.1, 0.0},
|
||||
{10.1, 10.0, 0.0},
|
||||
{10.0, 10.1, 0.0}
|
||||
};
|
||||
plane_t plane = {0.0, 0.0, 1.0, 0.0};
|
||||
vect_t total = VINIT_ZERO;
|
||||
vect_t cross;
|
||||
|
||||
if (bg_3d_polygon_sort_ccw(4, points, plane))
|
||||
return 1;
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
size_t next = i + 1 == 4 ? 0 : i + 1;
|
||||
VCROSS(cross, points[i], points[next]);
|
||||
VADD2(total, total, cross);
|
||||
}
|
||||
|
||||
return total[Z] <= SMALL_FASTF;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
if (argc != 1)
|
||||
return 1;
|
||||
|
||||
return test_view_bbox() || test_centroid() || test_ccw_sort();
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
/* P O L Y G O N _ T R I A N G U L A T E _ C L E A N . C
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "bu.h"
|
||||
#include "bg.h"
|
||||
#include "vmath.h"
|
||||
|
||||
|
||||
static int
|
||||
check_clean_triangulation(const point2d_t *points, size_t point_count,
|
||||
const int *outer, size_t outer_count, const int **holes,
|
||||
const size_t *hole_counts, size_t hole_count, const int *steiner,
|
||||
size_t steiner_count, double expected_area)
|
||||
{
|
||||
int *faces = NULL;
|
||||
int face_count = 0;
|
||||
point2d_t *out_points = NULL;
|
||||
int out_point_count = 0;
|
||||
struct bg_triangulation_report report = {BG_TRIANGULATION_OK, -1, ""};
|
||||
int ret = bg_nested_poly_triangulate_clean(&faces, &face_count,
|
||||
&out_points, &out_point_count, outer, outer_count, holes,
|
||||
hole_counts, hole_count, steiner, steiner_count, NULL, 0, points,
|
||||
point_count, &report);
|
||||
if (ret || !faces || !out_points || face_count <= 0 ||
|
||||
out_point_count <= 0 || report.reason != BG_TRIANGULATION_OK) {
|
||||
bu_log("clean test %.17g failed: ret %d faces %d points %d\n",
|
||||
expected_area, ret, face_count, out_point_count);
|
||||
return 1;
|
||||
}
|
||||
|
||||
double area = 0.0;
|
||||
for (int i = 0; i < face_count; i++) {
|
||||
const int a = faces[3 * i];
|
||||
const int b = faces[3 * i + 1];
|
||||
const int c = faces[3 * i + 2];
|
||||
if (a < 0 || b < 0 || c < 0 || a >= out_point_count ||
|
||||
b >= out_point_count || c >= out_point_count) {
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
const double twice_area =
|
||||
(out_points[b][X] - out_points[a][X]) *
|
||||
(out_points[c][Y] - out_points[a][Y]) -
|
||||
(out_points[b][Y] - out_points[a][Y]) *
|
||||
(out_points[c][X] - out_points[a][X]);
|
||||
if (!isfinite(twice_area)) {
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
area += 0.5 * fabs(twice_area);
|
||||
}
|
||||
|
||||
if (fabs(area - expected_area) > 1.0e-6) {
|
||||
bu_log("clean test %.17g area %.17g\n", expected_area, area);
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
bu_free(faces, "clean triangulation faces");
|
||||
bu_free(out_points, "clean triangulation points");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
check_clean_failure_report(void)
|
||||
{
|
||||
point2d_t points[4] = {
|
||||
{0.0, 0.0}, {1.0, 0.0}, {2.0, 0.0}, {3.0, 0.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
int *faces = NULL;
|
||||
int face_count = 0;
|
||||
point2d_t *out_points = NULL;
|
||||
int out_point_count = 0;
|
||||
struct bg_triangulation_report report = {BG_TRIANGULATION_OK, -1, ""};
|
||||
const int ret = bg_nested_poly_triangulate_clean(&faces, &face_count,
|
||||
&out_points, &out_point_count, outer, 4, NULL, NULL, 0, NULL, 0,
|
||||
NULL, 0, (const point2d_t *)points, 4, &report);
|
||||
if (faces)
|
||||
bu_free(faces, "unexpected clean failure faces");
|
||||
if (out_points)
|
||||
bu_free(out_points, "unexpected clean failure points");
|
||||
if (ret == BRLCAD_OK || report.reason == BG_TRIANGULATION_OK ||
|
||||
report.message[0] == '\0') {
|
||||
bu_log("clean failure did not return a structured diagnostic\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
check_clean_constraint(void)
|
||||
{
|
||||
point2d_t points[6] = {
|
||||
{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0},
|
||||
{2.0, 5.0}, {8.0, 5.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int steiner[2] = {4, 5};
|
||||
const int constraints[2] = {4, 5};
|
||||
int *faces = NULL;
|
||||
int face_count = 0;
|
||||
point2d_t *out_points = NULL;
|
||||
int out_point_count = 0;
|
||||
int ret = bg_nested_poly_triangulate_clean(&faces,
|
||||
&face_count, &out_points, &out_point_count, outer, 4, NULL, NULL, 0,
|
||||
steiner, 2, constraints, 1, (const point2d_t *)points, 6, NULL);
|
||||
if (ret || !faces || !out_points || face_count <= 0 ||
|
||||
out_point_count <= 0) {
|
||||
bu_log("constrained clean failed: ret %d faces %d points %d\n",
|
||||
ret, face_count, out_point_count);
|
||||
ret = 1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
int endpoints[2] = {-1, -1};
|
||||
for (int i = 0; i < out_point_count; ++i) {
|
||||
if (V2NEAR_EQUAL(out_points[i], points[4], 1.0e-8))
|
||||
endpoints[0] = i;
|
||||
if (V2NEAR_EQUAL(out_points[i], points[5], 1.0e-8))
|
||||
endpoints[1] = i;
|
||||
}
|
||||
if (endpoints[0] < 0 || endpoints[1] < 0) {
|
||||
bu_log("constrained clean omitted an endpoint\n");
|
||||
ret = 1;
|
||||
goto done;
|
||||
}
|
||||
ret = 1;
|
||||
for (int i = 0; i < face_count; ++i) {
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
const int first = faces[3 * i + corner];
|
||||
const int second = faces[3 * i + (corner + 1) % 3];
|
||||
if ((first == endpoints[0] && second == endpoints[1]) ||
|
||||
(first == endpoints[1] && second == endpoints[0])) {
|
||||
ret = 0;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bu_log("constrained clean omitted its certified edge\n");
|
||||
|
||||
done:
|
||||
if (faces)
|
||||
bu_free(faces, "clean constrained faces");
|
||||
if (out_points)
|
||||
bu_free(out_points, "clean constrained points");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, const char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
if (argc != 1)
|
||||
return 1;
|
||||
|
||||
{
|
||||
point2d_t points[9] = {
|
||||
{0.0, 0.0}, {2.0, 0.0}, {4.0, 0.0},
|
||||
{4.0, 4.0}, {0.0, 4.0}, {0.0, 0.0},
|
||||
{1.0, 0.0}, {2.0, 2.0}, {2.0, 2.0}
|
||||
};
|
||||
const int outer[6] = {0, 1, 2, 3, 4, 5};
|
||||
const int steiner[3] = {6, 7, 8};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 9, outer,
|
||||
6, NULL, NULL,
|
||||
0, steiner, 3, 16.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[12] = {
|
||||
{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0},
|
||||
{2.0, 2.0}, {2.0, 6.0}, {6.0, 6.0}, {6.0, 2.0},
|
||||
{4.0, 4.0}, {4.0, 8.0}, {8.0, 8.0}, {8.0, 4.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int hole1[4] = {4, 5, 6, 7};
|
||||
const int hole2[4] = {8, 9, 10, 11};
|
||||
const int *holes[2] = {hole1, hole2};
|
||||
const size_t hole_counts[2] = {4, 4};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 12, outer,
|
||||
4, holes,
|
||||
hole_counts, 2, NULL, 0, 72.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[8] = {
|
||||
{3.0, 3.0}, {7.0, 3.0}, {7.0, 7.0}, {3.0, 7.0},
|
||||
{1.0, 1.0}, {1.0, 9.0}, {9.0, 9.0}, {9.0, 1.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int hole[4] = {4, 5, 6, 7};
|
||||
const int *holes[1] = {hole};
|
||||
const size_t hole_counts[1] = {4};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 8, outer,
|
||||
4, holes,
|
||||
hole_counts, 1, NULL, 0, 48.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
/* A weakly-simple ring may encode a hole touching its outline at one
|
||||
* topology vertex. Strictly-simple Clipper output must retain the
|
||||
* intended outer-minus-hole area. */
|
||||
point2d_t points[8] = {
|
||||
{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0},
|
||||
{0.0, 0.0}, {0.0, 4.0}, {4.0, 4.0}, {4.0, 0.0}
|
||||
};
|
||||
const int outer[8] = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 8, outer,
|
||||
8, NULL, NULL, 0, NULL, 0, 84.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
/* Reinserting (2, 1) on the nonadjacent horizontal edge exposes a
|
||||
* clockwise triangular cutout in this touching walk. Filling both
|
||||
* extracted cycles would produce area 18 instead of area 10. The
|
||||
* interpretation must not depend on the starting vertex or winding. */
|
||||
point2d_t points[14] = {
|
||||
{2.0, 1.0}, {0.0, -1.0}, {-2.0, 1.0},
|
||||
{3.0, 1.0}, {-3.0, 2.0}, {0.0, -4.0},
|
||||
{-5.0, -5.0}, {5.0, -5.0}, {5.0, 5.0}, {-5.0, 5.0},
|
||||
{-0.25, 0.0}, {0.25, 0.0}, {0.25, 0.5}, {-0.25, 0.5}
|
||||
};
|
||||
const size_t ring_count = 6;
|
||||
const int enclosing[4] = {6, 7, 8, 9};
|
||||
const int island_hole[4] = {10, 11, 12, 13};
|
||||
for (int direction = -1; direction <= 1; direction += 2) {
|
||||
for (size_t start = 0; start < ring_count; ++start) {
|
||||
int ring[6];
|
||||
for (size_t i = 0; i < ring_count; ++i) {
|
||||
ring[i] = ((int)start + direction * (int)i +
|
||||
(int)ring_count) % (int)ring_count;
|
||||
}
|
||||
if (check_clean_triangulation((const point2d_t *)points, 14,
|
||||
ring, ring_count, NULL, NULL, 0, NULL, 0, 10.0))
|
||||
return 1;
|
||||
/* The same walk used as a hole leaves its cutout as a filled
|
||||
* island, which must be retained exactly once. */
|
||||
const int *holes[2] = {ring, island_hole};
|
||||
const size_t hole_counts[2] = {ring_count, 4};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 14,
|
||||
enclosing, 4, holes, hole_counts, 1, NULL, 0, 90.0))
|
||||
return 1;
|
||||
/* A hole in the island belongs to its smallest enclosing
|
||||
* filled cycle, not to the enclosing square. */
|
||||
if (check_clean_triangulation((const point2d_t *)points, 14,
|
||||
enclosing, 4, holes, hole_counts, 2, NULL, 0, 89.75))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
/* A zero-area A-B-A whisker in a hole does not change the filled set. */
|
||||
point2d_t points[11] = {
|
||||
{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0},
|
||||
{2.0, 2.0}, {8.0, 2.0}, {8.0, 8.0}, {2.0, 8.0},
|
||||
{2.0, 5.0}, {1.0, 5.0}, {2.0, 5.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int hole[7] = {4, 5, 6, 7, 8, 9, 10};
|
||||
const int *holes[1] = {hole};
|
||||
const size_t hole_counts[1] = {7};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 11, outer,
|
||||
4, holes, hole_counts, 1, NULL, 0, 64.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
/* Preserve a collinear boundary point while removing the excursion
|
||||
* beyond it. The filled region is still the ten-by-ten square. */
|
||||
point2d_t points[5] = {
|
||||
{0.0, 0.0}, {-2.0, 0.0}, {10.0, 0.0},
|
||||
{10.0, 10.0}, {0.0, 10.0}
|
||||
};
|
||||
const int outer[5] = {0, 1, 2, 3, 4};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 5, outer,
|
||||
5, NULL, NULL, 0, NULL, 0, 100.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
/* A vertex on a nonadjacent edge splits this touching outline into two
|
||||
* exact filled cycles. Their combined area is eight square units. */
|
||||
point2d_t points[6] = {
|
||||
{2.0, 1.0}, {-4.0, 1.0}, {-3.0, -1.0},
|
||||
{3.0, 1.0}, {4.0, -1.0}, {4.0, 1.0}
|
||||
};
|
||||
const int outer[6] = {0, 1, 2, 3, 4, 5};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 6, outer,
|
||||
6, NULL, NULL, 0, NULL, 0, 8.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
/* Clipper rejects a wholly collinear closed path. As a hole it
|
||||
* removes no area and must not invalidate the containing polygon. */
|
||||
point2d_t points[11] = {
|
||||
{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0},
|
||||
{2.0, 5.0}, {3.0, 5.0}, {4.0, 5.0}, {5.0, 5.0},
|
||||
{6.0, 5.0}, {7.0, 5.0}, {8.0, 5.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int hole[7] = {4, 5, 6, 7, 8, 9, 10};
|
||||
const int *holes[1] = {hole};
|
||||
const size_t hole_counts[1] = {7};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 11, outer,
|
||||
4, holes, hole_counts, 1, NULL, 0, 100.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
/* A hole touching the middle of an outline edge is a notch. Clipper
|
||||
* must split the T-junction before detria sees the constraint ring. */
|
||||
point2d_t points[7] = {
|
||||
{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0},
|
||||
{5.0, 0.0}, {4.0, 2.0}, {6.0, 2.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int hole[3] = {4, 5, 6};
|
||||
const int *holes[1] = {hole};
|
||||
const size_t hole_counts[1] = {3};
|
||||
if (check_clean_triangulation((const point2d_t *)points, 7, outer,
|
||||
4, holes, hole_counts, 1, NULL, 0, 98.0))
|
||||
return 1;
|
||||
}
|
||||
|
||||
{
|
||||
/* The overlapping holes split the clipped boundary into several filled
|
||||
* nodes. One retained triangular hole is attached to a node outside it;
|
||||
* it must be reassigned without filling the hole or changing chart area. */
|
||||
point2d_t points[17] = {
|
||||
{0.997, 0.000}, {0.997, 0.500}, {0.152, 1.000},
|
||||
{0.773, 0.322}, {0.777, 0.297}, {0.757, 0.321},
|
||||
{0.881, 0.382}, {0.903, 0.337}, {0.881, 0.291},
|
||||
{0.713, 0.279}, {0.728, 0.334}, {0.768, 0.277},
|
||||
{0.908, 0.428}, {0.907, 0.347}, {0.902, 0.417},
|
||||
{0.790, 0.254}, {0.820, 0.326}
|
||||
};
|
||||
const int outer[3] = {0, 1, 2};
|
||||
const int first_hole[3] = {3, 4, 5};
|
||||
const int second_hole[3] = {6, 7, 8};
|
||||
const int crossing_hole[8] = {9, 10, 11, 12, 13, 14, 15, 16};
|
||||
const int *holes[3] = {first_hole, second_hole, crossing_hole};
|
||||
const size_t hole_counts[3] = {3, 3, 8};
|
||||
const double clipped_area = 0.20669859094190662;
|
||||
if (check_clean_triangulation((const point2d_t *)points, 17, outer,
|
||||
3, holes, hole_counts, 3, NULL, 0, clipped_area))
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (check_clean_constraint())
|
||||
return 1;
|
||||
if (check_clean_failure_report())
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -0,0 +1,255 @@
|
||||
/* P O L Y G O N _ T R I A N G U L A T E _ S T R I C T . C
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by the
|
||||
* U.S. Army Research Laboratory.
|
||||
*
|
||||
* Distributed under the terms of the GNU Lesser General Public License
|
||||
* (LGPL), version 2.1.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "bu.h"
|
||||
#include "bg.h"
|
||||
|
||||
static int
|
||||
run_case(const point2d_t *points, size_t point_count, const int *outer,
|
||||
size_t outer_count, const int **holes, const size_t *hole_counts,
|
||||
size_t hole_count, const int *steiner, size_t steiner_count,
|
||||
int expect_success, int forbidden_index)
|
||||
{
|
||||
int *faces = NULL;
|
||||
int face_count = 0;
|
||||
point2d_t *out_points = NULL;
|
||||
int out_point_count = 0;
|
||||
struct bg_triangulation_report report = {
|
||||
BG_TRIANGULATION_OK, -1, {0}
|
||||
};
|
||||
int ret = bg_nested_poly_triangulate_strict(&faces, &face_count,
|
||||
&out_points, &out_point_count, outer, outer_count, holes, hole_counts,
|
||||
hole_count, steiner, steiner_count, NULL, 0, points, point_count,
|
||||
&report);
|
||||
if (!expect_success) {
|
||||
if (!ret || faces || face_count || out_points || out_point_count ||
|
||||
report.reason == BG_TRIANGULATION_OK || !report.message[0]) {
|
||||
bu_free(faces, "unexpected strict faces");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (ret || report.reason != BG_TRIANGULATION_OK || !faces ||
|
||||
face_count <= 0 ||
|
||||
(const void *)out_points != (const void *)points ||
|
||||
out_point_count <= 0) {
|
||||
bu_free(faces, "strict faces");
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < face_count * 3; ++i) {
|
||||
if (faces[i] < 0 || (size_t)faces[i] >= point_count ||
|
||||
faces[i] == forbidden_index) {
|
||||
bu_free(faces, "strict faces");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
bu_free(faces, "strict faces");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, const char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
if (argc != 1)
|
||||
return 1;
|
||||
|
||||
{
|
||||
point2d_t points[67];
|
||||
int outer[67];
|
||||
for (int i = 0; i < 65; ++i) {
|
||||
V2SET(points[i], (double)i, 0.0);
|
||||
outer[i] = i;
|
||||
}
|
||||
V2SET(points[65], 64.0, 10.0);
|
||||
V2SET(points[66], 0.0, 10.0);
|
||||
outer[65] = 65;
|
||||
outer[66] = 66;
|
||||
if (run_case((const point2d_t *)points, 67, outer, 67, NULL,
|
||||
NULL, 0, NULL, 0, 1, -1))
|
||||
return 2;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[5] = {
|
||||
{0.0, 0.0}, {1.0, 0x0.0000000000001p-1022}, {2.0, 0.0},
|
||||
{2.0, 2.0}, {0.0, 2.0}
|
||||
};
|
||||
const int outer[5] = {0, 1, 2, 3, 4};
|
||||
if (run_case((const point2d_t *)points, 5, outer, 5, NULL, NULL,
|
||||
0, NULL, 0, 1, -1))
|
||||
return 3;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[17];
|
||||
int outer[16];
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
const double angle = 2.0 * acos(-1.0) * i / 16.0;
|
||||
V2SET(points[i], cos(angle), sin(angle));
|
||||
outer[i] = i;
|
||||
}
|
||||
V2SET(points[16], 0.0, 0.0);
|
||||
const int steiner[1] = {16};
|
||||
if (run_case((const point2d_t *)points, 17, outer, 16, NULL,
|
||||
NULL, 0, steiner, 1, 1, -1))
|
||||
return 4;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[5] = {
|
||||
{0.0, 0.0}, {2.0, 0.0}, {2.0, 2.0}, {0.0, 2.0},
|
||||
{1.0, 0.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int steiner[1] = {4};
|
||||
if (run_case((const point2d_t *)points, 5, outer, 4, NULL, NULL,
|
||||
0, steiner, 1, 1, 4))
|
||||
return 5;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[5] = {
|
||||
{0.0, 0.0}, {2.0, 0.0}, {2.0, 2.0}, {0.0, 2.0},
|
||||
{0.0, 0.0}
|
||||
};
|
||||
const int outer[5] = {0, 1, 2, 3, 4};
|
||||
if (run_case((const point2d_t *)points, 5, outer, 5, NULL, NULL,
|
||||
0, NULL, 0, 0, -1))
|
||||
return 6;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[4] = {
|
||||
{0.0, 0.0}, {2.0, 2.0}, {0.0, 2.0}, {2.0, 0.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
if (run_case((const point2d_t *)points, 4, outer, 4, NULL, NULL,
|
||||
0, NULL, 0, 0, -1))
|
||||
return 7;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[12] = {
|
||||
{0.0, 0.0}, {10.0, 0.0}, {10.0, 10.0}, {0.0, 10.0},
|
||||
{2.0, 2.0}, {2.0, 8.0}, {8.0, 8.0}, {8.0, 2.0},
|
||||
{3.0, 3.0}, {3.0, 4.0}, {4.0, 4.0}, {4.0, 3.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int hole1[4] = {4, 5, 6, 7};
|
||||
const int hole2[4] = {8, 9, 10, 11};
|
||||
const int *holes[2] = {hole1, hole2};
|
||||
const size_t counts[2] = {4, 4};
|
||||
if (run_case((const point2d_t *)points, 12, outer, 4, holes,
|
||||
counts, 2, NULL, 0, 0, -1))
|
||||
return 8;
|
||||
}
|
||||
|
||||
{
|
||||
const double scales[2] = {0x1p900, 0x1p-900};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
point2d_t points[4] = {
|
||||
{0.0, 0.0}, {scales[i], 0.0},
|
||||
{scales[i], scales[i]}, {0.0, scales[i]}
|
||||
};
|
||||
if (run_case((const point2d_t *)points, 4, outer, 4, NULL,
|
||||
NULL, 0, NULL, 0, 1, -1))
|
||||
return 9 + i;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const double base = 0x1p500;
|
||||
const double delta = 0x1p450;
|
||||
point2d_t points[4] = {
|
||||
{base, base}, {base + delta, base},
|
||||
{base + delta, base + delta}, {base, base + delta}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
if (run_case((const point2d_t *)points, 4, outer, 4, NULL, NULL,
|
||||
0, NULL, 0, 1, -1))
|
||||
return 11;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[4] = {
|
||||
{0.0, 0.0}, {1.0, 0.0}, {1.0, INFINITY}, {0.0, 1.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
if (run_case((const point2d_t *)points, 4, outer, 4, NULL, NULL,
|
||||
0, NULL, 0, 0, -1))
|
||||
return 12;
|
||||
const int invalid_outer[4] = {0, 1, 4, 3};
|
||||
if (run_case((const point2d_t *)points, 4, invalid_outer, 4, NULL,
|
||||
NULL, 0, NULL, 0, 0, -1))
|
||||
return 13;
|
||||
}
|
||||
|
||||
{
|
||||
point2d_t points[4] = {
|
||||
{0.0, 0.0}, {2.0, 0.0}, {2.0, 1.0}, {0.0, 1.0}
|
||||
};
|
||||
const int outer[4] = {0, 1, 2, 3};
|
||||
const int constraint[2] = {0, 2};
|
||||
int *faces = NULL;
|
||||
int face_count = 0;
|
||||
struct bg_triangulation_report report = {
|
||||
BG_TRIANGULATION_OK, -1, {0}
|
||||
};
|
||||
if (bg_nested_poly_triangulate_strict(&faces,
|
||||
&face_count, NULL, NULL, outer, 4, NULL, NULL, 0, NULL,
|
||||
0, constraint, 1, (const point2d_t *)points, 4,
|
||||
&report) || face_count != 2 ||
|
||||
report.reason != BG_TRIANGULATION_OK) {
|
||||
bu_free(faces, "constrained strict faces");
|
||||
return 14;
|
||||
}
|
||||
int edge_uses = 0;
|
||||
for (int face = 0; face < face_count; ++face) {
|
||||
for (int edge = 0; edge < 3; ++edge) {
|
||||
const int first = faces[3 * face + edge];
|
||||
const int second = faces[3 * face + (edge + 1) % 3];
|
||||
if ((first == 0 && second == 2) ||
|
||||
(second == 0 && first == 2))
|
||||
edge_uses++;
|
||||
}
|
||||
}
|
||||
bu_free(faces, "constrained strict faces");
|
||||
if (edge_uses != 2)
|
||||
return 15;
|
||||
|
||||
const int crossing[4] = {0, 2, 1, 3};
|
||||
faces = NULL;
|
||||
face_count = 0;
|
||||
if (!bg_nested_poly_triangulate_strict(&faces,
|
||||
&face_count, NULL, NULL, outer, 4, NULL, NULL, 0, NULL,
|
||||
0, crossing, 2, (const point2d_t *)points, 4,
|
||||
&report) || faces || face_count ||
|
||||
report.reason != BG_TRIANGULATION_CROSSING_CONSTRAINTS)
|
||||
return 16;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* mode: C
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -0,0 +1,70 @@
|
||||
/* R T R E E . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Published in 2026 by the United States Government.
|
||||
* This work is in the public domain.
|
||||
*
|
||||
*/
|
||||
/** @file rtree.cpp
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "bu/app.h"
|
||||
#include "../RTree.h"
|
||||
|
||||
|
||||
static bool
|
||||
count_hit(const std::size_t &, void *context)
|
||||
{
|
||||
std::size_t *hit_count = static_cast<std::size_t *>(context);
|
||||
++(*hit_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int, const char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
// A volume this large has an ulp much greater than one. Identical
|
||||
// rectangles used to leave PickSeeds' two indices at zero because
|
||||
// -(cover volume) - 1 rounded back to -(cover volume).
|
||||
const double rmin[3] = {
|
||||
-2251799813685247.8,
|
||||
-2251799813685330.0,
|
||||
-2251799813685213.0
|
||||
};
|
||||
const double rmax[3] = {
|
||||
2251799813685311.0,
|
||||
2251799813685229.0,
|
||||
2251799813685346.0
|
||||
};
|
||||
|
||||
RTree<std::size_t, double, 3> tree;
|
||||
for (std::size_t i = 0; i < 9; ++i)
|
||||
tree.Insert(rmin, rmax, i);
|
||||
|
||||
if (tree.Count() != 9)
|
||||
return 1;
|
||||
|
||||
std::size_t hit_count = 0;
|
||||
const int found = tree.Search(rmin, rmax, count_hit, &hit_count);
|
||||
if (found != 9 || hit_count != 9)
|
||||
return 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C++
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
@@ -0,0 +1,279 @@
|
||||
/* S P S R . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this file; see the file named COPYING for more
|
||||
* information.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "vmath.h"
|
||||
#include "bu/app.h"
|
||||
#include "bu/log.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "bg/spsr.h"
|
||||
#include "bg/trimesh.h"
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void
|
||||
expect(bool condition, const char *message)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
bu_log("FAIL: %s\n", message);
|
||||
failures++;
|
||||
}
|
||||
|
||||
static std::vector<struct bg_3d_spsr_sample>
|
||||
sphere_samples(size_t count)
|
||||
{
|
||||
const fastf_t golden_angle = M_PI * (3.0 - std::sqrt(5.0));
|
||||
std::vector<struct bg_3d_spsr_sample> samples(count);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
fastf_t y = 1.0 - 2.0 *
|
||||
(static_cast<fastf_t>(i) + 0.5) / count;
|
||||
fastf_t radius = std::sqrt(std::max(0.0, 1.0 - y * y));
|
||||
fastf_t angle = golden_angle * i;
|
||||
VSET(samples[i].point, radius * std::cos(angle), y,
|
||||
radius * std::sin(angle));
|
||||
VMOVE(samples[i].normal, samples[i].point);
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
static void
|
||||
test_boundary_modes(void)
|
||||
{
|
||||
const int side = 8;
|
||||
const int sample_count = 6 * side * side;
|
||||
/* Use a solver regime known to distinguish the boundary modes instead of
|
||||
* relying on library defaults, which may change independently. */
|
||||
const fastf_t boundary_samples_per_node = 1.1;
|
||||
const fastf_t boundary_point_weight = 8.0;
|
||||
point_t *samples = static_cast<point_t *>(bu_calloc(sample_count,
|
||||
sizeof(point_t), "SPSR boundary test samples"));
|
||||
vect_t *normals = static_cast<vect_t *>(bu_calloc(sample_count,
|
||||
sizeof(vect_t), "SPSR boundary test normals"));
|
||||
|
||||
int sample = 0;
|
||||
for (int face = 0; face < 6; face++) {
|
||||
const int axis = face / 2;
|
||||
const fastf_t sign = (face % 2) ? 1.0 : -1.0;
|
||||
for (int first = 0; first < side; first++) {
|
||||
for (int second = 0; second < side; second++) {
|
||||
samples[sample][axis] = sign;
|
||||
samples[sample][(axis + 1) % 3] =
|
||||
-1.0 + 2.0 * first / (side - 1);
|
||||
samples[sample][(axis + 2) % 3] =
|
||||
-1.0 + 2.0 * second / (side - 1);
|
||||
normals[sample][axis] = sign;
|
||||
sample++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int face_counts[3] = {0, 0, 0};
|
||||
for (int boundary = BG_3D_SPSR_BOUNDARY_FREE;
|
||||
boundary <= BG_3D_SPSR_BOUNDARY_DIRICHLET; boundary++) {
|
||||
struct bg_3d_spsr_opts options = BG_3D_SPSR_OPTS_DEFAULT;
|
||||
options.depth = 5;
|
||||
options.full_depth = 3;
|
||||
options.threads = 1;
|
||||
options.samples_per_node = boundary_samples_per_node;
|
||||
options.scale = 1.2;
|
||||
options.point_weight = boundary_point_weight;
|
||||
options.btype = boundary;
|
||||
|
||||
int *faces = NULL;
|
||||
int face_count = 0;
|
||||
point_t *points = NULL;
|
||||
int point_count = 0;
|
||||
int result = bg_3d_spsr(&faces, &face_count, &points,
|
||||
&point_count, samples, normals, sample_count, &options);
|
||||
bool solid = result == BRLCAD_OK && faces && points &&
|
||||
face_count > 0 && point_count > 0 &&
|
||||
!bg_trimesh_solid2(point_count, face_count,
|
||||
reinterpret_cast<fastf_t *>(points), faces, NULL);
|
||||
if (!solid) {
|
||||
bu_log("SPSR boundary %d failed: result %d, %d points, "
|
||||
"%d faces\n", boundary, result, point_count, face_count);
|
||||
}
|
||||
expect(solid, "each SPSR boundary mode produces a solid mesh");
|
||||
face_counts[boundary - BG_3D_SPSR_BOUNDARY_FREE] = face_count;
|
||||
if (faces)
|
||||
bu_free(faces, "SPSR boundary test faces");
|
||||
if (points)
|
||||
bu_free(points, "SPSR boundary test points");
|
||||
}
|
||||
|
||||
expect(face_counts[0] != face_counts[1] ||
|
||||
face_counts[1] != face_counts[2],
|
||||
"SPSR boundary modes affect reconstruction");
|
||||
bu_free(samples, "SPSR boundary test samples");
|
||||
bu_free(normals, "SPSR boundary test normals");
|
||||
}
|
||||
|
||||
struct callback_state {
|
||||
size_t calls;
|
||||
size_t hints;
|
||||
struct bg_3d_spsr_sample additional_sample;
|
||||
bool refine_once;
|
||||
bool stop;
|
||||
};
|
||||
|
||||
static int
|
||||
accept_candidate(struct bg_3d_spsr_refinement_response *response,
|
||||
const struct bg_3d_spsr_refinement_request *request, void *data)
|
||||
{
|
||||
struct callback_state *state =
|
||||
static_cast<struct callback_state *>(data);
|
||||
state->calls++;
|
||||
state->hints = request->hint_count;
|
||||
response->validation.ray_count = 100;
|
||||
if (state->stop) {
|
||||
response->validation.passed = 0;
|
||||
response->stop_refinement = 1;
|
||||
} else if (state->refine_once && state->calls == 1) {
|
||||
response->samples = &state->additional_sample;
|
||||
response->sample_count = 1;
|
||||
response->validation.passed = 0;
|
||||
} else {
|
||||
response->validation.passed = 1;
|
||||
}
|
||||
return BRLCAD_OK;
|
||||
}
|
||||
|
||||
int
|
||||
main(int UNUSED(argc), const char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
|
||||
test_boundary_modes();
|
||||
|
||||
std::vector<struct bg_3d_spsr_sample> samples = sphere_samples(256);
|
||||
struct bg_3d_spsr_adaptive_opts options =
|
||||
BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT;
|
||||
options.solver.depth = 5;
|
||||
options.max_refinement_passes = 0;
|
||||
|
||||
int *faces = NULL;
|
||||
int face_count = 0;
|
||||
point_t *vertices = NULL;
|
||||
int vertex_count = 0;
|
||||
struct bg_3d_spsr_report report = {};
|
||||
int ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
NULL, NULL, &report);
|
||||
expect(ret == BRLCAD_OK, "fixed structured reconstruction succeeds");
|
||||
expect(face_count > 0 && vertex_count > 0,
|
||||
"fixed structured reconstruction returns a mesh");
|
||||
expect(report.solve_count == 1 && report.validation.passed,
|
||||
"fixed structured reconstruction reports one successful solve");
|
||||
if (faces)
|
||||
bu_free(faces, "SPSR test faces");
|
||||
if (vertices)
|
||||
bu_free(vertices, "SPSR test vertices");
|
||||
|
||||
struct callback_state state = {};
|
||||
options.max_refinement_passes = 3;
|
||||
options.target_feature_size = 0.1;
|
||||
options.max_points = 512;
|
||||
faces = NULL;
|
||||
vertices = NULL;
|
||||
face_count = 0;
|
||||
vertex_count = 0;
|
||||
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
accept_candidate, &state, &report);
|
||||
expect(ret == BRLCAD_OK, "callback can accept an adaptive candidate");
|
||||
expect(state.calls == 1, "accepted candidate stops adaptive solving");
|
||||
expect(report.validation.ray_count == 100,
|
||||
"adaptive report preserves callback validation");
|
||||
if (faces)
|
||||
bu_free(faces, "SPSR test faces");
|
||||
if (vertices)
|
||||
bu_free(vertices, "SPSR test vertices");
|
||||
|
||||
state = {};
|
||||
state.stop = true;
|
||||
faces = NULL;
|
||||
vertices = NULL;
|
||||
face_count = 0;
|
||||
vertex_count = 0;
|
||||
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
accept_candidate, &state, &report);
|
||||
expect(ret == BRLCAD_ERROR,
|
||||
"callback can stop an unproductive adaptive solve");
|
||||
expect(state.calls == 1 && report.solve_count == 1 &&
|
||||
report.termination == BG_3D_SPSR_CALLBACK_STOP,
|
||||
"callback stop preserves the final validation report");
|
||||
|
||||
state = {};
|
||||
state.refine_once = true;
|
||||
VSET(state.additional_sample.point, 1.0, 0.0, 0.0);
|
||||
VSET(state.additional_sample.normal, 1.0, 0.0, 0.0);
|
||||
faces = NULL;
|
||||
vertices = NULL;
|
||||
face_count = 0;
|
||||
vertex_count = 0;
|
||||
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
accept_candidate, &state, &report);
|
||||
expect(ret == BRLCAD_OK, "adaptive refinement can add a source sample");
|
||||
expect(state.calls == 2 && report.solve_count == 2,
|
||||
"new source sample triggers a second solve");
|
||||
expect(report.accepted_sample_count == 1 &&
|
||||
report.final_sample_count == samples.size() + 1,
|
||||
"adaptive report counts incorporated samples");
|
||||
if (faces)
|
||||
bu_free(faces, "SPSR test faces");
|
||||
if (vertices)
|
||||
bu_free(vertices, "SPSR test vertices");
|
||||
|
||||
options.solver.threads = 1;
|
||||
options.max_refinement_passes = 0;
|
||||
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
NULL, NULL, NULL);
|
||||
expect(ret == BRLCAD_OK, "explicit serial reconstruction succeeds");
|
||||
if (faces)
|
||||
bu_free(faces, "SPSR test faces");
|
||||
if (vertices)
|
||||
bu_free(vertices, "SPSR test vertices");
|
||||
|
||||
expect(bg_3d_spsr_adaptive(NULL, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
NULL, NULL, NULL) == BRLCAD_ERROR,
|
||||
"invalid output arguments are rejected");
|
||||
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* mode: C++
|
||||
* c-basic-offset: 4
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8 cino=N-s
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,108 @@
|
||||
/* T R I M E S H _ S O L I D . C
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public License
|
||||
* version 2.1 as published by the Free Software Foundation.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "bu/app.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "bg/trimesh.h"
|
||||
|
||||
|
||||
static int
|
||||
check_solid_diagnostics(int *faces, int face_count, int unmatched,
|
||||
int misoriented, int excess)
|
||||
{
|
||||
fastf_t vertices[15] = {
|
||||
0.0, 0.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 1.0,
|
||||
0.0, -1.0, 0.0
|
||||
};
|
||||
int *bad_edges = NULL;
|
||||
struct bg_trimesh_solid_errors errors = BG_TRIMESH_SOLID_ERRORS_INIT_NULL;
|
||||
const int expected = unmatched || misoriented || excess;
|
||||
int failed =
|
||||
((bg_trimesh_solid2(5, face_count, vertices, faces, &errors) != 0) !=
|
||||
expected);
|
||||
|
||||
failed |= errors.unmatched.count != unmatched;
|
||||
failed |= errors.misoriented.count != misoriented;
|
||||
failed |= errors.excess.count != excess;
|
||||
failed |= ((bg_trimesh_solid2(5, face_count, vertices, faces, NULL) != 0)
|
||||
!= expected);
|
||||
failed |= bg_trimesh_solid(5, face_count, vertices, faces,
|
||||
&bad_edges) != expected;
|
||||
failed |= (expected && !bad_edges) || (!expected && bad_edges);
|
||||
|
||||
bg_free_trimesh_solid_errors(&errors);
|
||||
if (bad_edges)
|
||||
bu_free(bad_edges, "bad edges");
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_solid_diagnostics(void)
|
||||
{
|
||||
/* Reverse one tetrahedron face, then attach a fin to an edge. Boundary,
|
||||
* orientation, and overuse defects must be reported independently. */
|
||||
int faces[15] = {
|
||||
0, 1, 2,
|
||||
0, 1, 3,
|
||||
1, 2, 3,
|
||||
2, 0, 3,
|
||||
0, 1, 4
|
||||
};
|
||||
|
||||
if (check_solid_diagnostics(faces, 5, 2, 2, 1) ||
|
||||
check_solid_diagnostics(faces, 4, 0, 3, 0))
|
||||
return 1;
|
||||
|
||||
faces[1] = 2;
|
||||
faces[2] = 1;
|
||||
return check_solid_diagnostics(faces, 5, 2, 0, 1) ||
|
||||
check_solid_diagnostics(faces, 4, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_free_helpers(void)
|
||||
{
|
||||
struct bg_trimesh_edges edges = BG_TRIMESH_EDGES_INIT_NULL;
|
||||
struct bg_trimesh_faces faces = BG_TRIMESH_FACES_INIT_NULL;
|
||||
|
||||
edges.count = 1;
|
||||
edges.edges = (int *)bu_malloc(2 * sizeof(int), "test edges");
|
||||
faces.count = 1;
|
||||
faces.faces = (int *)bu_malloc(sizeof(int), "test faces");
|
||||
bg_free_trimesh_edges(&edges);
|
||||
bg_free_trimesh_faces(&faces);
|
||||
|
||||
return edges.count != 0 || edges.edges || faces.count != 0 || faces.faces;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
bu_setprogname(argv[0]);
|
||||
if (argc != 1)
|
||||
return 1;
|
||||
|
||||
return test_solid_diagnostics() || test_free_helpers();
|
||||
}
|
||||
@@ -243,6 +243,19 @@ main(int UNUSED(argc), const char *argv[])
|
||||
if (fcheck("ARB4_face4", arb4_faces_flipped_f4, arb4_faces_ctrl))
|
||||
ret = -1;
|
||||
|
||||
/* A strip whose ends are identified with opposite parity is a Mobius
|
||||
* band. No sequence of triangle flips can orient it consistently, so
|
||||
* report the contradiction instead of silently leaving a bad cycle. */
|
||||
int mobius_faces[18] = {
|
||||
0,2,3, 0,3,1, 2,4,5,
|
||||
2,5,3, 4,1,0, 4,0,5
|
||||
};
|
||||
int mobius_output[18] = {0};
|
||||
if (bg_trimesh_sync(mobius_output, mobius_faces, 6) >= 0) {
|
||||
bu_log("Mobius strip orientation contradiction was not detected\n");
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ intersect_triangle(const point_t origin, const vect_t direction,
|
||||
extern "C" {
|
||||
|
||||
int
|
||||
bg_isect_tri_ray(const point_t orig, const point_t dir,
|
||||
bg_isect_tri_ray(const point_t orig, const vect_t dir,
|
||||
const point_t vert0, const point_t vert1, const point_t vert2,
|
||||
point_t *isect)
|
||||
{
|
||||
|
||||
+41
-46
@@ -177,11 +177,9 @@ edge_copies(int num_edges, struct bg_trimesh_halfedge *edge_list, int cur_idx)
|
||||
static int
|
||||
edge_unmatched(int *edge_skip, int num_edges, struct bg_trimesh_halfedge *edge_list, int cur_idx)
|
||||
{
|
||||
if (edge_copies(num_edges, edge_list, cur_idx) == 1) {
|
||||
return 1;
|
||||
}
|
||||
*edge_skip = 1;
|
||||
return 0;
|
||||
const int copies = edge_copies(num_edges, edge_list, cur_idx);
|
||||
*edge_skip = copies - 1;
|
||||
return copies == 1;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -199,7 +197,7 @@ edge_misoriented(int *edge_skip, int num_edges, struct bg_trimesh_halfedge *edge
|
||||
int copies = edge_copies(num_edges, edge_list, cur_idx);
|
||||
*edge_skip = copies - 1;
|
||||
|
||||
return (copies != 2 || edge_list[cur_idx].flipped == edge_list[cur_idx + 1].flipped);
|
||||
return copies == 2 && edge_list[cur_idx].flipped == edge_list[cur_idx + 1].flipped;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -259,7 +257,6 @@ bg_trimesh_solid2(int vcnt, int fcnt, fastf_t *v, int *f, struct bg_trimesh_soli
|
||||
int unmatched_edges = 0;
|
||||
int excess_edges = 0;
|
||||
int misoriented_edges = 0;
|
||||
bg_edge_error_funct_t bad_edge_func;
|
||||
|
||||
if (vcnt < 4 || fcnt < 4 || !v || !f) return 1;
|
||||
|
||||
@@ -283,26 +280,26 @@ bg_trimesh_solid2(int vcnt, int fcnt, fastf_t *v, int *f, struct bg_trimesh_soli
|
||||
|
||||
if (!(edge_list = bg_trimesh_generate_edge_list(fcnt, f))) return 1;
|
||||
|
||||
bad_edge_func = errors ? bg_trimesh_edge_continue : bg_trimesh_edge_exit;
|
||||
|
||||
if ((unmatched_edges = bg_trimesh_unmatched_edges(num_edges, edge_list, bad_edge_func, NULL)) ||
|
||||
(misoriented_edges = bg_trimesh_misoriented_edges(num_edges, edge_list, bad_edge_func, NULL)) ||
|
||||
(excess_edges = bg_trimesh_excess_edges(num_edges, edge_list, bad_edge_func, NULL)))
|
||||
{
|
||||
if (!errors) {
|
||||
bu_free(edge_list, "edge_list");
|
||||
return 1;
|
||||
}
|
||||
if (!errors) {
|
||||
not_solid = bg_trimesh_unmatched_edges(num_edges, edge_list, bg_trimesh_edge_exit, NULL) ||
|
||||
bg_trimesh_misoriented_edges(num_edges, edge_list, bg_trimesh_edge_exit, NULL) ||
|
||||
bg_trimesh_excess_edges(num_edges, edge_list, bg_trimesh_edge_exit, NULL);
|
||||
bu_free(edge_list, "edge_list");
|
||||
return not_solid;
|
||||
}
|
||||
|
||||
/* If we either aren't tracking edges or we didn't have any problems, we're done */
|
||||
/* A requested report needs every category, including when an earlier
|
||||
* scan has already established that the mesh is not solid. */
|
||||
unmatched_edges = bg_trimesh_unmatched_edges(num_edges, edge_list, bg_trimesh_edge_continue, NULL);
|
||||
misoriented_edges = bg_trimesh_misoriented_edges(num_edges, edge_list, bg_trimesh_edge_continue, NULL);
|
||||
excess_edges = bg_trimesh_excess_edges(num_edges, edge_list, bg_trimesh_edge_continue, NULL);
|
||||
not_solid = unmatched_edges + excess_edges + misoriented_edges;
|
||||
if (!errors || !not_solid) {
|
||||
if (!not_solid) {
|
||||
bu_free(edge_list, "edge_list");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If we got here, we're interesting in knowing what the problems are */
|
||||
/* If we got here, we're interested in knowing what the problems are. */
|
||||
if (unmatched_edges) {
|
||||
errors->unmatched.count = 0;
|
||||
errors->unmatched.edges = (int *)bu_calloc(unmatched_edges * 2, sizeof(int), "unmatched edge indices");
|
||||
@@ -329,6 +326,7 @@ bg_free_trimesh_edges(struct bg_trimesh_edges *edges)
|
||||
if (!edges) return;
|
||||
|
||||
bu_free(edges->edges, "bg trimesh edges");
|
||||
edges->edges = NULL;
|
||||
edges->count = 0;
|
||||
}
|
||||
|
||||
@@ -338,6 +336,7 @@ bg_free_trimesh_faces(struct bg_trimesh_faces *faces)
|
||||
if (!faces) return;
|
||||
|
||||
bu_free(faces->faces, "bg trimesh faces");
|
||||
faces->faces = NULL;
|
||||
faces->count = 0;
|
||||
}
|
||||
|
||||
@@ -355,34 +354,30 @@ bg_free_trimesh_solid_errors(struct bg_trimesh_solid_errors *errors)
|
||||
int
|
||||
bg_trimesh_solid(int vcnt, int fcnt, fastf_t *v, int *f, int **bedges)
|
||||
{
|
||||
int bedge_cnt = 0;
|
||||
if (!bedges)
|
||||
return bg_trimesh_solid2(vcnt, fcnt, v, f, NULL) != 0;
|
||||
|
||||
if (bedges) {
|
||||
int copy_cnt = 0;
|
||||
struct bg_trimesh_solid_errors errors = BG_TRIMESH_SOLID_ERRORS_INIT_NULL;
|
||||
|
||||
bedge_cnt = bg_trimesh_solid2(vcnt, fcnt, v, f, &errors);
|
||||
|
||||
*bedges = (int *)bu_calloc(bedge_cnt * 2, sizeof(int), "bad edges");
|
||||
|
||||
if (!errors.unmatched.edges || !errors.unmatched.count)
|
||||
return 0;
|
||||
|
||||
memcpy(*bedges, errors.unmatched.edges, errors.unmatched.count * 2 * sizeof(int));
|
||||
copy_cnt += errors.unmatched.count * 2;
|
||||
|
||||
memcpy(*bedges + copy_cnt, errors.misoriented.edges, errors.misoriented.count * 2 * sizeof(int));
|
||||
copy_cnt += errors.misoriented.count * 2;
|
||||
|
||||
memcpy(*bedges + copy_cnt, errors.excess.edges, errors.excess.count * 2 * sizeof(int));
|
||||
|
||||
bg_free_trimesh_solid_errors(&errors);
|
||||
} else {
|
||||
bedge_cnt = bg_trimesh_solid2(vcnt, fcnt, v, f, NULL);
|
||||
*bedges = NULL;
|
||||
struct bg_trimesh_solid_errors errors = BG_TRIMESH_SOLID_ERRORS_INIT_NULL;
|
||||
const int not_solid = bg_trimesh_solid2(vcnt, fcnt, v, f, &errors);
|
||||
const int edge_count = errors.unmatched.count + errors.misoriented.count +
|
||||
errors.excess.count;
|
||||
if (edge_count) {
|
||||
*bedges = (int *)bu_calloc(edge_count * 2, sizeof(int), "bad edges");
|
||||
int offset = 0;
|
||||
const struct bg_trimesh_edges *groups[] = {
|
||||
&errors.unmatched, &errors.misoriented, &errors.excess
|
||||
};
|
||||
for (const struct bg_trimesh_edges *group : groups) {
|
||||
if (group->count) {
|
||||
memcpy(*bedges + offset, group->edges,
|
||||
group->count * 2 * sizeof(int));
|
||||
offset += group->count * 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* returns true when not solid */
|
||||
return (bedge_cnt > 0) ? 1 : 0;
|
||||
bg_free_trimesh_solid_errors(&errors);
|
||||
return not_solid != 0;
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user