Compare commits
177
Commits
annotrt
...
brepraytrace
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bc4413b57 | ||
|
|
a69ed574d9 | ||
|
|
c233a4fb61 | ||
|
|
0b77165c0b | ||
|
|
726f89cd7a | ||
|
|
22a670b44d | ||
|
|
eea3eccbbc | ||
|
|
901347705f | ||
|
|
fc37a8caf8 | ||
|
|
dad479edd3 | ||
|
|
2a32a6a71b | ||
|
|
0d2eb82c4c | ||
|
|
401928d735 | ||
|
|
f4dbb89258 | ||
|
|
de1bb455af | ||
|
|
1d31418815 | ||
|
|
b89127c016 | ||
|
|
732d329177 | ||
|
|
ff7cfadd5a | ||
|
|
be3314a685 | ||
|
|
9110dc6b44 | ||
|
|
9a73feac84 | ||
|
|
bd8fd932fd | ||
|
|
487954cbca | ||
|
|
c640884cf0 | ||
|
|
65e5ad6a62 | ||
|
|
c26b4b2b4e | ||
|
|
29744a4da7 | ||
|
|
3450dee989 | ||
|
|
b5ed0043ee | ||
|
|
d4cf5d1b8c | ||
|
|
97a1b9c30f | ||
|
|
9d3cb83a28 | ||
|
|
4a3c66ceff | ||
|
|
fd2b077c6b | ||
|
|
8ba2eb90b0 | ||
|
|
61ebf18379 | ||
|
|
3378e9d15a | ||
|
|
8f053346fd | ||
|
|
44c65fc483 | ||
|
|
ec03234357 | ||
|
|
49c387662a | ||
|
|
dfa3468619 | ||
|
|
6e31fde0f2 | ||
|
|
ee4c1798f2 | ||
|
|
84b434bbd5 | ||
|
|
8b4e7c1207 | ||
|
|
ad4b4aaeae | ||
|
|
0945249041 | ||
|
|
9f7d58028e | ||
|
|
584d43cd28 | ||
|
|
2e40d4f988 | ||
|
|
6690aa38c0 | ||
|
|
9d72450607 | ||
|
|
8d16f39bf7 | ||
|
|
7ae01d3df6 | ||
|
|
7652f60da2 | ||
|
|
8164162ada | ||
|
|
f9dfa02c19 | ||
|
|
44f6855c08 | ||
|
|
081061a288 | ||
|
|
9751f520d6 | ||
|
|
7ee6fd981a | ||
|
|
ad47b8ccd1 | ||
|
|
8d186e7fc0 | ||
|
|
b11ae6dee2 | ||
|
|
64f37c61af | ||
|
|
001740a5b8 | ||
|
|
bca851ea40 | ||
|
|
7b88bb8eb2 | ||
|
|
bf3d8a77c5 | ||
|
|
282e2d2361 | ||
|
|
7ceb68956f | ||
|
|
f7b21edca6 | ||
|
|
080e062328 | ||
|
|
24d92cd200 | ||
|
|
a036e2b3ee | ||
|
|
63b42010ce | ||
|
|
e660a7e80f | ||
|
|
6516c2794b | ||
|
|
e62a547795 | ||
|
|
34282ddbed | ||
|
|
597292bb67 | ||
|
|
1ad4ba4181 | ||
|
|
f1810edb9e | ||
|
|
35afe5d2d8 | ||
|
|
cbb00d0466 | ||
|
|
04d770dfbd | ||
|
|
3c2b469a33 | ||
|
|
cb8ee49a52 | ||
|
|
9ddf8c6993 | ||
|
|
a6f824e143 | ||
|
|
8ff6e56fb7 | ||
|
|
2b39054b44 | ||
|
|
ebfcc5f997 | ||
|
|
0b663ad2fd | ||
|
|
e1a2d40e26 | ||
|
|
4cb4de46fe | ||
|
|
8ad1135f07 | ||
|
|
beab75c232 | ||
|
|
708638afe6 | ||
|
|
ce7f4bb237 | ||
|
|
961d389bc8 | ||
|
|
5309a76eb4 | ||
|
|
4ff4c81b0b | ||
|
|
2c77c33c2f | ||
|
|
cbdfb2440b | ||
|
|
64e8e4fd2d | ||
|
|
995af03df6 | ||
|
|
e021397912 | ||
|
|
f2e8cff36a | ||
|
|
f48c488744 | ||
|
|
46f2d40f1f | ||
|
|
805e29a543 | ||
|
|
b5dac09aae | ||
|
|
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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
|
||||
====
|
||||
|
||||
|
||||
+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);
|
||||
|
||||
|
||||
@@ -293,6 +293,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 +308,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.
|
||||
*/
|
||||
|
||||
+127
-5
@@ -67,6 +67,7 @@
|
||||
#define BG_SPSR_H
|
||||
|
||||
#include "common.h"
|
||||
#include <stdint.h>
|
||||
#include "vmath.h"
|
||||
#include "bg/defines.h"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -87,7 +87,11 @@ BG_EXPORT extern int bg_trimesh_oriented(int vcnt, int fcnt, fastf_t *v, 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);
|
||||
|
||||
|
||||
+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,
|
||||
|
||||
@@ -44,6 +44,7 @@ __BEGIN_DECLS
|
||||
#include "brep/quality.h"
|
||||
#include "brep/intersect.h"
|
||||
#include "brep/boolean.h"
|
||||
#include "brep/cobb.h"
|
||||
#include "brep/cdt.h"
|
||||
#include "brep/csg.h"
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ set(
|
||||
bbnode.h
|
||||
assembly.h
|
||||
boolean.h
|
||||
cobb.h
|
||||
brnode.h
|
||||
cdt.h
|
||||
csg.h
|
||||
|
||||
+79
-36
@@ -29,6 +29,13 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C++" {
|
||||
# include <cstdint>
|
||||
# include <list>
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "brep/defines.h"
|
||||
#include "brep/curvetree.h"
|
||||
#include "brep/ray.h"
|
||||
@@ -41,6 +48,7 @@
|
||||
extern "C++" {
|
||||
namespace brlcad {
|
||||
class BBNode;
|
||||
class BBNodeArena;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -65,7 +73,7 @@ namespace brlcad {
|
||||
/**
|
||||
* Bounding Box Hierarchy Node
|
||||
*/
|
||||
class BREP_EXPORT BBNode : public PooledObject<BBNode> {
|
||||
class BREP_EXPORT BBNode {
|
||||
public:
|
||||
explicit BBNode(const ON_BoundingBox &node, const CurveTree *ct = NULL);
|
||||
BBNode(const CurveTree *ct,
|
||||
@@ -77,6 +85,8 @@ namespace brlcad {
|
||||
~BBNode();
|
||||
|
||||
BBNode(Deserializer &deserieralizer, const CurveTree &ctree);
|
||||
static BBNode *deserializeTree(Deserializer &deserializer,
|
||||
const CurveTree &ctree);
|
||||
void serialize(Serializer &serializer) const;
|
||||
|
||||
const ON_BrepFace &get_face() const;
|
||||
@@ -115,18 +125,36 @@ namespace brlcad {
|
||||
/** Test whether a ray intersects the 3D bounding volume of the
|
||||
* node - if so, and node is not a leaf node, query children. If
|
||||
* leaf node, and intersects, add to list.
|
||||
*
|
||||
* The fixed-array overload appends while count is below capacity,
|
||||
* increments count for every intersected leaf, and sets overflow if
|
||||
* any leaf cannot be stored. The caller initializes count and
|
||||
* overflow and may use the total count to select a fallback.
|
||||
*/
|
||||
bool intersectsHierarchy(const ON_Ray &ray, std::list<const BBNode *> &results) const;
|
||||
bool intersectsHierarchy(const ON_Ray &ray, const BBNode **results,
|
||||
std::size_t capacity, std::size_t &count,
|
||||
bool &overflow) const;
|
||||
|
||||
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt) const;
|
||||
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const;
|
||||
int getLeavesBoundingPoint(const ON_3dPoint &pt, std::list<const BBNode *> &out) const;
|
||||
bool isTrimmed(const ON_2dPoint &uv, const BRNode **closest, double &closesttrim, double within_distance_tol) const;
|
||||
/** Classify against the prepared trim leaves without allocating.
|
||||
* isTrimmedAllocating retains the former filtered-list path for
|
||||
* transition-time equivalence diagnostics only. */
|
||||
bool isTrimmed(const ON_2dPoint &uv, const BRNode **closest,
|
||||
double &closesttrim, double within_distance_tol,
|
||||
std::size_t *candidate_count = NULL) const;
|
||||
bool isTrimmedAllocating(const ON_2dPoint &uv, const BRNode **closest,
|
||||
double &closesttrim, double within_distance_tol,
|
||||
std::size_t *candidate_count = NULL) const;
|
||||
|
||||
void BuildBBox();
|
||||
bool prepTrims();
|
||||
bool prepTrims(double within_distance_tol =
|
||||
BREP_EDGE_MISS_TOLERANCE);
|
||||
|
||||
const std::vector<BBNode *> &get_children() const;
|
||||
std::size_t childCount() const;
|
||||
BBNode *child(std::size_t index) const;
|
||||
|
||||
/** Bounding Box */
|
||||
ON_BoundingBox m_node;
|
||||
@@ -138,6 +166,8 @@ namespace brlcad {
|
||||
/** Trimming Flags */
|
||||
bool m_checkTrim;
|
||||
bool m_trimmed;
|
||||
/* Arena construction nodes are destroyed shallowly by their arena. */
|
||||
bool m_arena_node;
|
||||
|
||||
/** Point used for closeness testing - usually based on evaluation
|
||||
* of the curve/surface at the center of the parametric domain
|
||||
@@ -152,10 +182,15 @@ namespace brlcad {
|
||||
|
||||
|
||||
private:
|
||||
friend class BBNodeArena;
|
||||
|
||||
BBNode(const BBNode &source);
|
||||
BBNode &operator=(const BBNode &source);
|
||||
BBNode(Deserializer &deserializer, const CurveTree &ctree,
|
||||
BBNodeArena *arena);
|
||||
void adopt(BBNode &source);
|
||||
void setArenaOwner(BBNodeArena *owner);
|
||||
|
||||
void removeChild(BBNode *child);
|
||||
bool intersectedBy(const ON_Ray &ray, double *tnear = NULL, double *tfar = NULL) const;
|
||||
|
||||
/** Report if a given uv point is within the uv boundaries defined
|
||||
@@ -163,39 +198,41 @@ namespace brlcad {
|
||||
*/
|
||||
bool containsUV(const ON_2dPoint &uv) const;
|
||||
|
||||
void getTrimsAbove(const ON_2dPoint &uv, std::list<const BRNode *> &out_leaves) const;
|
||||
const BBNode *closer(const ON_3dPoint &pt, const BBNode *left, const BBNode *right) const;
|
||||
|
||||
struct Stl : public PooledObject<Stl> {
|
||||
Stl() : m_children(), m_trims_above() {}
|
||||
|
||||
std::vector<BBNode *> m_children;
|
||||
std::list<const BRNode *> m_trims_above;
|
||||
} * const m_stl;
|
||||
BBNode **m_children;
|
||||
uint32_t m_child_count;
|
||||
uint32_t m_child_capacity;
|
||||
};
|
||||
|
||||
inline void
|
||||
BBNode::addChild(BBNode *child)
|
||||
{
|
||||
if (LIKELY(child != NULL)) {
|
||||
m_stl->m_children.push_back(child);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Per-SurfaceTree construction storage. The first node uses ordinary
|
||||
* transferable storage and subsequent nodes are bulk allocated. The
|
||||
* final logical root is adopted into that first node, whose lifetime owns
|
||||
* the arena.
|
||||
*/
|
||||
class BBNodeArena {
|
||||
public:
|
||||
BBNodeArena();
|
||||
~BBNodeArena();
|
||||
|
||||
inline void
|
||||
BBNode::removeChild(BBNode *child)
|
||||
{
|
||||
std::vector<BBNode *>::iterator i;
|
||||
for (i = m_stl->m_children.begin(); i != m_stl->m_children.end();) {
|
||||
if (*i == child) {
|
||||
delete *i;
|
||||
i = m_stl->m_children.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
BBNode *make(const ON_BoundingBox &node,
|
||||
const CurveTree *ct = NULL);
|
||||
BBNode *make(const CurveTree *ct, const ON_BoundingBox &node,
|
||||
const ON_Interval &u, const ON_Interval &v,
|
||||
bool checkTrim, bool trimmed);
|
||||
BBNode *finish(BBNode *root);
|
||||
|
||||
private:
|
||||
friend class BBNode;
|
||||
struct Block;
|
||||
void *allocate();
|
||||
BBNode *make(Deserializer &deserializer, const CurveTree &ctree);
|
||||
|
||||
Block *m_blocks;
|
||||
BBNode *m_anchor;
|
||||
bool m_started;
|
||||
};
|
||||
|
||||
inline const ON_BrepFace &
|
||||
BBNode::get_face() const
|
||||
@@ -203,16 +240,22 @@ namespace brlcad {
|
||||
return *m_ctree->m_face;
|
||||
}
|
||||
|
||||
inline const std::vector<BBNode *> &
|
||||
BBNode::get_children() const
|
||||
inline std::size_t
|
||||
BBNode::childCount() const
|
||||
{
|
||||
return m_stl->m_children;
|
||||
return m_child_count;
|
||||
}
|
||||
|
||||
inline BBNode *
|
||||
BBNode::child(std::size_t index) const
|
||||
{
|
||||
return index < m_child_count ? m_children[index] : NULL;
|
||||
}
|
||||
|
||||
inline bool
|
||||
BBNode::isLeaf() const
|
||||
{
|
||||
return m_stl->m_children.empty();
|
||||
return m_child_count == 0;
|
||||
}
|
||||
|
||||
inline void
|
||||
|
||||
+44
-99
@@ -33,8 +33,7 @@
|
||||
#ifdef __cplusplus
|
||||
extern "C++" {
|
||||
/* @cond */
|
||||
# include <vector>
|
||||
# include <list>
|
||||
# include <algorithm>
|
||||
/* @endcond */
|
||||
}
|
||||
#endif
|
||||
@@ -49,34 +48,19 @@ extern "C++" {
|
||||
namespace brlcad {
|
||||
|
||||
/**
|
||||
* Bounding Rectangle Hierarchy
|
||||
* Compact prepared trim-curve leaf.
|
||||
*/
|
||||
class BREP_EXPORT BRNode : public PooledObject<BRNode> {
|
||||
class BREP_EXPORT BRNode {
|
||||
public:
|
||||
explicit BRNode(const ON_BoundingBox &node);
|
||||
BRNode(const ON_Curve *curve,
|
||||
int trim_index,
|
||||
int adj_face_index,
|
||||
const ON_BoundingBox &node,
|
||||
const ON_BrepFace *face,
|
||||
const ON_Interval &t,
|
||||
bool innerTrim,
|
||||
bool checkTrim,
|
||||
bool trimmed);
|
||||
~BRNode();
|
||||
|
||||
bool innerTrim);
|
||||
BRNode(Deserializer &deserializer, const ON_Brep &brep);
|
||||
void serialize(Serializer &serializer) const;
|
||||
|
||||
/** Node management functions */
|
||||
void addChild(BRNode *child);
|
||||
|
||||
/** Return a list of all nodes below this node that are leaf nodes */
|
||||
void getLeaves(std::list<const BRNode *> &out_leaves) const;
|
||||
|
||||
/** Report the depth of this node in the hierarchy */
|
||||
int depth() const;
|
||||
|
||||
/**
|
||||
* Get 2 points defining bounding box:
|
||||
*
|
||||
@@ -91,19 +75,42 @@ namespace brlcad {
|
||||
*/
|
||||
void GetBBox(fastf_t *min, fastf_t *max) const;
|
||||
|
||||
bool isTrimmed(const ON_2dPoint &uv, double &trimdist) const;
|
||||
/** Test a vertical +V parameter-space ray against this monotone
|
||||
* trim leaf. Endpoint fuzz bridges small imported loop gaps while
|
||||
* the exact test uses a half-open interval to avoid double counting
|
||||
* shared vertices. */
|
||||
bool crossesAbove(const ON_2dPoint &uv, double &distance,
|
||||
double endpoint_tolerance = 0.0) const;
|
||||
/** Test a horizontal +U parameter-space ray using the same half-open
|
||||
* endpoint ownership as crossesAbove(). */
|
||||
bool crossesRight(const ON_2dPoint &uv, double &distance,
|
||||
double endpoint_tolerance = 0.0) const;
|
||||
/** Return a fixed-workspace estimate of the shortest UV distance from
|
||||
* a point to this trim leaf. */
|
||||
double curveDistance(const ON_2dPoint &uv) const;
|
||||
/** Lower bound from the leaf's sampled UV bounding box. */
|
||||
double curveBBoxDistance(const ON_2dPoint &uv) const;
|
||||
int trimIndex() const { return m_trim_index; }
|
||||
ON_Interval parameterInterval() const { return m_t; }
|
||||
ON_Interval curveDomain() const {
|
||||
return m_trim ? m_trim->Domain() : ON_Interval::EmptyInterval;
|
||||
}
|
||||
ON_3dPoint startPoint() const {
|
||||
return ON_3dPoint(m_start[0], m_start[1], 0.0);
|
||||
}
|
||||
ON_3dPoint endPoint() const {
|
||||
return ON_3dPoint(m_end[0], m_end[1], 0.0);
|
||||
}
|
||||
ON_2dPoint bboxMinimum() const {
|
||||
return ON_2dPoint(m_bbox_min[0], m_bbox_min[1]);
|
||||
}
|
||||
ON_2dPoint bboxMaximum() const {
|
||||
return ON_2dPoint(m_bbox_max[0], m_bbox_max[1]);
|
||||
}
|
||||
|
||||
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt) const;
|
||||
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const;
|
||||
fastf_t getCurveEstimateOfV(fastf_t u, fastf_t tol) const;
|
||||
fastf_t getCurveEstimateOfU(fastf_t v, fastf_t tol) const;
|
||||
|
||||
/** Bounding Box */
|
||||
ON_BoundingBox m_node;
|
||||
|
||||
/** Surface Information */
|
||||
ON_Interval m_v;
|
||||
|
||||
/** Trim Curve Information */
|
||||
int m_adj_face_index;
|
||||
|
||||
@@ -114,85 +121,23 @@ namespace brlcad {
|
||||
bool m_innerTrim;
|
||||
|
||||
private:
|
||||
BRNode(const BRNode &source);
|
||||
BRNode &operator=(const BRNode &source);
|
||||
|
||||
void removeChild(BRNode *child);
|
||||
|
||||
/** Test if this node is a leaf node (i.e. m_children is empty) */
|
||||
bool isLeaf() const;
|
||||
|
||||
fastf_t getLinearEstimateOfV(fastf_t u) const;
|
||||
const BRNode *closer(const ON_3dPoint &pt, const BRNode *left, const BRNode *right) const;
|
||||
|
||||
struct Stl : public PooledObject<Stl> {
|
||||
Stl() : m_children() {}
|
||||
|
||||
std::vector<const BRNode *> m_children;
|
||||
} * const m_stl;
|
||||
|
||||
const ON_BrepFace *m_face;
|
||||
ON_Interval m_u;
|
||||
const ON_Curve *m_trim;
|
||||
int m_trim_index;
|
||||
ON_Interval m_t;
|
||||
bool m_checkTrim;
|
||||
bool m_trimmed;
|
||||
ON_3dPoint m_estimate;
|
||||
fastf_t m_slope;
|
||||
fastf_t m_bb_diag;
|
||||
ON_3dPoint m_start;
|
||||
ON_3dPoint m_end;
|
||||
double m_bbox_min[2];
|
||||
double m_bbox_max[2];
|
||||
double m_start[2];
|
||||
double m_end[2];
|
||||
};
|
||||
|
||||
inline void
|
||||
BRNode::addChild(BRNode *child)
|
||||
{
|
||||
if (LIKELY(child != NULL)) {
|
||||
m_stl->m_children.push_back(child);
|
||||
}
|
||||
}
|
||||
|
||||
inline void
|
||||
BRNode::removeChild(BRNode *child)
|
||||
{
|
||||
std::vector<const BRNode *>::iterator i;
|
||||
for (i = m_stl->m_children.begin(); i != m_stl->m_children.end();) {
|
||||
if (*i == child) {
|
||||
delete *i;
|
||||
i = m_stl->m_children.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool
|
||||
BRNode::isLeaf() const
|
||||
{
|
||||
if (m_stl->m_children.empty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline void
|
||||
BRNode::GetBBox(fastf_t *min, fastf_t *max) const
|
||||
{
|
||||
VSETALL(min, INFINITY);
|
||||
VSETALL(max, -INFINITY);
|
||||
if (m_start != ON_3dPoint::UnsetPoint) {
|
||||
VMINMAX(min, max, m_start);
|
||||
}
|
||||
if (m_end != ON_3dPoint::UnsetPoint) {
|
||||
VMINMAX(min, max, m_end);
|
||||
}
|
||||
VSET(min, std::min(m_start[X], m_end[X]),
|
||||
std::min(m_start[Y], m_end[Y]), 0.0);
|
||||
VSET(max, std::max(m_start[X], m_end[X]),
|
||||
std::max(m_start[Y], m_end[Y]), 0.0);
|
||||
}
|
||||
|
||||
extern bool sortX(const BRNode *first, const BRNode *second);
|
||||
extern bool sortY(const BRNode *first, const BRNode *second);
|
||||
|
||||
|
||||
} /* namespace brlcad */
|
||||
} /* extern C++ */
|
||||
|
||||
|
||||
@@ -159,6 +159,9 @@ 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);
|
||||
extern BREP_EXPORT int cdt_test_boundary_start(void);
|
||||
extern BREP_EXPORT int cdt_test_boundary_steiner_filter(void);
|
||||
extern BREP_EXPORT int cdt_test_spurious_components(void);
|
||||
|
||||
__END_DECLS
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/* C O B B . 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.
|
||||
*/
|
||||
/** @file brep/cobb.h
|
||||
*
|
||||
* Reusable Cobb rational Bezier sphere fixtures.
|
||||
*/
|
||||
#ifndef BREP_COBB_H
|
||||
#define BREP_COBB_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include "brep/defines.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C++" {
|
||||
|
||||
class ON_3dPoint;
|
||||
class ON_BezierSurface;
|
||||
class ON_Brep;
|
||||
|
||||
/** Return one unit Cobb sphere patch after rotations in degrees. */
|
||||
BREP_EXPORT extern ON_BezierSurface *
|
||||
ON_Brep_CobbSphereFace(double rotation_x, double rotation_z);
|
||||
|
||||
/**
|
||||
* Return the legacy six-patch representation with independent face topology.
|
||||
* This is an open plate-mode fixture, not a solid.
|
||||
*/
|
||||
BREP_EXPORT extern ON_Brep *
|
||||
ON_Brep_CobbSphereUnsewn(double radius, const ON_3dPoint &origin);
|
||||
|
||||
/**
|
||||
* Return the same six support surfaces with eight shared vertices and twelve
|
||||
* shared edges. The result is a closed, oriented solid.
|
||||
*/
|
||||
BREP_EXPORT extern ON_Brep *
|
||||
ON_Brep_CobbSphereSewn(double radius, const ON_3dPoint &origin);
|
||||
|
||||
/** Measure maximum radial error on a uniform per-surface sample grid. */
|
||||
BREP_EXPORT extern double
|
||||
ON_Brep_CobbSphereMaxRadialError(const ON_Brep *brep, double radius,
|
||||
const ON_3dPoint &origin, int samples_per_direction = 33);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BREP_COBB_H */
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* mode: C++
|
||||
* tab-width: 8
|
||||
* indent-tabs-mode: t
|
||||
* c-file-style: "stroustrup"
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
+57
-29
@@ -32,11 +32,11 @@
|
||||
#include "common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
// @cond SKIP_C++_INCLUDE
|
||||
extern "C++" {
|
||||
# include <map>
|
||||
# include <cstdint>
|
||||
# include <list>
|
||||
# include <vector>
|
||||
}
|
||||
// @endcond
|
||||
#endif
|
||||
|
||||
#include "brep/defines.h"
|
||||
@@ -54,24 +54,39 @@ namespace brlcad {
|
||||
/**
|
||||
* CurveTree declaration
|
||||
*/
|
||||
class BREP_EXPORT CurveTree : public PooledObject<CurveTree> {
|
||||
class BREP_EXPORT CurveTree {
|
||||
public:
|
||||
explicit CurveTree(const ON_BrepFace *face);
|
||||
~CurveTree();
|
||||
|
||||
CurveTree(Deserializer &deserializer, const ON_BrepFace &face);
|
||||
void serialize(Serializer &serializer) const;
|
||||
std::vector<std::size_t> serialize_get_leaves_keys(const std::list<const BRNode *> &leaves) const;
|
||||
std::list<const BRNode *> serialize_get_leaves(const std::size_t *keys, std::size_t num_keys) const;
|
||||
void serialize_cleanup() const;
|
||||
|
||||
/**
|
||||
* Return just the leaves of the surface tree
|
||||
*/
|
||||
void getLeaves(std::list<const BRNode *> &out_leaves) const;
|
||||
void getLeavesAbove(std::list<const BRNode *> &out_leaves, const ON_Interval &u, const ON_Interval &v) const;
|
||||
void getLeavesAbove(std::list<const BRNode *> &out_leaves,
|
||||
const ON_Interval &u, const ON_Interval &v,
|
||||
fastf_t tol = BREP_UV_DIST_FUZZ) const;
|
||||
void getLeavesAbove(std::list<const BRNode *> &out_leaves, const ON_2dPoint &pt, fastf_t tol) const;
|
||||
void getLeavesRight(std::list<const BRNode *> &out_leaves, const ON_2dPoint &pt, fastf_t tol) const;
|
||||
std::size_t preparedLeafCount() const;
|
||||
std::size_t preparedLeafImageCount() const;
|
||||
int preparedMaximumDepth() const;
|
||||
|
||||
/** Classify a face parameter against all prepared trim leaves without
|
||||
* allocating a filtered list. */
|
||||
bool isTrimmed(const ON_2dPoint &uv, const BRNode **closest,
|
||||
double &closesttrim, double within_distance_tol,
|
||||
std::size_t *candidate_count = NULL,
|
||||
bool heal_join_gaps = true) const;
|
||||
|
||||
/** Certify a UV cell whose material state cannot change without
|
||||
* crossing a trim or a structural trim endpoint. Returns false when
|
||||
* the cell must retain pointwise trim checks. */
|
||||
bool classifyCell(const ON_Interval &u, const ON_Interval &v,
|
||||
double within_distance_tol, bool &trimmed) const;
|
||||
|
||||
private:
|
||||
friend class BBNode;
|
||||
@@ -79,37 +94,50 @@ namespace brlcad {
|
||||
CurveTree(const CurveTree &source);
|
||||
CurveTree &operator=(const CurveTree &source);
|
||||
|
||||
const BRNode *getRootNode() const;
|
||||
|
||||
/**
|
||||
* Calculate, using the surface bounding volume hierarchy, a uv
|
||||
* estimate for the closest point on the surface to the point in
|
||||
* 3-space.
|
||||
*/
|
||||
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt) const;
|
||||
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const;
|
||||
|
||||
void getLeavesRight(std::list<const BRNode *> &out_leaves, const ON_Interval &u, const ON_Interval &v) const;
|
||||
|
||||
int depth() const;
|
||||
|
||||
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;
|
||||
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);
|
||||
void prepareLoopLeaves();
|
||||
void subdivideCurve(const ON_Curve *curve, int trim_index,
|
||||
int adj_face_index, double min, double max, bool innerTrim,
|
||||
int depth);
|
||||
|
||||
const ON_BrepFace * const m_face;
|
||||
BRNode *m_root;
|
||||
|
||||
struct Stl {
|
||||
struct Loop {
|
||||
struct Leaf {
|
||||
Leaf() : node_index(0), image{0, 0},
|
||||
extend_minimum{false, false} {}
|
||||
Leaf(uint32_t leaf_index, int u_image, int v_image,
|
||||
bool extend_u_minimum, bool extend_v_minimum) :
|
||||
node_index(leaf_index), image{u_image, v_image},
|
||||
extend_minimum{extend_u_minimum,
|
||||
extend_v_minimum} {}
|
||||
|
||||
struct Stl : public PooledObject<Stl> {
|
||||
Stl() : m_sortedX() {}
|
||||
uint32_t node_index;
|
||||
int image[2];
|
||||
bool extend_minimum[2];
|
||||
};
|
||||
|
||||
std::vector<const BRNode *> m_sortedX;
|
||||
Loop() : index(-1), type(ON_BrepLoop::unknown), minimum(0.0, 0.0),
|
||||
maximum(0.0, 0.0), have_bbox(false), sorted() {}
|
||||
|
||||
int index;
|
||||
ON_BrepLoop::TYPE type;
|
||||
ON_2dPoint minimum;
|
||||
ON_2dPoint maximum;
|
||||
bool have_bbox;
|
||||
std::vector<Leaf> sorted;
|
||||
};
|
||||
|
||||
Stl() : m_leaves(), m_loops(), m_maximum_depth(0) {}
|
||||
|
||||
std::vector<BRNode> m_leaves;
|
||||
std::vector<Loop> m_loops;
|
||||
int m_maximum_depth;
|
||||
} * const m_stl;
|
||||
|
||||
mutable std::map<const BRNode *, std::size_t> *m_sortedX_indices;
|
||||
};
|
||||
|
||||
} /* namespace brlcad */
|
||||
|
||||
@@ -57,6 +57,14 @@ namespace brlcad {
|
||||
*/
|
||||
class BREP_EXPORT SurfaceTree {
|
||||
public:
|
||||
enum FailureReason {
|
||||
FAILURE_NONE = 0,
|
||||
FAILURE_NULL_SURFACE,
|
||||
FAILURE_BOUNDING_BOX,
|
||||
FAILURE_NORMAL_EVALUATION,
|
||||
FAILURE_SUBDIVISION
|
||||
};
|
||||
|
||||
explicit SurfaceTree(const ON_BrepFace *face, bool removeTrimmed = true, int depthLimit = BREP_MAX_FT_DEPTH, double within_distance_tol = BREP_EDGE_MISS_TOLERANCE);
|
||||
~SurfaceTree();
|
||||
|
||||
@@ -66,9 +74,29 @@ namespace brlcad {
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
/* The first deterministic construction failure and its local cell. */
|
||||
FailureReason Failure() const { return m_failure; };
|
||||
ON_Interval FailureDomain(int direction) const {
|
||||
return direction == 0 ? m_failure_u : m_failure_v;
|
||||
};
|
||||
int FailureDepth() const { return m_failure_depth; };
|
||||
|
||||
BBNode *getRootNode() const;
|
||||
|
||||
/**
|
||||
* Transfer ownership of the prepared surface hierarchy to a caller.
|
||||
* The returned node must be deleted by the caller. After this call the
|
||||
* SurfaceTree destructor will not delete the hierarchy.
|
||||
*/
|
||||
BBNode *releaseRootNode();
|
||||
|
||||
/**
|
||||
* Transfer ownership of the curve hierarchy referenced by the prepared
|
||||
* BBNodes to a caller. The curve tree must outlive the BBNode hierarchy.
|
||||
* After this call the SurfaceTree destructor will not delete it.
|
||||
*/
|
||||
const CurveTree *releaseCurveTree();
|
||||
|
||||
/**
|
||||
* Calculate, using the surface bounding volume hierarchy, a uv
|
||||
* estimate for the closest point on the surface to the point in
|
||||
@@ -100,14 +128,21 @@ namespace brlcad {
|
||||
bool isFlatU(const ON_Plane frames[9]) const;
|
||||
bool isFlatV(const ON_Plane frames[9]) const;
|
||||
bool hasSplit(const ON_Surface *surf, int dir, const ON_Interval &interval, double &split) const;
|
||||
BBNode *initialBBox(const CurveTree *ctree, const ON_Surface *surf, const ON_Interval &u, const ON_Interval &v, int depth) const;
|
||||
BBNode *subdivideSurface(const ON_Surface *localsurf, const ON_Interval &u, const ON_Interval &v, ON_Plane frames[9], int depth, int depthLimit, int prev_knot, double within_distance_tol) const;
|
||||
BBNode *surfaceBBox(const ON_Surface *localsurf, bool leaf, const ON_Plane frames[9], const ON_Interval &u, const ON_Interval &v, double within_distance_tol) const;
|
||||
BBNode *surfaceBBox(const ON_Surface *localsurf, bool leaf, const ON_Plane frames[9], const ON_Interval &u, const ON_Interval &v, int depth, double within_distance_tol) const;
|
||||
void recordFailure(FailureReason reason, const ON_Interval &u, const ON_Interval &v, int depth) const;
|
||||
|
||||
const bool m_removeTrimmed;
|
||||
const ON_BrepFace * const m_face;
|
||||
BBNode *m_root;
|
||||
BBNodeArena *m_node_arena;
|
||||
std::queue<ON_Plane *> * const m_f_queue;
|
||||
SurfaceSplitCache * const m_split_cache;
|
||||
mutable FailureReason m_failure;
|
||||
mutable ON_Interval m_failure_u;
|
||||
mutable ON_Interval m_failure_v;
|
||||
mutable int m_failure_depth;
|
||||
};
|
||||
|
||||
} /* namespace brlcad */
|
||||
|
||||
+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, ...);
|
||||
|
||||
+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;
|
||||
|
||||
@@ -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) */
|
||||
|
||||
@@ -219,8 +219,8 @@ RT_EXPORT extern int rt_gettrees(struct rt_i *rtip,
|
||||
* observing nodes or soltabs that the finishing pass will subsequently free.
|
||||
*
|
||||
* Semaphores used for critical sections in parallel mode:
|
||||
* RT_SEM_TREE ====> protects rtip->i->rti_solidheads[] lists, d_uses(solids)
|
||||
* RT_SEM_RESULTS => protects HeadRegion, mdl_min/max, d_uses(reg), nregions
|
||||
* RT_SEM_TREE ====> protects rti_solidheads[], directory d_uses, prep publish
|
||||
* RT_SEM_RESULTS => protects HeadRegion, mdl_min/max, nregions
|
||||
* RT_SEM_WORKER ==> (db_walk_dispatcher, from db_walk_tree)
|
||||
* RT_SEM_STATS ===> nsolids
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-1
@@ -60,6 +60,7 @@ struct soltab {
|
||||
struct rt_i * st_rtip; /**< @brief "up" pointer to rt_i */
|
||||
long st_uses; /**< @brief Usage count, for instanced solids */
|
||||
int st_id; /**< @brief Solid ident */
|
||||
int st_prep_complete; /**< @brief prep publication flag */
|
||||
point_t st_center; /**< @brief Centroid of solid */
|
||||
fastf_t st_aradius; /**< @brief Radius of APPROXIMATING sphere */
|
||||
fastf_t st_bradius; /**< @brief Radius of BOUNDING sphere */
|
||||
@@ -77,7 +78,7 @@ struct soltab {
|
||||
struct bound_rpp * st_piece_rpps; /**< @brief bounding RPP of each piece of this solid */
|
||||
};
|
||||
|
||||
#define RT_SOLTAB_INIT_ZERO {BU_LIST_INIT_ZERO, BU_LIST_INIT_ZERO, NULL, NULL, 0, 0, VINIT_ZERO, 0.0, 0.0, NULL, NULL, VINIT_ZERO, VINIT_ZERO, 0, BU_PTBL_INIT_ZERO, NULL, DB_FULL_PATH_INIT_ZERO, 0, 0, NULL}
|
||||
#define RT_SOLTAB_INIT_ZERO {BU_LIST_INIT_ZERO, BU_LIST_INIT_ZERO, NULL, NULL, 0, 0, 0, VINIT_ZERO, 0.0, 0.0, NULL, NULL, VINIT_ZERO, VINIT_ZERO, 0, BU_PTBL_INIT_ZERO, NULL, DB_FULL_PATH_INIT_ZERO, 0, 0, NULL}
|
||||
|
||||
#define st_name st_dp->d_namep
|
||||
#define RT_SOLTAB_NULL ((struct soltab *)0)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) ---"
|
||||
|
||||
@@ -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,7 @@ add_subdirectory(gsh)
|
||||
add_subdirectory(tests)
|
||||
|
||||
cmakefiles(
|
||||
brep-audit.cpp
|
||||
CMakeLists.txt
|
||||
)
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
@@ -1,5 +1,34 @@
|
||||
brlcad_addexec(bigdb bigdb.c "libged;libwdb;librt;libbu" TEST_USESDATA)
|
||||
|
||||
brlcad_add_test(
|
||||
NAME brep_audit_surface_trees_all
|
||||
COMMAND
|
||||
brep-audit --surface-trees-all --surface-tree-depth=0
|
||||
${CMAKE_SOURCE_DIR}/regress/nurbs/sph.g
|
||||
)
|
||||
|
||||
brlcad_add_test(
|
||||
NAME brep_audit_prep_repeat
|
||||
COMMAND
|
||||
brep-audit --prep-only --prep-repeat=3
|
||||
${CMAKE_SOURCE_DIR}/regress/nurbs/sph.g sph.brep
|
||||
)
|
||||
|
||||
brlcad_add_test(
|
||||
NAME brep_audit_ray_depth_compare
|
||||
COMMAND
|
||||
brep-audit --ray-depth-compare --ray-count=4 --feature-ray-count=3
|
||||
${CMAKE_SOURCE_DIR}/regress/nurbs/sph.g sph.brep
|
||||
)
|
||||
|
||||
brlcad_add_test(
|
||||
NAME brep_audit_ray_depth_compare_multiface
|
||||
COMMAND
|
||||
brep-audit --ray-depth-compare --ray-count=4 --feature-ray-count=3
|
||||
${CMAKE_SOURCE_DIR}/src/libbrep/tests/nurbs_csg.g
|
||||
recursive_child_linkage_complex_subtractions.s
|
||||
)
|
||||
|
||||
brlcad_add_test(NAME slow-bigdb_1gb COMMAND bigdb 1)
|
||||
if(TARGET slow-bigdb_1gb)
|
||||
set_property(TEST slow-bigdb_1gb PROPERTY SKIP_RETURN_CODE 123)
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
+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;
|
||||
|
||||
+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;
|
||||
}
|
||||
|
||||
|
||||
+796
-134
@@ -8,8 +8,8 @@
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
@@ -19,170 +19,832 @@
|
||||
*/
|
||||
/** @file spsr.cpp
|
||||
*
|
||||
* Interface to Adaptive Multigrid Solvers for Screened Poisson Surface Reconstruction
|
||||
* Compatible with mkazhdan/PoissonRecon API (v18.x+).
|
||||
*
|
||||
* Screened Poisson surface reconstruction for fixed and adaptive oriented
|
||||
* point sets.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "vmath.h"
|
||||
#include "bu/log.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "bg/spsr.h"
|
||||
#include "RTree.h"
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic push /* start new diagnostic pragma */
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wall"
|
||||
#elif defined(__clang__)
|
||||
# pragma clang diagnostic push /* start new diagnostic pragma */
|
||||
# pragma clang diagnostic push
|
||||
# pragma clang diagnostic ignored "-Weverything"
|
||||
#endif
|
||||
#include "SPSR/Reconstructors.h"
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic pop /* end ignoring warnings */
|
||||
# pragma GCC diagnostic pop
|
||||
#elif defined(__clang__)
|
||||
# pragma clang diagnostic pop /* end ignoring warnings */
|
||||
# pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
using namespace PoissonRecon;
|
||||
|
||||
// Input stream for oriented samples: implements `read` for coordinates and normals
|
||||
template<typename Real, unsigned int Dim>
|
||||
struct PtStream : public Reconstructor::InputOrientedSampleStream<Real, Dim>
|
||||
{
|
||||
PtStream(const point_t *ipnts, const vect_t *inorms, int cnt)
|
||||
: _size(cnt), _current(0), _input_pnts(ipnts), _input_nrmls(inorms) {}
|
||||
void reset(void) { _current = 0; }
|
||||
bool read(Point<Real, Dim> &p, Point<Real, Dim> &n)
|
||||
{
|
||||
if (_current < _size) {
|
||||
for (size_t d = 0; d < Dim; d++)
|
||||
p[d] = _input_pnts[_current][d];
|
||||
for (size_t d = 0; d < Dim; d++)
|
||||
n[d] = _input_nrmls[_current][d];
|
||||
_current++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
protected:
|
||||
unsigned int _size, _current;
|
||||
const point_t *_input_pnts;
|
||||
const vect_t *_input_nrmls;
|
||||
namespace {
|
||||
|
||||
using clock_type = std::chrono::steady_clock;
|
||||
using real_type = fastf_t;
|
||||
|
||||
constexpr size_t SPSR_MAX_HINTS = 4096;
|
||||
constexpr size_t SPSR_MIN_NEIGHBORS = 4;
|
||||
constexpr double SPSR_NORMAL_ANGLE_DEGREES = 15.0;
|
||||
constexpr double SPSR_DEDUP_FRACTION = 0.25;
|
||||
constexpr int SPSR_MAX_DEPTH = 30;
|
||||
|
||||
struct spsr_mesh {
|
||||
std::vector<int> faces;
|
||||
std::vector<real_type> vertices;
|
||||
};
|
||||
|
||||
// Output face stream: must implement write() and size()
|
||||
template <typename Index>
|
||||
struct PolygonStream : public Reconstructor::OutputFaceStream<2>
|
||||
template<typename Real, unsigned int Dim>
|
||||
struct point_stream : public Reconstructor::InputOrientedSampleStream<Real, Dim>
|
||||
{
|
||||
PolygonStream(std::vector<std::vector<Index>> &polygonStream) : _polygons(polygonStream) {}
|
||||
size_t size(void) const { return _polygons.size(); }
|
||||
point_stream(const std::vector<struct bg_3d_spsr_sample> &samples)
|
||||
: current(0), input(samples)
|
||||
{
|
||||
}
|
||||
|
||||
void reset(void)
|
||||
{
|
||||
current = 0;
|
||||
}
|
||||
|
||||
bool read(Point<Real, Dim> &point, Point<Real, Dim> &normal)
|
||||
{
|
||||
if (current >= input.size())
|
||||
return false;
|
||||
|
||||
for (size_t dimension = 0; dimension < Dim; dimension++) {
|
||||
point[dimension] = input[current].point[dimension];
|
||||
normal[dimension] = input[current].normal[dimension];
|
||||
}
|
||||
current++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t current;
|
||||
const std::vector<struct bg_3d_spsr_sample> &input;
|
||||
};
|
||||
|
||||
template<typename Index>
|
||||
struct polygon_stream : public Reconstructor::OutputFaceStream<2>
|
||||
{
|
||||
polygon_stream(std::vector<std::vector<Index>> &output) : polygons(output)
|
||||
{
|
||||
}
|
||||
|
||||
size_t size(void) const
|
||||
{
|
||||
return polygons.size();
|
||||
}
|
||||
|
||||
size_t write(const std::vector<node_index_type> &polygon)
|
||||
{
|
||||
std::vector<Index> poly(polygon.size());
|
||||
for (size_t i = 0; i < polygon.size(); i++) poly[i] = (Index)polygon[i];
|
||||
_polygons.push_back(poly);
|
||||
return _polygons.size() - 1;
|
||||
std::vector<Index> converted(polygon.size());
|
||||
for (size_t i = 0; i < polygon.size(); i++)
|
||||
converted[i] = static_cast<Index>(polygon[i]);
|
||||
polygons.push_back(std::move(converted));
|
||||
return polygons.size() - 1;
|
||||
}
|
||||
protected:
|
||||
std::vector<std::vector<Index>> &_polygons;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<Index>> &polygons;
|
||||
};
|
||||
|
||||
// Output vertex stream: must implement write() and size()
|
||||
template <typename Real, unsigned int Dim>
|
||||
struct VertexStream : public Reconstructor::OutputLevelSetVertexStream<Real, Dim>
|
||||
template<typename Real, unsigned int Dim>
|
||||
struct vertex_stream : public Reconstructor::OutputLevelSetVertexStream<Real, Dim>
|
||||
{
|
||||
VertexStream(std::vector<Real> &vCoordinates) : _vCoordinates(vCoordinates) {}
|
||||
size_t size(void) const { return _vCoordinates.size() / Dim; }
|
||||
size_t write(const Point<Real, Dim> &p, const Point<Real, Dim> &, const Real &)
|
||||
vertex_stream(std::vector<Real> &output) : coordinates(output)
|
||||
{
|
||||
for (unsigned int d = 0; d < Dim; d++) _vCoordinates.push_back(p[d]);
|
||||
return _vCoordinates.size() / Dim - 1;
|
||||
}
|
||||
protected:
|
||||
std::vector<Real> &_vCoordinates;
|
||||
|
||||
size_t size(void) const
|
||||
{
|
||||
return coordinates.size() / Dim;
|
||||
}
|
||||
|
||||
size_t write(const Point<Real, Dim> &point, const Point<Real, Dim> &,
|
||||
const Real &)
|
||||
{
|
||||
for (unsigned int dimension = 0; dimension < Dim; dimension++)
|
||||
coordinates.push_back(point[dimension]);
|
||||
return coordinates.size() / Dim - 1;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<Real> &coordinates;
|
||||
};
|
||||
|
||||
extern "C" int
|
||||
bg_3d_spsr(int **faces, int *num_faces, point_t **points, int *num_pnts,
|
||||
const point_t *input_points_3d, const vect_t *input_normals_3d,
|
||||
int num_input_pnts, struct bg_3d_spsr_opts *spsr_opts)
|
||||
static double
|
||||
elapsed_seconds(const clock_type::time_point &start)
|
||||
{
|
||||
using Real = fastf_t;
|
||||
static const unsigned int FEMSig = FEMDegreeAndBType<Reconstructor::Poisson::DefaultFEMDegree, Reconstructor::Poisson::DefaultFEMBoundary>::Signature;
|
||||
using FEMSigs = IsotropicUIntPack<3, FEMSig>;
|
||||
|
||||
// Set up multithreading
|
||||
PoissonRecon::ThreadPool::ParallelizationType = PoissonRecon::ThreadPool::ASYNC;
|
||||
|
||||
// Solver and extraction parameters
|
||||
Reconstructor::Poisson::SolutionParameters<Real> solverParams;
|
||||
solverParams.verbose = false;
|
||||
solverParams.depth = (spsr_opts) ? spsr_opts->depth : 8;
|
||||
solverParams.fullDepth = (spsr_opts) ? spsr_opts->full_depth : 11;
|
||||
solverParams.samplesPerNode = (spsr_opts) ? spsr_opts->samples_per_node : 1.5;
|
||||
solverParams.exactInterpolation = true;
|
||||
|
||||
Reconstructor::LevelSetExtractionParameters extractionParams;
|
||||
extractionParams.forceManifold = true;
|
||||
extractionParams.linearFit = false;
|
||||
extractionParams.polygonMesh = false;
|
||||
extractionParams.verbose = false;
|
||||
|
||||
PtStream<Real, 3> vstream(input_points_3d, input_normals_3d, num_input_pnts);
|
||||
|
||||
using Implicit = Reconstructor::Implicit<Real, 3, FEMSigs>;
|
||||
using Solver = Reconstructor::Poisson::Solver<Real, 3, FEMSigs>;
|
||||
Implicit *implicit = Solver::Solve(vstream, solverParams);
|
||||
|
||||
if (!implicit) {
|
||||
bu_log("PoissonRecon: Solver::Solve failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<std::vector<int>> polygons;
|
||||
std::vector<Real> vCoordinates;
|
||||
PolygonStream<int> pStream(polygons);
|
||||
VertexStream<Real, 3> vStream(vCoordinates);
|
||||
|
||||
implicit->extractLevelSet(vStream, pStream, extractionParams);
|
||||
|
||||
*num_faces = (int)(polygons.size());
|
||||
*num_pnts = (int)(vCoordinates.size() / 3);
|
||||
bu_log("Point cnt: %d\n", *num_pnts);
|
||||
bu_log("Face cnt: %d\n", *num_faces);
|
||||
|
||||
// Allocate output arrays
|
||||
(*faces) = (int *)bu_calloc(*num_faces * 3, sizeof(int), "faces array");
|
||||
(*points) = (point_t *)bu_calloc(*num_pnts, sizeof(point_t), "points array");
|
||||
|
||||
// Copy faces (triangulated output expected)
|
||||
for (int i = 0; i < *num_faces; i++) {
|
||||
if (polygons[i].size() != 3) {
|
||||
bu_log("Warning: polygon %d is not a triangle (has %zu vertices)\n", i, polygons[i].size());
|
||||
// TODO - triangulate if we don't have a triangle.
|
||||
continue;
|
||||
}
|
||||
(*faces)[3*i+0] = polygons[i][0];
|
||||
(*faces)[3*i+1] = polygons[i][1];
|
||||
(*faces)[3*i+2] = polygons[i][2];
|
||||
}
|
||||
// Copy points
|
||||
for (int i = 0; i < *num_pnts; i++) {
|
||||
(*points)[i][X] = vCoordinates[3*i+0];
|
||||
(*points)[i][Y] = vCoordinates[3*i+1];
|
||||
(*points)[i][Z] = vCoordinates[3*i+2];
|
||||
}
|
||||
|
||||
delete implicit;
|
||||
return 0;
|
||||
return std::chrono::duration<double>(clock_type::now() - start).count();
|
||||
}
|
||||
|
||||
static bool
|
||||
finite_vector(const fastf_t *vector)
|
||||
{
|
||||
return std::isfinite(vector[X]) && std::isfinite(vector[Y]) &&
|
||||
std::isfinite(vector[Z]);
|
||||
}
|
||||
|
||||
// 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
|
||||
static bool
|
||||
valid_options(const struct bg_3d_spsr_opts &options)
|
||||
{
|
||||
if (options.degree != BG_3D_SPSR_DEFAULT_DEGREE ||
|
||||
options.btype != BG_3D_SPSR_BOUNDARY_NEUMANN ||
|
||||
options.max_memory_GB != BG_3D_SPSR_DEFAULT_MAX_MEM ||
|
||||
options.threads != BG_3D_SPSR_DEFAULT_THREADS ||
|
||||
!NEAR_ZERO(options.confidence, SMALL_FASTF) ||
|
||||
!NEAR_ZERO(options.confidence_bias, SMALL_FASTF))
|
||||
return false;
|
||||
|
||||
if (options.depth < 1 || options.depth > SPSR_MAX_DEPTH ||
|
||||
options.kerneldepth < 0 || options.iterations < 0 ||
|
||||
options.full_depth < 0 || options.base_depth < 0 ||
|
||||
options.baseVcycles < 0 || options.samples_per_node <= 0.0 ||
|
||||
options.scale < 1.0 || options.width < 0.0 ||
|
||||
options.cgsolver_accuracy <= 0.0 || options.point_weight < 0.0)
|
||||
return false;
|
||||
|
||||
return options.nonManifold >= 0 && options.nonManifold <= 1 &&
|
||||
options.linearFit >= 0 && options.linearFit <= 1 &&
|
||||
options.exact >= 0 && options.exact <= 1;
|
||||
}
|
||||
|
||||
static void
|
||||
set_solver_options(
|
||||
Reconstructor::Poisson::SolutionParameters<real_type> &solver,
|
||||
Reconstructor::LevelSetExtractionParameters &extraction,
|
||||
const struct bg_3d_spsr_opts &options)
|
||||
{
|
||||
solver.depth = static_cast<unsigned int>(options.depth);
|
||||
solver.kernelDepth = options.kerneldepth ?
|
||||
static_cast<unsigned int>(options.kerneldepth) :
|
||||
static_cast<unsigned int>(-1);
|
||||
solver.iters = options.iterations ?
|
||||
static_cast<unsigned int>(options.iterations) : 8u;
|
||||
solver.fullDepth = options.full_depth ?
|
||||
static_cast<unsigned int>(options.full_depth) :
|
||||
std::min(5u, solver.depth);
|
||||
solver.baseDepth = options.base_depth ?
|
||||
static_cast<unsigned int>(options.base_depth) :
|
||||
static_cast<unsigned int>(-1);
|
||||
solver.baseVCycles = options.baseVcycles ?
|
||||
static_cast<unsigned int>(options.baseVcycles) : 1u;
|
||||
solver.samplesPerNode = options.samples_per_node;
|
||||
solver.scale = options.scale;
|
||||
solver.width = options.width;
|
||||
solver.cgSolverAccuracy = options.cgsolver_accuracy;
|
||||
solver.pointWeight = options.point_weight;
|
||||
solver.exactInterpolation = options.exact != 0;
|
||||
solver.confidence = false;
|
||||
solver.verbose = false;
|
||||
solver.showResidual = false;
|
||||
|
||||
extraction.forceManifold = options.nonManifold == 0;
|
||||
extraction.linearFit = options.linearFit != 0;
|
||||
extraction.polygonMesh = false;
|
||||
extraction.verbose = false;
|
||||
}
|
||||
|
||||
static fastf_t
|
||||
sample_extent(const std::vector<struct bg_3d_spsr_sample> &samples)
|
||||
{
|
||||
point_t minimum;
|
||||
point_t maximum;
|
||||
VSETALL(minimum, INFINITY);
|
||||
VSETALL(maximum, -INFINITY);
|
||||
for (const auto &sample : samples)
|
||||
VMINMAX(minimum, maximum, sample.point);
|
||||
return DIST_PNT_PNT(minimum, maximum);
|
||||
}
|
||||
|
||||
class sample_set
|
||||
{
|
||||
public:
|
||||
sample_set(fastf_t tolerance, size_t limit) :
|
||||
distance_tolerance(tolerance), distance_tolerance_sq(tolerance * tolerance),
|
||||
point_limit(limit)
|
||||
{
|
||||
}
|
||||
|
||||
bool append(const struct bg_3d_spsr_sample &input)
|
||||
{
|
||||
if (!finite_vector(input.point) || !finite_vector(input.normal))
|
||||
return false;
|
||||
|
||||
struct bg_3d_spsr_sample sample = input;
|
||||
fastf_t normal_length = MAGNITUDE(sample.normal);
|
||||
if (!std::isfinite(normal_length) || normal_length <= VUNITIZE_TOL)
|
||||
return false;
|
||||
VSCALE(sample.normal, sample.normal, 1.0 / normal_length);
|
||||
|
||||
double minimum[3];
|
||||
double maximum[3];
|
||||
for (size_t dimension = 0; dimension < 3; dimension++) {
|
||||
minimum[dimension] = sample.point[dimension] - distance_tolerance;
|
||||
maximum[dimension] = sample.point[dimension] + distance_tolerance;
|
||||
}
|
||||
|
||||
bool duplicate = false;
|
||||
index.Search(minimum, maximum,
|
||||
[&sample, this, &duplicate](const size_t &candidate, void *) {
|
||||
if (DIST_PNT_PNT_SQ(sample.point, values[candidate].point) <=
|
||||
distance_tolerance_sq) {
|
||||
duplicate = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, NULL);
|
||||
if (duplicate || (point_limit && values.size() >= point_limit))
|
||||
return false;
|
||||
|
||||
size_t sample_index = values.size();
|
||||
values.push_back(sample);
|
||||
double point[3] = {
|
||||
sample.point[X], sample.point[Y], sample.point[Z]
|
||||
};
|
||||
index.Insert(point, point, sample_index);
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<struct bg_3d_spsr_sample> &samples(void) const
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
bool full(void) const
|
||||
{
|
||||
return point_limit && values.size() >= point_limit;
|
||||
}
|
||||
|
||||
private:
|
||||
fastf_t distance_tolerance;
|
||||
fastf_t distance_tolerance_sq;
|
||||
size_t point_limit;
|
||||
std::vector<struct bg_3d_spsr_sample> values;
|
||||
RTree<size_t, double, 3> index;
|
||||
};
|
||||
|
||||
static void
|
||||
append_hint(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
|
||||
const point_t point, const vect_t normal, fastf_t error,
|
||||
unsigned int reasons)
|
||||
{
|
||||
struct bg_3d_spsr_refinement_hint hint = {};
|
||||
VMOVE(hint.point, point);
|
||||
VMOVE(hint.normal, normal);
|
||||
hint.error = error;
|
||||
hint.reasons = reasons;
|
||||
hints.push_back(hint);
|
||||
}
|
||||
|
||||
template<typename Implicit>
|
||||
static void
|
||||
sample_hints(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
|
||||
const std::vector<struct bg_3d_spsr_sample> &samples,
|
||||
const Implicit &implicit, fastf_t feature_size)
|
||||
{
|
||||
const fastf_t normal_dot_limit =
|
||||
std::cos(SPSR_NORMAL_ANGLE_DEGREES * DEG2RAD);
|
||||
typename Implicit::Evaluator evaluator = implicit.evaluator();
|
||||
|
||||
for (const auto &sample : samples) {
|
||||
Point<real_type, 3> point;
|
||||
for (size_t dimension = 0; dimension < 3; dimension++)
|
||||
point[dimension] = sample.point[dimension];
|
||||
|
||||
try {
|
||||
Point<real_type, 3> gradient = evaluator.grad(point);
|
||||
real_type gradient_length = std::sqrt(
|
||||
gradient[0] * gradient[0] + gradient[1] * gradient[1] +
|
||||
gradient[2] * gradient[2]);
|
||||
unsigned int reasons = 0;
|
||||
fastf_t error = feature_size;
|
||||
vect_t normal;
|
||||
VSET(normal, gradient[0], gradient[1], gradient[2]);
|
||||
|
||||
if (gradient_length <= VUNITIZE_TOL) {
|
||||
reasons = BG_3D_SPSR_REFINE_RESIDUAL |
|
||||
BG_3D_SPSR_REFINE_NORMAL;
|
||||
VSETALL(normal, 0.0);
|
||||
} else {
|
||||
fastf_t residual = std::fabs(
|
||||
evaluator(point) - implicit.isoValue) / gradient_length;
|
||||
fastf_t normal_dot = std::fabs(
|
||||
VDOT(sample.normal, normal) / gradient_length);
|
||||
if (residual > feature_size) {
|
||||
reasons |= BG_3D_SPSR_REFINE_RESIDUAL;
|
||||
error = residual;
|
||||
}
|
||||
if (normal_dot < normal_dot_limit) {
|
||||
reasons |= BG_3D_SPSR_REFINE_NORMAL;
|
||||
error = std::max(error,
|
||||
feature_size * (1.0 + normal_dot_limit - normal_dot));
|
||||
}
|
||||
VSCALE(normal, normal, 1.0 / gradient_length);
|
||||
}
|
||||
|
||||
if (reasons)
|
||||
append_hint(hints, sample.point, normal, error, reasons);
|
||||
} catch (const std::exception &) {
|
||||
append_hint(hints, sample.point, sample.normal,
|
||||
2.0 * feature_size, BG_3D_SPSR_REFINE_RESIDUAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
density_hints(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
|
||||
const std::vector<struct bg_3d_spsr_sample> &samples,
|
||||
fastf_t feature_size)
|
||||
{
|
||||
RTree<size_t, double, 3> index;
|
||||
for (size_t i = 0; i < samples.size(); i++) {
|
||||
double point[3] = {
|
||||
samples[i].point[X], samples[i].point[Y], samples[i].point[Z]
|
||||
};
|
||||
index.Insert(point, point, i);
|
||||
}
|
||||
|
||||
fastf_t search_distance_sq = feature_size * feature_size;
|
||||
for (size_t i = 0; i < samples.size(); i++) {
|
||||
double minimum[3];
|
||||
double maximum[3];
|
||||
for (size_t dimension = 0; dimension < 3; dimension++) {
|
||||
minimum[dimension] =
|
||||
samples[i].point[dimension] - feature_size;
|
||||
maximum[dimension] =
|
||||
samples[i].point[dimension] + feature_size;
|
||||
}
|
||||
|
||||
size_t neighbor_count = 0;
|
||||
index.Search(minimum, maximum,
|
||||
[&samples, i, search_distance_sq, &neighbor_count](
|
||||
const size_t &candidate, void *) {
|
||||
if (DIST_PNT_PNT_SQ(samples[i].point,
|
||||
samples[candidate].point) <= search_distance_sq)
|
||||
neighbor_count++;
|
||||
return neighbor_count < SPSR_MIN_NEIGHBORS;
|
||||
}, NULL);
|
||||
|
||||
if (neighbor_count < SPSR_MIN_NEIGHBORS)
|
||||
append_hint(hints, samples[i].point, samples[i].normal,
|
||||
feature_size, BG_3D_SPSR_REFINE_DENSITY);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
surface_variation_hints(
|
||||
std::vector<struct bg_3d_spsr_refinement_hint> &hints,
|
||||
const spsr_mesh &mesh, fastf_t feature_size)
|
||||
{
|
||||
size_t vertex_count = mesh.vertices.size() / 3;
|
||||
std::vector<std::array<fastf_t, 3>> first_normal(vertex_count);
|
||||
std::vector<fastf_t> variation(vertex_count, 0.0);
|
||||
std::vector<unsigned char> have_normal(vertex_count, 0);
|
||||
const fastf_t variation_limit =
|
||||
1.0 - std::cos(SPSR_NORMAL_ANGLE_DEGREES * DEG2RAD);
|
||||
|
||||
for (size_t face = 0; face < mesh.faces.size() / 3; face++) {
|
||||
int ia = mesh.faces[3 * face];
|
||||
int ib = mesh.faces[3 * face + 1];
|
||||
int ic = mesh.faces[3 * face + 2];
|
||||
const fastf_t *a = &mesh.vertices[3 * static_cast<size_t>(ia)];
|
||||
const fastf_t *b = &mesh.vertices[3 * static_cast<size_t>(ib)];
|
||||
const fastf_t *c = &mesh.vertices[3 * static_cast<size_t>(ic)];
|
||||
vect_t ab;
|
||||
vect_t ac;
|
||||
vect_t normal;
|
||||
VSUB2(ab, b, a);
|
||||
VSUB2(ac, c, a);
|
||||
VCROSS(normal, ab, ac);
|
||||
fastf_t length = MAGNITUDE(normal);
|
||||
if (length <= VUNITIZE_TOL)
|
||||
continue;
|
||||
VSCALE(normal, normal, 1.0 / length);
|
||||
|
||||
const int vertices[3] = {ia, ib, ic};
|
||||
for (size_t corner = 0; corner < 3; corner++) {
|
||||
size_t vertex = static_cast<size_t>(vertices[corner]);
|
||||
if (!have_normal[vertex]) {
|
||||
VMOVE(first_normal[vertex].data(), normal);
|
||||
have_normal[vertex] = 1;
|
||||
} else {
|
||||
variation[vertex] = std::max(variation[vertex],
|
||||
1.0 - std::fabs(VDOT(first_normal[vertex].data(), normal)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t vertex = 0; vertex < vertex_count; vertex++) {
|
||||
if (variation[vertex] <= variation_limit)
|
||||
continue;
|
||||
const fastf_t *point = &mesh.vertices[3 * vertex];
|
||||
append_hint(hints, point, first_normal[vertex].data(),
|
||||
feature_size * (1.0 + variation[vertex]),
|
||||
BG_3D_SPSR_REFINE_SURFACE_VARIATION);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
limit_hints(std::vector<struct bg_3d_spsr_refinement_hint> &hints,
|
||||
fastf_t feature_size)
|
||||
{
|
||||
std::sort(hints.begin(), hints.end(),
|
||||
[](const struct bg_3d_spsr_refinement_hint &left,
|
||||
const struct bg_3d_spsr_refinement_hint &right) {
|
||||
return left.error > right.error;
|
||||
});
|
||||
|
||||
RTree<size_t, double, 3> locations;
|
||||
std::vector<struct bg_3d_spsr_refinement_hint> selected;
|
||||
selected.reserve(std::min(hints.size(), SPSR_MAX_HINTS));
|
||||
fastf_t separation = feature_size * SPSR_DEDUP_FRACTION;
|
||||
fastf_t separation_sq = separation * separation;
|
||||
|
||||
for (const auto &hint : hints) {
|
||||
double minimum[3];
|
||||
double maximum[3];
|
||||
for (size_t dimension = 0; dimension < 3; dimension++) {
|
||||
minimum[dimension] = hint.point[dimension] - separation;
|
||||
maximum[dimension] = hint.point[dimension] + separation;
|
||||
}
|
||||
|
||||
bool nearby = false;
|
||||
locations.Search(minimum, maximum,
|
||||
[&hint, &selected, separation_sq, &nearby](
|
||||
const size_t &candidate, void *) {
|
||||
if (DIST_PNT_PNT_SQ(hint.point,
|
||||
selected[candidate].point) <= separation_sq) {
|
||||
nearby = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, NULL);
|
||||
if (nearby)
|
||||
continue;
|
||||
|
||||
size_t hint_index = selected.size();
|
||||
selected.push_back(hint);
|
||||
double point[3] = {hint.point[X], hint.point[Y], hint.point[Z]};
|
||||
locations.Insert(point, point, hint_index);
|
||||
if (selected.size() == SPSR_MAX_HINTS)
|
||||
break;
|
||||
}
|
||||
|
||||
hints.swap(selected);
|
||||
}
|
||||
|
||||
template<typename Implicit>
|
||||
static bool
|
||||
extract_mesh(spsr_mesh &mesh, const Implicit &implicit,
|
||||
const Reconstructor::LevelSetExtractionParameters ¶meters)
|
||||
{
|
||||
std::vector<std::vector<int>> polygons;
|
||||
polygon_stream<int> faces(polygons);
|
||||
vertex_stream<real_type, 3> vertices(mesh.vertices);
|
||||
implicit.extractLevelSet(vertices, faces, parameters);
|
||||
|
||||
if (mesh.vertices.empty() || mesh.vertices.size() % 3 ||
|
||||
mesh.vertices.size() / 3 >
|
||||
static_cast<size_t>(std::numeric_limits<int>::max()) ||
|
||||
polygons.empty() ||
|
||||
polygons.size() > static_cast<size_t>(std::numeric_limits<int>::max()))
|
||||
return false;
|
||||
|
||||
mesh.faces.reserve(polygons.size() * 3);
|
||||
for (const auto &polygon : polygons) {
|
||||
if (polygon.size() != 3)
|
||||
return false;
|
||||
for (int vertex : polygon) {
|
||||
if (vertex < 0 ||
|
||||
static_cast<size_t>(vertex) >= mesh.vertices.size() / 3)
|
||||
return false;
|
||||
mesh.faces.push_back(vertex);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
solve_once(spsr_mesh &mesh,
|
||||
std::vector<struct bg_3d_spsr_refinement_hint> &hints,
|
||||
const std::vector<struct bg_3d_spsr_sample> &samples,
|
||||
const struct bg_3d_spsr_adaptive_opts &options)
|
||||
{
|
||||
static std::mutex solver_mutex;
|
||||
std::lock_guard<std::mutex> guard(solver_mutex);
|
||||
|
||||
static const unsigned int fem_signature =
|
||||
FEMDegreeAndBType<Reconstructor::Poisson::DefaultFEMDegree,
|
||||
Reconstructor::Poisson::DefaultFEMBoundary>::Signature;
|
||||
using fem_signatures = IsotropicUIntPack<3, fem_signature>;
|
||||
using implicit_type =
|
||||
Reconstructor::Implicit<real_type, 3, fem_signatures>;
|
||||
using solver_type =
|
||||
Reconstructor::Poisson::Solver<real_type, 3, fem_signatures>;
|
||||
|
||||
Reconstructor::Poisson::SolutionParameters<real_type> solver_parameters;
|
||||
Reconstructor::LevelSetExtractionParameters extraction_parameters;
|
||||
set_solver_options(solver_parameters, extraction_parameters,
|
||||
options.solver);
|
||||
|
||||
ThreadPool::ParallelizationType = ThreadPool::ASYNC;
|
||||
point_stream<real_type, 3> points(samples);
|
||||
std::unique_ptr<implicit_type> implicit(
|
||||
solver_type::Solve(points, solver_parameters));
|
||||
if (!implicit || !extract_mesh(mesh, *implicit, extraction_parameters))
|
||||
return false;
|
||||
|
||||
if (options.target_feature_size > 0.0) {
|
||||
sample_hints(hints, samples, *implicit,
|
||||
options.target_feature_size);
|
||||
density_hints(hints, samples, options.target_feature_size);
|
||||
surface_variation_hints(hints, mesh,
|
||||
options.target_feature_size);
|
||||
limit_hints(hints, options.target_feature_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
copy_mesh(int **faces, int *face_count, point_t **vertices,
|
||||
int *vertex_count, const spsr_mesh &mesh)
|
||||
{
|
||||
size_t faces_size = mesh.faces.size() * sizeof(int);
|
||||
size_t vertices_size = mesh.vertices.size() * sizeof(real_type);
|
||||
int *new_faces = static_cast<int *>(bu_malloc(faces_size,
|
||||
"SPSR face array"));
|
||||
point_t *new_vertices = static_cast<point_t *>(bu_malloc(vertices_size,
|
||||
"SPSR vertex array"));
|
||||
if (!new_faces || !new_vertices) {
|
||||
if (new_faces)
|
||||
bu_free(new_faces, "SPSR face array");
|
||||
if (new_vertices)
|
||||
bu_free(new_vertices, "SPSR vertex array");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(new_faces, mesh.faces.data(), faces_size);
|
||||
std::memcpy(new_vertices, mesh.vertices.data(), vertices_size);
|
||||
*faces = new_faces;
|
||||
*face_count = static_cast<int>(mesh.faces.size() / 3);
|
||||
*vertices = new_vertices;
|
||||
*vertex_count = static_cast<int>(mesh.vertices.size() / 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
initialize_outputs(int **faces, int *face_count, point_t **vertices,
|
||||
int *vertex_count, struct bg_3d_spsr_report *report)
|
||||
{
|
||||
if (faces)
|
||||
*faces = NULL;
|
||||
if (face_count)
|
||||
*face_count = 0;
|
||||
if (vertices)
|
||||
*vertices = NULL;
|
||||
if (vertex_count)
|
||||
*vertex_count = 0;
|
||||
if (report)
|
||||
std::memset(report, 0, sizeof(*report));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" 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 *input_options,
|
||||
bg_3d_spsr_refinement_func_t refine, void *client_data,
|
||||
struct bg_3d_spsr_report *report)
|
||||
{
|
||||
initialize_outputs(faces, num_faces, vertices, num_vertices, report);
|
||||
if (!faces || !num_faces || !vertices || !num_vertices ||
|
||||
!input_samples || !sample_count)
|
||||
return BRLCAD_ERROR;
|
||||
|
||||
struct bg_3d_spsr_adaptive_opts default_options =
|
||||
BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT;
|
||||
const struct bg_3d_spsr_adaptive_opts &options =
|
||||
input_options ? *input_options : default_options;
|
||||
if (!valid_options(options.solver) ||
|
||||
options.target_feature_size < 0.0 || options.max_time < 0.0 ||
|
||||
(options.max_refinement_passes &&
|
||||
(!refine || options.target_feature_size <= 0.0)) ||
|
||||
(options.max_points && options.max_points < sample_count))
|
||||
return BRLCAD_ERROR;
|
||||
|
||||
fastf_t extent = 0.0;
|
||||
{
|
||||
std::vector<struct bg_3d_spsr_sample> initial(
|
||||
input_samples, input_samples + sample_count);
|
||||
extent = sample_extent(initial);
|
||||
}
|
||||
if (!std::isfinite(extent) || extent <= SMALL_FASTF)
|
||||
return BRLCAD_ERROR;
|
||||
|
||||
fastf_t deduplication_distance =
|
||||
options.target_feature_size > 0.0 ?
|
||||
options.target_feature_size * SPSR_DEDUP_FRACTION :
|
||||
std::max(extent * 1.0e-12, static_cast<fastf_t>(SMALL_FASTF));
|
||||
sample_set accumulated(deduplication_distance, options.max_points);
|
||||
for (size_t i = 0; i < sample_count; i++)
|
||||
accumulated.append(input_samples[i]);
|
||||
if (accumulated.samples().empty())
|
||||
return BRLCAD_ERROR;
|
||||
|
||||
const clock_type::time_point start = clock_type::now();
|
||||
if (report)
|
||||
report->initial_sample_count = accumulated.samples().size();
|
||||
|
||||
bool point_limit_reached = accumulated.full();
|
||||
for (size_t pass = 0; ; pass++) {
|
||||
if (options.max_time > 0.0 &&
|
||||
elapsed_seconds(start) >= options.max_time) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_TIME_LIMIT;
|
||||
break;
|
||||
}
|
||||
|
||||
spsr_mesh mesh;
|
||||
std::vector<struct bg_3d_spsr_refinement_hint> hints;
|
||||
try {
|
||||
if (!solve_once(mesh, hints, accumulated.samples(), options)) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_SOLVER_ERROR;
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_SOLVER_ERROR;
|
||||
break;
|
||||
} catch (...) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_SOLVER_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
if (report)
|
||||
report->solve_count++;
|
||||
|
||||
if (!refine) {
|
||||
if (copy_mesh(faces, num_faces, vertices, num_vertices, mesh)) {
|
||||
if (report) {
|
||||
report->termination = BG_3D_SPSR_COMPLETE;
|
||||
report->validation.passed = 1;
|
||||
report->final_sample_count = accumulated.samples().size();
|
||||
report->elapsed_time = elapsed_seconds(start);
|
||||
}
|
||||
return BRLCAD_OK;
|
||||
}
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_SOLVER_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
double remaining_time = options.max_time > 0.0 ?
|
||||
std::max(0.0, options.max_time - elapsed_seconds(start)) : 0.0;
|
||||
if (options.max_time > 0.0 && remaining_time <= 0.0) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_TIME_LIMIT;
|
||||
break;
|
||||
}
|
||||
|
||||
struct bg_3d_spsr_refinement_request request = {};
|
||||
request.pass = pass;
|
||||
request.target_feature_size = options.target_feature_size;
|
||||
request.remaining_time = remaining_time;
|
||||
request.vertices =
|
||||
reinterpret_cast<const point_t *>(mesh.vertices.data());
|
||||
request.vertex_count = mesh.vertices.size() / 3;
|
||||
request.faces = mesh.faces.data();
|
||||
request.face_count = mesh.faces.size() / 3;
|
||||
request.hints = hints.data();
|
||||
request.hint_count = hints.size();
|
||||
|
||||
struct bg_3d_spsr_refinement_response response = {};
|
||||
if (refine(&response, &request, client_data) != BRLCAD_OK) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_CALLBACK_STOP;
|
||||
break;
|
||||
}
|
||||
if (report)
|
||||
report->validation = response.validation;
|
||||
|
||||
if (response.validation.passed) {
|
||||
if (copy_mesh(faces, num_faces, vertices, num_vertices, mesh)) {
|
||||
if (report) {
|
||||
report->termination = BG_3D_SPSR_COMPLETE;
|
||||
report->final_sample_count = accumulated.samples().size();
|
||||
report->elapsed_time = elapsed_seconds(start);
|
||||
}
|
||||
return BRLCAD_OK;
|
||||
}
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_SOLVER_ERROR;
|
||||
break;
|
||||
}
|
||||
if (response.stop_refinement) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_CALLBACK_STOP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pass == options.max_refinement_passes) {
|
||||
if (report)
|
||||
report->termination = point_limit_reached ?
|
||||
BG_3D_SPSR_POINT_LIMIT : BG_3D_SPSR_CALLBACK_STOP;
|
||||
break;
|
||||
}
|
||||
if (response.sample_count && !response.samples) {
|
||||
if (report)
|
||||
report->termination = BG_3D_SPSR_CALLBACK_STOP;
|
||||
break;
|
||||
}
|
||||
|
||||
if (report)
|
||||
report->requested_sample_count += response.sample_count;
|
||||
size_t accepted = 0;
|
||||
for (size_t i = 0; i < response.sample_count; i++) {
|
||||
if (accumulated.append(response.samples[i]))
|
||||
accepted++;
|
||||
}
|
||||
if (report)
|
||||
report->accepted_sample_count += accepted;
|
||||
point_limit_reached = accumulated.full();
|
||||
|
||||
if (!accepted) {
|
||||
if (report)
|
||||
report->termination = point_limit_reached ?
|
||||
BG_3D_SPSR_POINT_LIMIT : BG_3D_SPSR_NO_NEW_SAMPLES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (report) {
|
||||
report->final_sample_count = accumulated.samples().size();
|
||||
report->elapsed_time = elapsed_seconds(start);
|
||||
}
|
||||
return BRLCAD_ERROR;
|
||||
}
|
||||
|
||||
extern "C" 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 *spsr_opts)
|
||||
{
|
||||
if (!input_points_3d || !input_normals_3d || num_input_pnts <= 0)
|
||||
return BRLCAD_ERROR;
|
||||
|
||||
std::vector<struct bg_3d_spsr_sample> samples(
|
||||
static_cast<size_t>(num_input_pnts));
|
||||
for (size_t i = 0; i < samples.size(); i++) {
|
||||
VMOVE(samples[i].point, input_points_3d[i]);
|
||||
VMOVE(samples[i].normal, input_normals_3d[i]);
|
||||
}
|
||||
|
||||
struct bg_3d_spsr_adaptive_opts options =
|
||||
BG_3D_SPSR_ADAPTIVE_OPTS_DEFAULT;
|
||||
options.max_refinement_passes = 0;
|
||||
if (spsr_opts)
|
||||
options.solver = *spsr_opts;
|
||||
|
||||
return bg_3d_spsr_adaptive(faces, num_faces, vertices, num_vertices,
|
||||
samples.data(), samples.size(), &options, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -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,7 +310,24 @@ 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_add_test(NAME bg_polygon_triangulate COMMAND bg_polygon_triangulate)
|
||||
@@ -448,6 +469,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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,200 @@
|
||||
/* 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"
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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]);
|
||||
|
||||
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;
|
||||
ret = bg_3d_spsr_adaptive(&faces, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
accept_candidate, &state, NULL);
|
||||
expect(ret == BRLCAD_ERROR,
|
||||
"unsupported solver settings are rejected");
|
||||
|
||||
expect(bg_3d_spsr_adaptive(NULL, &face_count, &vertices,
|
||||
&vertex_count, samples.data(), samples.size(), &options,
|
||||
accept_candidate, &state, 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
|
||||
*/
|
||||
@@ -0,0 +1,106 @@
|
||||
/* 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.h"
|
||||
#include "bg.h"
|
||||
|
||||
|
||||
static int
|
||||
test_solid_diagnostics(void)
|
||||
{
|
||||
fastf_t vertices[][3] = {
|
||||
{0.0, 0.0, 0.0},
|
||||
{1.0, 0.0, 0.0},
|
||||
{0.0, 1.0, 0.0},
|
||||
{0.0, 0.0, 1.0},
|
||||
{1.0, 1.0, 1.0}
|
||||
};
|
||||
int solid_faces[] = {
|
||||
0, 2, 1,
|
||||
0, 1, 3,
|
||||
0, 3, 2,
|
||||
1, 2, 3
|
||||
};
|
||||
int reversed_faces[] = {
|
||||
0, 1, 2,
|
||||
0, 1, 3,
|
||||
0, 3, 2,
|
||||
1, 2, 3
|
||||
};
|
||||
int mixed_error_faces[] = {
|
||||
0, 1, 2,
|
||||
0, 1, 3,
|
||||
0, 3, 4,
|
||||
1, 2, 3
|
||||
};
|
||||
int *bad_edges = NULL;
|
||||
struct bg_trimesh_solid_errors errors = BG_TRIMESH_SOLID_ERRORS_INIT_NULL;
|
||||
int failed = 0;
|
||||
|
||||
if (bg_trimesh_solid(4, 4, &vertices[0][0], solid_faces, &bad_edges) || bad_edges)
|
||||
failed = 1;
|
||||
|
||||
if (bad_edges) {
|
||||
bu_free(bad_edges, "bad edges");
|
||||
bad_edges = NULL;
|
||||
}
|
||||
|
||||
if (bg_trimesh_solid(4, 4, &vertices[0][0], reversed_faces, &bad_edges) != 1
|
||||
|| !bad_edges) {
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
if (bad_edges)
|
||||
bu_free(bad_edges, "bad edges");
|
||||
|
||||
if (!bg_trimesh_solid2(5, 4, &vertices[0][0], mixed_error_faces, &errors)
|
||||
|| !errors.unmatched.count || !errors.misoriented.count) {
|
||||
failed = 1;
|
||||
}
|
||||
bg_free_trimesh_solid_errors(&errors);
|
||||
|
||||
return failed;
|
||||
}
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -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
-21
@@ -285,10 +285,20 @@ bg_trimesh_solid2(int vcnt, int fcnt, fastf_t *v, int *f, struct bg_trimesh_soli
|
||||
|
||||
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)))
|
||||
{
|
||||
unmatched_edges = bg_trimesh_unmatched_edges(num_edges, edge_list, bad_edge_func, NULL);
|
||||
if (!errors && unmatched_edges) {
|
||||
bu_free(edge_list, "edge_list");
|
||||
return 1;
|
||||
}
|
||||
|
||||
misoriented_edges = bg_trimesh_misoriented_edges(num_edges, edge_list, bad_edge_func, NULL);
|
||||
if (!errors && misoriented_edges) {
|
||||
bu_free(edge_list, "edge_list");
|
||||
return 1;
|
||||
}
|
||||
|
||||
excess_edges = bg_trimesh_excess_edges(num_edges, edge_list, bad_edge_func, NULL);
|
||||
if (unmatched_edges || misoriented_edges || excess_edges) {
|
||||
if (!errors) {
|
||||
bu_free(edge_list, "edge_list");
|
||||
return 1;
|
||||
@@ -329,6 +339,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 +349,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 +367,42 @@ 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;
|
||||
int not_solid;
|
||||
|
||||
if (bedges) {
|
||||
int copy_cnt = 0;
|
||||
int copy_count = 0;
|
||||
int edge_count;
|
||||
struct bg_trimesh_solid_errors errors = BG_TRIMESH_SOLID_ERRORS_INIT_NULL;
|
||||
|
||||
bedge_cnt = bg_trimesh_solid2(vcnt, fcnt, v, f, &errors);
|
||||
*bedges = NULL;
|
||||
not_solid = bg_trimesh_solid2(vcnt, fcnt, v, f, &errors);
|
||||
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");
|
||||
|
||||
*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));
|
||||
if (errors.unmatched.count) {
|
||||
memcpy(*bedges + copy_count, errors.unmatched.edges,
|
||||
errors.unmatched.count * 2 * sizeof(int));
|
||||
copy_count += errors.unmatched.count * 2;
|
||||
}
|
||||
if (errors.misoriented.count) {
|
||||
memcpy(*bedges + copy_count, errors.misoriented.edges,
|
||||
errors.misoriented.count * 2 * sizeof(int));
|
||||
copy_count += errors.misoriented.count * 2;
|
||||
}
|
||||
if (errors.excess.count) {
|
||||
memcpy(*bedges + copy_count, 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);
|
||||
not_solid = bg_trimesh_solid2(vcnt, fcnt, v, f, NULL);
|
||||
}
|
||||
|
||||
/* returns true when not solid */
|
||||
return (bedge_cnt > 0) ? 1 : 0;
|
||||
return not_solid ? 1 : 0;
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
+38
-98
@@ -662,87 +662,6 @@ bn_vec_perp(vect_t new_vec, const vect_t old_vec)
|
||||
VCROSS(new_vec, another_vec, old_vec);
|
||||
}
|
||||
|
||||
static int
|
||||
bn_lseg3_lseg3_parallel(const point_t sg1pt1, const point_t sg1pt2,
|
||||
const point_t sg2pt1, const point_t sg2pt2,
|
||||
const struct bn_tol *tol)
|
||||
{
|
||||
vect_t e_dif[2] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
|
||||
vect_t e_dif_a[2] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
|
||||
fastf_t e_rr[2][3] = {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}};
|
||||
char e_sc[2][3] = {{0, 0, 0}, {0, 0, 0}};
|
||||
fastf_t dist = tol->dist;
|
||||
fastf_t tmp;
|
||||
int i, j;
|
||||
|
||||
VSUB2(e_dif[0], sg1pt2, sg1pt1);
|
||||
VSUB2(e_dif[1], sg2pt2, sg2pt1);
|
||||
|
||||
for ( i = 0 ; i < 2 ; i++ ) {
|
||||
for ( j = 0 ; j < 3 ; j++ ) {
|
||||
e_dif_a[i][j] = fabs(e_dif[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
for ( i = 0 ; i < 2 ; i++ ) {
|
||||
if ((e_dif_a[i][X] < dist) && (e_dif_a[i][Y] > dist)) {
|
||||
e_sc[i][0] = 1;
|
||||
} else if ((e_dif_a[i][X] > dist) && (e_dif_a[i][Y] < dist)) {
|
||||
e_sc[i][0] = 2;
|
||||
} else if ((e_dif_a[i][X] < dist) && (e_dif_a[i][Y] < dist)) {
|
||||
e_sc[i][0] = 3;
|
||||
} else {
|
||||
e_rr[i][0] = e_dif[i][Y] / e_dif[i][X];
|
||||
e_sc[i][0] = 0;
|
||||
}
|
||||
if ((e_dif_a[i][X] < dist) && (e_dif_a[i][Z] > dist)) {
|
||||
e_sc[i][1] = 1;
|
||||
} else if ((e_dif_a[i][X] > dist) && (e_dif_a[i][Z] < dist)) {
|
||||
e_sc[i][1] = 2;
|
||||
} else if ((e_dif_a[i][X] < dist) && (e_dif_a[i][Z] < dist)) {
|
||||
e_sc[i][1] = 3;
|
||||
} else {
|
||||
e_rr[i][1] = e_dif[i][Z] / e_dif[i][X];
|
||||
e_sc[i][1] = 0;
|
||||
}
|
||||
if ((e_dif_a[i][X] < dist) && (e_dif_a[i][Z] > dist)) {
|
||||
e_sc[i][1] = 1;
|
||||
} else if ((e_dif_a[i][X] > dist) && (e_dif_a[i][Z] < dist)) {
|
||||
e_sc[i][1] = 2;
|
||||
} else if ((e_dif_a[i][X] < dist) && (e_dif_a[i][Z] < dist)) {
|
||||
e_sc[i][1] = 3;
|
||||
} else {
|
||||
e_rr[i][1] = e_dif[i][Z] / e_dif[i][X];
|
||||
e_sc[i][1] = 0;
|
||||
}
|
||||
if ((e_dif_a[i][Y] < dist) && (e_dif_a[i][Z] > dist)) {
|
||||
e_sc[i][2] = 1;
|
||||
} else if ((e_dif_a[i][Y] > dist) && (e_dif_a[i][Z] < dist)) {
|
||||
e_sc[i][2] = 2;
|
||||
} else if ((e_dif_a[i][Y] < dist) && (e_dif_a[i][Z] < dist)) {
|
||||
e_sc[i][2] = 3;
|
||||
} else {
|
||||
e_rr[i][2] = e_dif[i][Z] / e_dif[i][Y];
|
||||
e_sc[i][2] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* loop thru (rise/run) ratios from xy, xz and yz planes */
|
||||
for ( i = 0 ; i < 3 ; i++ ) {
|
||||
if (e_sc[0][i] != e_sc[1][i]) {
|
||||
return 0;
|
||||
}
|
||||
if (e_sc[0][i] == 0) {
|
||||
tmp = e_rr[0][i] - e_rr[1][i];
|
||||
if (fabs(tmp) > dist) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
bn_mat_fromto(
|
||||
mat_t m,
|
||||
@@ -753,7 +672,6 @@ bn_mat_fromto(
|
||||
vect_t test_to;
|
||||
vect_t unit_from, unit_to;
|
||||
fastf_t dot;
|
||||
point_t origin = VINIT_ZERO;
|
||||
|
||||
/**
|
||||
* The method used here is from Graphics Gems, A. Glassner, ed.
|
||||
@@ -767,18 +685,24 @@ bn_mat_fromto(
|
||||
vect_t N, M;
|
||||
vect_t w_prime; /* image of "to" ("w") in Qt */
|
||||
|
||||
BN_CK_TOL(tol);
|
||||
|
||||
VMOVE(unit_from, from);
|
||||
VUNITIZE(unit_from); /* aka "v" */
|
||||
VMOVE(unit_to, to);
|
||||
VUNITIZE(unit_to); /* aka "w" */
|
||||
|
||||
/* If from and to are the same or opposite, special handling is
|
||||
* needed, because the cross product isn't defined. asin(0.00001)
|
||||
* = 0.0005729 degrees (1/2000 degree)
|
||||
if (VZERO(unit_from) || VZERO(unit_to)) {
|
||||
MAT_IDN(m);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Same and opposite directions need special handling because their
|
||||
* cross product does not define a rotation axis.
|
||||
*/
|
||||
if (bn_lseg3_lseg3_parallel(origin, from, origin, to, tol)) {
|
||||
dot = VDOT(from, to);
|
||||
if (dot > SMALL_FASTF) {
|
||||
dot = VDOT(unit_from, unit_to);
|
||||
if (BN_VECT_ARE_PARALLEL(dot, tol)) {
|
||||
if (dot > 0.0) {
|
||||
MAT_IDN(m);
|
||||
return;
|
||||
} else {
|
||||
@@ -812,7 +736,7 @@ bn_mat_fromto(
|
||||
|
||||
/* Verify that it worked */
|
||||
MAT4X3VEC(test_to, m, unit_from);
|
||||
if (UNLIKELY(!bn_lseg3_lseg3_parallel(origin, unit_to, origin, test_to, tol))) {
|
||||
if (UNLIKELY(!BN_VECT_ARE_PARALLEL(VDOT(unit_to, test_to), tol))) {
|
||||
dot = VDOT(unit_to, test_to);
|
||||
bu_log("bn_mat_fromto() ERROR! from (%g, %g, %g) to (%g, %g, %g) went to (%g, %g, %g), dot=%g?\n",
|
||||
V3ARGS(from), V3ARGS(to), V3ARGS(test_to), dot);
|
||||
@@ -899,16 +823,32 @@ bn_mat_lookat(mat_t rot, const vect_t dir, int yflip)
|
||||
vect_t x;
|
||||
vect_t z;
|
||||
vect_t t1;
|
||||
vect_t unit_dir;
|
||||
fastf_t hypot_xy;
|
||||
vect_t xproj;
|
||||
vect_t zproj;
|
||||
|
||||
VMOVE(unit_dir, dir);
|
||||
VUNITIZE(unit_dir);
|
||||
if (VZERO(unit_dir)) {
|
||||
MAT_IDN(rot);
|
||||
return;
|
||||
}
|
||||
|
||||
/* First, rotate D around Z axis to match +X axis (azimuth) */
|
||||
hypot_xy = hypot(dir[X], dir[Y]);
|
||||
bn_mat_zrot(first, -dir[Y] / hypot_xy, dir[X] / hypot_xy);
|
||||
hypot_xy = hypot(unit_dir[X], unit_dir[Y]);
|
||||
if (hypot_xy < VDIVIDE_TOL) {
|
||||
if (unit_dir[Z] < 0.0) {
|
||||
MAT_IDN(rot);
|
||||
} else {
|
||||
bn_mat_yrot(rot, 0.0, -1.0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
bn_mat_zrot(first, -unit_dir[Y] / hypot_xy, unit_dir[X] / hypot_xy);
|
||||
|
||||
/* Next, rotate D around Y axis to match -Z axis (elevation) */
|
||||
bn_mat_yrot(second, -hypot_xy, -dir[Z]);
|
||||
bn_mat_yrot(second, -hypot_xy, -unit_dir[Z]);
|
||||
bn_mat_mul(prod12, second, first);
|
||||
|
||||
/* Produce twist correction, by re-orienting projection of X axis */
|
||||
@@ -937,7 +877,7 @@ bn_mat_lookat(mat_t rot, const vect_t dir, int yflip)
|
||||
}
|
||||
|
||||
/* Check the final results */
|
||||
MAT4X3VEC(t1, rot, dir);
|
||||
MAT4X3VEC(t1, rot, unit_dir);
|
||||
if (t1[Z] > -0.98) {
|
||||
bu_log("Error: bn_mat_lookat final= (%g, %g, %g)\n", V3ARGS(t1));
|
||||
}
|
||||
@@ -1277,10 +1217,10 @@ bn_mat_is_non_unif(const mat_t m)
|
||||
void
|
||||
bn_wrt_point_direc(mat_t out, const mat_t change, const mat_t in, const point_t point, const vect_t direc, const struct bn_tol *tol)
|
||||
{
|
||||
static mat_t t1;
|
||||
static mat_t pt_to_origin, origin_to_pt;
|
||||
static mat_t d_to_zaxis, zaxis_to_d;
|
||||
static vect_t zaxis;
|
||||
mat_t t1;
|
||||
mat_t pt_to_origin, origin_to_pt;
|
||||
mat_t d_to_zaxis, zaxis_to_d;
|
||||
vect_t zaxis;
|
||||
|
||||
/* build "point to origin" matrix */
|
||||
MAT_IDN(pt_to_origin);
|
||||
@@ -1288,7 +1228,7 @@ bn_wrt_point_direc(mat_t out, const mat_t change, const mat_t in, const point_t
|
||||
|
||||
/* build "origin to point" matrix */
|
||||
MAT_IDN(origin_to_pt);
|
||||
MAT_DELTAS_VEC_NEG(origin_to_pt, point);
|
||||
MAT_DELTAS_VEC(origin_to_pt, point);
|
||||
|
||||
/* build "direc to zaxis" matrix */
|
||||
VSET(zaxis, 0.0, 0.0, 1.0);
|
||||
|
||||
+86
-43
@@ -28,21 +28,11 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "bu/interrupt.h"
|
||||
#include "bu/malloc.h"
|
||||
#include "vmath.h"
|
||||
#include "bn/multipoly.h"
|
||||
|
||||
|
||||
#define FAILSTR "failure in multipoly.c"
|
||||
/* the Max and Min macros in vmath.h are destructive and not suited for here, should this be
|
||||
* added somewhere else? */
|
||||
#define Max(a, b) (((a) > (b)) ? (a) : (b))
|
||||
#define Min(a, b) (((a) > (b)) ? (a) : (b))
|
||||
|
||||
/**
|
||||
* bn_multipoly_new
|
||||
*
|
||||
@@ -52,25 +42,41 @@ struct bn_multipoly *
|
||||
bn_multipoly_new(int dgrs, int dgrt)
|
||||
{
|
||||
struct bn_multipoly *newmp;
|
||||
int i, s, t;
|
||||
int i;
|
||||
|
||||
if (dgrs <= 0 || dgrt <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BU_ALLOC(newmp, struct bn_multipoly);
|
||||
newmp->cf = (double **)bu_malloc(dgrs * sizeof(double *), FAILSTR);
|
||||
newmp->cf = (double **)bu_calloc(dgrs, sizeof(double *), FAILSTR);
|
||||
|
||||
for (i = 0; i < dgrs; i++) {
|
||||
newmp->cf[i] = (double *)bu_malloc(dgrt * sizeof(double), FAILSTR);
|
||||
newmp->cf[i] = (double *)bu_calloc(dgrt, sizeof(double), FAILSTR);
|
||||
}
|
||||
|
||||
newmp->dgrs = dgrs;
|
||||
newmp->dgrt = dgrt;
|
||||
for (s = 0; s < dgrs; s++) {
|
||||
for (t = 0; t < dgrt; t++) {
|
||||
newmp->cf[s][t] = 0;
|
||||
}
|
||||
}
|
||||
return newmp;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
bn_multipoly_free(struct bn_multipoly *poly)
|
||||
{
|
||||
int s;
|
||||
|
||||
if (!poly) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (s = 0; s < poly->dgrs; s++) {
|
||||
bu_free(poly->cf[s], "bn_multipoly coefficients");
|
||||
}
|
||||
bu_free(poly->cf, "bn_multipoly coefficient rows");
|
||||
bu_free(poly, "bn_multipoly");
|
||||
}
|
||||
|
||||
/**
|
||||
* bn_multipoly_grow
|
||||
*
|
||||
@@ -79,24 +85,35 @@ bn_multipoly_new(int dgrs, int dgrt)
|
||||
struct bn_multipoly *
|
||||
bn_multipoly_grow(register struct bn_multipoly *P, int dgrs, int dgrt)
|
||||
{
|
||||
int i, j;
|
||||
if (dgrs > P->dgrs) {
|
||||
P->cf = (double **)bu_realloc(P->cf, dgrs * sizeof(double *), FAILSTR);
|
||||
for (i = P->dgrs; i < dgrs; i++) {
|
||||
P->cf[i] = (double *)bu_malloc(Max(P->dgrt, dgrt) * sizeof(double), FAILSTR);
|
||||
for (j = 0; j < Max(P->dgrt, dgrt); j++) {
|
||||
P->cf[i][j] = 0;
|
||||
}
|
||||
}
|
||||
double **coefficients;
|
||||
int new_dgrs, new_dgrt;
|
||||
int s, t;
|
||||
|
||||
if (!P) {
|
||||
return NULL;
|
||||
}
|
||||
if (dgrt > P->dgrt) {
|
||||
for (i = 0; i < P->dgrt; i++) {
|
||||
P->cf[i] = (double *)bu_realloc(P->cf, dgrt * sizeof(double *), FAILSTR);
|
||||
for (j = P->dgrt; j < dgrt; j++) {
|
||||
P->cf[i][j] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
new_dgrs = (dgrs > P->dgrs) ? dgrs : P->dgrs;
|
||||
new_dgrt = (dgrt > P->dgrt) ? dgrt : P->dgrt;
|
||||
if (new_dgrs == P->dgrs && new_dgrt == P->dgrt) {
|
||||
return P;
|
||||
}
|
||||
|
||||
coefficients = (double **)bu_calloc(new_dgrs, sizeof(double *), FAILSTR);
|
||||
for (s = 0; s < new_dgrs; s++) {
|
||||
coefficients[s] = (double *)bu_calloc(new_dgrt, sizeof(double), FAILSTR);
|
||||
}
|
||||
for (s = 0; s < P->dgrs; s++) {
|
||||
for (t = 0; t < P->dgrt; t++) {
|
||||
coefficients[s][t] = P->cf[s][t];
|
||||
}
|
||||
bu_free(P->cf[s], "bn_multipoly coefficients");
|
||||
}
|
||||
bu_free(P->cf, "bn_multipoly coefficient rows");
|
||||
|
||||
P->cf = coefficients;
|
||||
P->dgrs = new_dgrs;
|
||||
P->dgrt = new_dgrt;
|
||||
return P;
|
||||
}
|
||||
|
||||
@@ -108,7 +125,12 @@ bn_multipoly_grow(register struct bn_multipoly *P, int dgrs, int dgrt)
|
||||
struct bn_multipoly *
|
||||
bn_multipoly_set(register struct bn_multipoly *P, int s, int t, double val)
|
||||
{
|
||||
bn_multipoly_grow(P, s + 1, t + 1);
|
||||
if (!P || s < 0 || t < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (!bn_multipoly_grow(P, s + 1, t + 1)) {
|
||||
return NULL;
|
||||
}
|
||||
P->cf[s][t] = val;
|
||||
return P;
|
||||
}
|
||||
@@ -119,13 +141,26 @@ bn_multipoly_set(register struct bn_multipoly *P, int s, int t, double val)
|
||||
* @brief add two polynomials
|
||||
*/
|
||||
struct bn_multipoly *
|
||||
bn_multipoly_add(register struct bn_multipoly *p1, register struct bn_multipoly *p2)
|
||||
bn_multipoly_add(register const struct bn_multipoly *p1, register const struct bn_multipoly *p2)
|
||||
{
|
||||
struct bn_multipoly *sum = bn_multipoly_new(Max(p1->dgrs, p2->dgrs), Max(p1->dgrt, p2->dgrs));
|
||||
struct bn_multipoly *sum;
|
||||
int s, t;
|
||||
|
||||
if (!p1 || !p2 || p1->dgrs <= 0 || p1->dgrt <= 0 ||
|
||||
p2->dgrs <= 0 || p2->dgrt <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sum = bn_multipoly_new((p1->dgrs > p2->dgrs) ? p1->dgrs : p2->dgrs,
|
||||
(p1->dgrt > p2->dgrt) ? p1->dgrt : p2->dgrt);
|
||||
for (s = 0; s < sum->dgrs; s++) {
|
||||
for (t = 0; t < sum->dgrt; t++) {
|
||||
sum->cf[s][t] = p1->cf[s][t] + p2->cf[s][t];
|
||||
if (s < p1->dgrs && t < p1->dgrt) {
|
||||
sum->cf[s][t] += p1->cf[s][t];
|
||||
}
|
||||
if (s < p2->dgrs && t < p2->dgrt) {
|
||||
sum->cf[s][t] += p2->cf[s][t];
|
||||
}
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
@@ -138,15 +173,23 @@ bn_multipoly_add(register struct bn_multipoly *p1, register struct bn_multipoly
|
||||
*/
|
||||
|
||||
struct bn_multipoly *
|
||||
bn_multipoly_mul(register struct bn_multipoly *p1, register struct bn_multipoly *p2)
|
||||
bn_multipoly_mul(register const struct bn_multipoly *p1, register const struct bn_multipoly *p2)
|
||||
{
|
||||
struct bn_multipoly *product = bn_multipoly_new(p1->dgrs + p2->dgrs, p1->dgrt + p2->dgrt);
|
||||
int s1,s2,t1,t2;
|
||||
struct bn_multipoly *product;
|
||||
int s1, s2, t1, t2;
|
||||
|
||||
if (!p1 || !p2 || p1->dgrs <= 0 || p1->dgrt <= 0 ||
|
||||
p2->dgrs <= 0 || p2->dgrt <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
product = bn_multipoly_new(p1->dgrs + p2->dgrs - 1,
|
||||
p1->dgrt + p2->dgrt - 1);
|
||||
for (s1 = 0; s1 < p1->dgrs; s1++) {
|
||||
for (t1 = 0; t1 < p1->dgrt; t1++) {
|
||||
for (s2 = 0; s2 < p2->dgrs; s2++) {
|
||||
for (t2 = 0; t2 < p2->dgrt; p2++) {
|
||||
product->cf[s1 + s2][t1 + t2] = p1->cf[s1][t1] * p2->cf[s2][t2];
|
||||
for (t2 = 0; t2 < p2->dgrt; t2++) {
|
||||
product->cf[s1 + s2][t1 + t2] += p1->cf[s1][t1] * p2->cf[s2][t2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+64
-39
@@ -28,7 +28,6 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdlib.h> /* for abs */
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
@@ -49,7 +48,24 @@ static const struct bn_poly bn_Zero_poly = { BN_POLY_MAGIC, 0, {0.0} };
|
||||
struct bn_poly *
|
||||
bn_poly_mul(register struct bn_poly *product, register const struct bn_poly *m1, register const struct bn_poly *m2)
|
||||
{
|
||||
if (m1->dgr > BN_MAX_POLY_DEGREE ||
|
||||
m2->dgr > BN_MAX_POLY_DEGREE ||
|
||||
m1->dgr > BN_MAX_POLY_DEGREE - m2->dgr) {
|
||||
return BN_POLY_NULL;
|
||||
}
|
||||
|
||||
if (product == m1 || product == m2) {
|
||||
struct bn_poly result;
|
||||
|
||||
if (!bn_poly_mul(&result, m1, m2)) {
|
||||
return BN_POLY_NULL;
|
||||
}
|
||||
*product = result;
|
||||
return product;
|
||||
}
|
||||
|
||||
if (m1->dgr == 1 && m2->dgr == 1) {
|
||||
product->magic = BN_POLY_MAGIC;
|
||||
product->dgr = 2;
|
||||
product->cf[0] = m1->cf[0] * m2->cf[0];
|
||||
product->cf[1] = m1->cf[0] * m2->cf[1] +
|
||||
@@ -58,6 +74,7 @@ bn_poly_mul(register struct bn_poly *product, register const struct bn_poly *m1,
|
||||
return product;
|
||||
}
|
||||
if (m1->dgr == 2 && m2->dgr == 2) {
|
||||
product->magic = BN_POLY_MAGIC;
|
||||
product->dgr = 4;
|
||||
product->cf[0] = m1->cf[0] * m2->cf[0];
|
||||
product->cf[1] = m1->cf[0] * m2->cf[1] +
|
||||
@@ -77,12 +94,7 @@ bn_poly_mul(register struct bn_poly *product, register const struct bn_poly *m1,
|
||||
|
||||
*product = bn_Zero_poly;
|
||||
|
||||
/* If the degree of the product will be larger than the
|
||||
* maximum size allowed in "polyno.h", then return a null
|
||||
* pointer to indicate failure.
|
||||
*/
|
||||
if ((product->dgr = m1->dgr + m2->dgr) > BN_MAX_POLY_DEGREE)
|
||||
return BN_POLY_NULL;
|
||||
product->dgr = m1->dgr + m2->dgr;
|
||||
|
||||
for (ct1=0; ct1 <= m1->dgr; ++ct1) {
|
||||
for (ct2=0; ct2 <= m2->dgr; ++ct2) {
|
||||
@@ -110,28 +122,26 @@ bn_poly_scale(register struct bn_poly *eqn, double factor)
|
||||
struct bn_poly *
|
||||
bn_poly_add(register struct bn_poly *sum, register const struct bn_poly *poly1, register const struct bn_poly *poly2)
|
||||
{
|
||||
struct bn_poly tmp;
|
||||
struct bn_poly result;
|
||||
register size_t i, offset;
|
||||
|
||||
offset = labs((long)poly1->dgr - (long)poly2->dgr);
|
||||
|
||||
tmp = bn_Zero_poly;
|
||||
offset = (poly1->dgr >= poly2->dgr) ?
|
||||
poly1->dgr - poly2->dgr : poly2->dgr - poly1->dgr;
|
||||
|
||||
if (poly1->dgr >= poly2->dgr) {
|
||||
*sum = *poly1;
|
||||
result = *poly1;
|
||||
for (i=0; i <= poly2->dgr; ++i) {
|
||||
tmp.cf[i+offset] = poly2->cf[i];
|
||||
result.cf[i+offset] += poly2->cf[i];
|
||||
}
|
||||
} else {
|
||||
*sum = *poly2;
|
||||
result = *poly2;
|
||||
for (i=0; i <= poly1->dgr; ++i) {
|
||||
tmp.cf[i+offset] = poly1->cf[i];
|
||||
result.cf[i+offset] += poly1->cf[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i <= sum->dgr; ++i) {
|
||||
sum->cf[i] += tmp.cf[i];
|
||||
}
|
||||
result.magic = BN_POLY_MAGIC;
|
||||
*sum = result;
|
||||
return sum;
|
||||
}
|
||||
|
||||
@@ -139,30 +149,30 @@ bn_poly_add(register struct bn_poly *sum, register const struct bn_poly *poly1,
|
||||
struct bn_poly *
|
||||
bn_poly_sub(register struct bn_poly *diff, register const struct bn_poly *poly1, register const struct bn_poly *poly2)
|
||||
{
|
||||
struct bn_poly tmp;
|
||||
struct bn_poly result;
|
||||
register size_t i, offset;
|
||||
|
||||
offset = labs((long)poly1->dgr - (long)poly2->dgr);
|
||||
|
||||
*diff = bn_Zero_poly;
|
||||
tmp = bn_Zero_poly;
|
||||
offset = (poly1->dgr >= poly2->dgr) ?
|
||||
poly1->dgr - poly2->dgr : poly2->dgr - poly1->dgr;
|
||||
|
||||
if (poly1->dgr >= poly2->dgr) {
|
||||
*diff = *poly1;
|
||||
result = *poly1;
|
||||
for (i=0; i <= poly2->dgr; ++i) {
|
||||
tmp.cf[i+offset] = poly2->cf[i];
|
||||
result.cf[i+offset] -= poly2->cf[i];
|
||||
}
|
||||
} else {
|
||||
diff->dgr = poly2->dgr;
|
||||
result = bn_Zero_poly;
|
||||
result.dgr = poly2->dgr;
|
||||
for (i=0; i <= poly1->dgr; ++i) {
|
||||
diff->cf[i+offset] = poly1->cf[i];
|
||||
result.cf[i+offset] = poly1->cf[i];
|
||||
}
|
||||
for (i=0; i <= poly2->dgr; ++i) {
|
||||
result.cf[i] -= poly2->cf[i];
|
||||
}
|
||||
tmp = *poly2;
|
||||
}
|
||||
|
||||
for (i=0; i <= diff->dgr; ++i) {
|
||||
diff->cf[i] -= tmp.cf[i];
|
||||
}
|
||||
result.magic = BN_POLY_MAGIC;
|
||||
*diff = result;
|
||||
return diff;
|
||||
}
|
||||
|
||||
@@ -177,12 +187,20 @@ bn_poly_synthetic_division(struct bn_poly *quo, struct bn_poly *rem, const struc
|
||||
*rem = bn_Zero_poly; /* struct copy */
|
||||
|
||||
if (dvsor->dgr > dvdend->dgr) {
|
||||
quo->dgr = -1;
|
||||
} else {
|
||||
quo->dgr = dvdend->dgr - dvsor->dgr;
|
||||
*rem = *dvdend;
|
||||
*quo = bn_Zero_poly;
|
||||
return;
|
||||
}
|
||||
if ((rem->dgr = dvsor->dgr - 1) > dvdend->dgr)
|
||||
rem->dgr = dvdend->dgr;
|
||||
|
||||
if (dvsor->dgr == 0) {
|
||||
for (n=0; n <= quo->dgr; ++n) {
|
||||
quo->cf[n] /= dvsor->cf[0];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
quo->dgr = dvdend->dgr - dvsor->dgr;
|
||||
rem->dgr = dvsor->dgr - 1;
|
||||
|
||||
for (n=0; n <= quo->dgr; ++n) {
|
||||
quo->cf[n] /= dvsor->cf[0];
|
||||
@@ -200,10 +218,11 @@ bn_poly_synthetic_division(struct bn_poly *quo, struct bn_poly *rem, const struc
|
||||
int
|
||||
bn_poly_quadratic_roots(register struct bn_complex *roots, register const struct bn_poly *quadrat)
|
||||
{
|
||||
struct bn_poly monic;
|
||||
fastf_t discrim, denom, rad;
|
||||
const fastf_t small = SMALL_FASTF;
|
||||
|
||||
if (NEAR_ZERO(quadrat->cf[0], small)) {
|
||||
if (ZERO(quadrat->cf[0]) && fpclassify(quadrat->cf[0]) == FP_ZERO) {
|
||||
/* root = -cf[2] / cf[1] */
|
||||
if (NEAR_ZERO(quadrat->cf[1], small)) {
|
||||
/* No solution. Now what? */
|
||||
@@ -216,6 +235,12 @@ bn_poly_quadratic_roots(register struct bn_complex *roots, register const struct
|
||||
return 1; /* OK - repeated root */
|
||||
}
|
||||
|
||||
if (!NEAR_EQUAL(quadrat->cf[0], 1.0, small)) {
|
||||
monic = *quadrat;
|
||||
bn_poly_scale(&monic, 1.0 / monic.cf[0]);
|
||||
quadrat = &monic;
|
||||
}
|
||||
|
||||
discrim = quadrat->cf[1]*quadrat->cf[1] - 4.0* quadrat->cf[0]*quadrat->cf[2];
|
||||
denom = 0.5 / quadrat->cf[0];
|
||||
|
||||
@@ -264,7 +289,7 @@ bn_poly_cubic_roots(register struct bn_complex *roots, register const struct bn_
|
||||
fastf_t a, b, c1, c1_3rd, delta;
|
||||
register int i;
|
||||
|
||||
if (ZERO(eqn->cf[0])) return 0;
|
||||
if (ZERO(eqn->cf[0]) && fpclassify(eqn->cf[0]) == FP_ZERO) return 0;
|
||||
|
||||
/* Cardano's formula below is expressed for a monic polynomial. */
|
||||
if (!NEAR_EQUAL(eqn->cf[0], 1.0, SMALL_FASTF)) {
|
||||
@@ -358,7 +383,7 @@ bn_poly_quartic_roots(register struct bn_complex *roots, register const struct b
|
||||
|
||||
#define Max3(a, b, c) ((c)>((a)>(b)?(a):(b)) ? (c) : ((a)>(b)?(a):(b)))
|
||||
|
||||
if (ZERO(eqn->cf[0])) return 0;
|
||||
if (ZERO(eqn->cf[0]) && fpclassify(eqn->cf[0]) == FP_ZERO) return 0;
|
||||
|
||||
/* Ferrari's formula below is expressed for a monic polynomial. */
|
||||
if (!NEAR_EQUAL(eqn->cf[0], 1.0, SMALL_FASTF)) {
|
||||
|
||||
+12
-2
@@ -174,7 +174,14 @@ quat_double(quat_t qout, const quat_t q1, const quat_t q2)
|
||||
void
|
||||
quat_bisect(quat_t qout, const quat_t q1, const quat_t q2)
|
||||
{
|
||||
QADD2( qout, q1, q2 );
|
||||
quat_t qsum;
|
||||
|
||||
QADD2(qsum, q1, q2);
|
||||
if (QMAGSQ(qsum) < VDIVIDE_TOL * VDIVIDE_TOL) {
|
||||
quat_slerp(qout, q1, q2, 0.5);
|
||||
return;
|
||||
}
|
||||
QMOVE(qout, qsum);
|
||||
QUNITIZE( qout );
|
||||
}
|
||||
|
||||
@@ -302,9 +309,12 @@ quat_log(quat_t out, const quat_t in)
|
||||
fastf_t theta;
|
||||
fastf_t scale;
|
||||
|
||||
if ( (scale = MAGNITUDE(in)) > VDIVIDE_TOL ) {
|
||||
scale = MAGNITUDE(in);
|
||||
if (scale > VDIVIDE_TOL) {
|
||||
theta = atan2( scale, in[W] );
|
||||
scale = theta/scale;
|
||||
} else {
|
||||
scale = 0.0;
|
||||
}
|
||||
|
||||
VSCALE( out, in, scale );
|
||||
|
||||
@@ -33,6 +33,22 @@ vect_close_or_neg(const vect_t a, const vect_t b, double tol)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
lookat_is_valid(const vect_t direction)
|
||||
{
|
||||
vect_t expected = {0.0, 0.0, -1.0};
|
||||
vect_t output = VINIT_ZERO;
|
||||
mat_t matrix = MAT_INIT_ZERO;
|
||||
|
||||
bn_mat_lookat(matrix, direction, 0);
|
||||
MAT4X3VEC(output, matrix, direction);
|
||||
VUNITIZE(output);
|
||||
|
||||
return finite_vec(output) && vect_close(output, expected, 1.0e-12)
|
||||
&& orthonormal_rotation(matrix, 1.0e-12);
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_mat_basic(void)
|
||||
{
|
||||
@@ -799,6 +815,13 @@ test_mat_orientation(void)
|
||||
int failures = 0;
|
||||
const char *test = "mat_orientation";
|
||||
struct bn_tol tol = BN_TOL_INIT_TOL;
|
||||
struct bn_tol angular_tol = BN_TOL_INIT_TOL;
|
||||
static const vect_t lookat_directions[] = {
|
||||
{0.0, 0.0, -1.0},
|
||||
{0.0, 0.0, 1.0},
|
||||
{0.0, 0.0, -5.0},
|
||||
{0.0, 0.0, 5.0}
|
||||
};
|
||||
vect_t input = {1.0, 2.0, 3.0};
|
||||
vect_t perp = VINIT_ZERO;
|
||||
vect_t expected_perp = {0.0, 0.0, 1.0};
|
||||
@@ -806,9 +829,14 @@ test_mat_orientation(void)
|
||||
vect_t to = {0.0, 1.0, 0.0};
|
||||
vect_t opposite = {-1.0, 0.0, 0.0};
|
||||
vect_t dir = {1.0, 1.0, -2.0};
|
||||
vect_t near_from = {1.0, 0.0, 0.0};
|
||||
vect_t near_to = {1.0, 0.1, 0.0};
|
||||
vect_t near_to_unit = VINIT_ZERO;
|
||||
vect_t zero_dir = VINIT_ZERO;
|
||||
vect_t out = VINIT_ZERO;
|
||||
hvect_t homogeneous = HINIT_ZERO;
|
||||
mat_t m = MAT_INIT_ZERO;
|
||||
size_t i;
|
||||
|
||||
if (!HZERO(homogeneous)) {
|
||||
report_failure(test, "HZERO failed for a zero homogeneous vector");
|
||||
@@ -844,6 +872,18 @@ test_mat_orientation(void)
|
||||
failures++;
|
||||
}
|
||||
|
||||
angular_tol.dist = 1.0;
|
||||
angular_tol.dist_sq = 1.0;
|
||||
bn_mat_fromto(m, near_from, near_to, &angular_tol);
|
||||
MAT4X3VEC(out, m, near_from);
|
||||
VUNITIZE(out);
|
||||
VMOVE(near_to_unit, near_to);
|
||||
VUNITIZE(near_to_unit);
|
||||
if (VDOT(out, near_to_unit) < 1.0 - 1.0e-12 || !orthonormal_rotation(m, 1.0e-12)) {
|
||||
report_failure(test, "bn_mat_fromto used distance tolerance for angular classification");
|
||||
failures++;
|
||||
}
|
||||
|
||||
VUNITIZE(dir);
|
||||
bn_mat_lookat(m, dir, 0);
|
||||
MAT4X3VEC(out, m, dir);
|
||||
@@ -854,6 +894,19 @@ test_mat_orientation(void)
|
||||
failures++;
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(lookat_directions) / sizeof(lookat_directions[0]); i++) {
|
||||
if (!lookat_is_valid(lookat_directions[i])) {
|
||||
report_failure(test, "bn_mat_lookat failed for an axial direction");
|
||||
failures++;
|
||||
}
|
||||
}
|
||||
|
||||
bn_mat_lookat(m, zero_dir, 0);
|
||||
if (!mat_close(m, bn_mat_identity, 0.0)) {
|
||||
report_failure(test, "bn_mat_lookat did not return identity for a zero direction");
|
||||
failures++;
|
||||
}
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
@@ -864,6 +917,7 @@ test_mat_transform(void)
|
||||
int failures = 0;
|
||||
const char *test = "mat_transform";
|
||||
const double legacy_tol = BN_TOL_DIST;
|
||||
struct bn_tol tol = BN_TOL_INIT_TOL;
|
||||
static const struct {
|
||||
double sinv;
|
||||
double cosv;
|
||||
@@ -919,8 +973,14 @@ test_mat_transform(void)
|
||||
point_t p = {2.0, 2.0, 3.0};
|
||||
point_t axis_point = {1.0, 2.0, 10.0};
|
||||
point_t expected = {1.0, 3.0, 3.0};
|
||||
point_t pivot = {1.0, 2.0, 3.0};
|
||||
point_t pivot_offset = {2.0, 2.0, 3.0};
|
||||
point_t pivot_expected = {1.0, 3.0, 3.0};
|
||||
point_t out = VINIT_ZERO;
|
||||
vect_t pivot_direction = {0.0, 0.0, 1.0};
|
||||
mat_t m = MAT_INIT_ZERO;
|
||||
mat_t change = MAT_INIT_ZERO;
|
||||
mat_t input = MAT_INIT_ZERO;
|
||||
int i;
|
||||
|
||||
bn_mat_arb_rot(m, anchor, axis, M_PI_2);
|
||||
@@ -936,6 +996,20 @@ test_mat_transform(void)
|
||||
failures++;
|
||||
}
|
||||
|
||||
bn_mat_zrot(change, 1.0, 0.0);
|
||||
MAT_IDN(input);
|
||||
bn_wrt_point_direc(m, change, input, pivot, pivot_direction, &tol);
|
||||
MAT4X3PNT(out, m, pivot);
|
||||
if (!vect_close(out, pivot, 1.0e-12)) {
|
||||
report_failure(test, "bn_wrt_point_direc moved its pivot point");
|
||||
failures++;
|
||||
}
|
||||
MAT4X3PNT(out, m, pivot_offset);
|
||||
if (!vect_close(out, pivot_expected, 1.0e-12)) {
|
||||
report_failure(test, "bn_wrt_point_direc did not rotate around its pivot point");
|
||||
failures++;
|
||||
}
|
||||
|
||||
MAT_IDN(m);
|
||||
if (bn_mat_is_non_unif(m) != 0) {
|
||||
report_failure(test, "bn_mat_is_non_unif misclassified the identity matrix");
|
||||
|
||||
@@ -199,6 +199,153 @@ test_poly_ops(void)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_poly_edge_cases(void)
|
||||
{
|
||||
int failures = 0;
|
||||
const char *test = "poly_edge_cases";
|
||||
const double linear_coeffs[] = {1.0, -1.0};
|
||||
const double quadratic_coeffs[] = {1.0, 0.0, -4.0};
|
||||
const double aliased_sum_coeffs[] = {1.0, 1.0, -5.0};
|
||||
const double aliased_difference_coeffs[] = {-1.0, 1.0, 3.0};
|
||||
const double aliased_product_coeffs[] = {1.0, -1.0, -4.0, 4.0};
|
||||
const double dividend_coeffs[] = {1.0, -3.0, 2.0};
|
||||
const double constant_divisor_coeffs[] = {2.0};
|
||||
const double constant_quotient_coeffs[] = {0.5, -1.5, 1.0};
|
||||
const double higher_divisor_coeffs[] = {1.0, 0.0, 1.0};
|
||||
const double degree_six_coeffs[] = {1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
|
||||
const double untouched_product_coeffs[] = {42.0};
|
||||
const double zero_coeffs[] = {0.0};
|
||||
const double scaled_quadratic_coeffs[] = {1.0e-12, -5.0e-12, 6.0e-12};
|
||||
const double scaled_cubic_coeffs[] = {1.0e-12, -6.0e-12, 11.0e-12, -6.0e-12};
|
||||
const double scaled_quartic_coeffs[] = {1.0e-12, 0.0, -10.0e-12, 0.0, 9.0e-12};
|
||||
bn_poly_t linear = poly_from_coeffs(1, linear_coeffs);
|
||||
bn_poly_t quadratic = poly_from_coeffs(2, quadratic_coeffs);
|
||||
bn_poly_t dividend = poly_from_coeffs(2, dividend_coeffs);
|
||||
bn_poly_t constant_divisor = poly_from_coeffs(0, constant_divisor_coeffs);
|
||||
bn_poly_t higher_divisor = poly_from_coeffs(2, higher_divisor_coeffs);
|
||||
bn_poly_t degree_six = poly_from_coeffs(6, degree_six_coeffs);
|
||||
bn_poly_t rejected_product = poly_from_coeffs(0, untouched_product_coeffs);
|
||||
bn_poly_t quotient = BN_POLY_INIT_ZERO;
|
||||
bn_poly_t remainder = BN_POLY_INIT_ZERO;
|
||||
bn_poly_t scaled_quadratic = poly_from_coeffs(2, scaled_quadratic_coeffs);
|
||||
bn_poly_t scaled_cubic = poly_from_coeffs(3, scaled_cubic_coeffs);
|
||||
bn_poly_t scaled_quartic = poly_from_coeffs(4, scaled_quartic_coeffs);
|
||||
bn_complex_t roots[4];
|
||||
|
||||
if (!bn_poly_add(&linear, &linear, &quadratic) ||
|
||||
!poly_close(&linear, 2, aliased_sum_coeffs, 0.0)) {
|
||||
report_failure(test, "bn_poly_add did not support output aliasing a lower-degree input");
|
||||
failures++;
|
||||
}
|
||||
|
||||
linear = poly_from_coeffs(1, linear_coeffs);
|
||||
if (!bn_poly_sub(&linear, &linear, &quadratic) ||
|
||||
!poly_close(&linear, 2, aliased_difference_coeffs, 0.0)) {
|
||||
report_failure(test, "bn_poly_sub did not support output aliasing a lower-degree input");
|
||||
failures++;
|
||||
}
|
||||
|
||||
linear = poly_from_coeffs(1, linear_coeffs);
|
||||
if (!bn_poly_mul(&linear, &linear, &quadratic) ||
|
||||
!poly_close(&linear, 3, aliased_product_coeffs, 0.0)) {
|
||||
report_failure(test, "bn_poly_mul did not support output aliasing an input");
|
||||
failures++;
|
||||
}
|
||||
|
||||
if (bn_poly_mul(&rejected_product, °ree_six, &quadratic) ||
|
||||
!poly_close(&rejected_product, 0, untouched_product_coeffs, 0.0)) {
|
||||
report_failure(test, "bn_poly_mul modified its output after a degree-limit failure");
|
||||
failures++;
|
||||
}
|
||||
|
||||
linear = poly_from_coeffs(1, linear_coeffs);
|
||||
bn_poly_synthetic_division("ient, &remainder, ÷nd, &constant_divisor);
|
||||
if (!poly_close("ient, 2, constant_quotient_coeffs, 0.0) ||
|
||||
!poly_close(&remainder, 0, zero_coeffs, 0.0)) {
|
||||
report_failure(test, "bn_poly_synthetic_division failed for a constant divisor");
|
||||
failures++;
|
||||
}
|
||||
|
||||
bn_poly_synthetic_division("ient, &remainder, &linear, &higher_divisor);
|
||||
if (!poly_close("ient, 0, zero_coeffs, 0.0) ||
|
||||
!poly_close(&remainder, 1, linear_coeffs, 0.0)) {
|
||||
report_failure(test, "bn_poly_synthetic_division mishandled a higher-degree divisor");
|
||||
failures++;
|
||||
}
|
||||
|
||||
if (!bn_poly_quadratic_roots(roots, &scaled_quadratic) ||
|
||||
!has_real_root(roots, 2, 2.0, 1.0e-10) ||
|
||||
!has_real_root(roots, 2, 3.0, 1.0e-10)) {
|
||||
report_failure(test, "bn_poly_quadratic_roots rejected a scaled quadratic");
|
||||
failures++;
|
||||
}
|
||||
|
||||
if (!bn_poly_cubic_roots(roots, &scaled_cubic) ||
|
||||
!has_real_root(roots, 3, 1.0, 1.0e-10) ||
|
||||
!has_real_root(roots, 3, 2.0, 1.0e-10) ||
|
||||
!has_real_root(roots, 3, 3.0, 1.0e-10)) {
|
||||
report_failure(test, "bn_poly_cubic_roots rejected a scaled cubic");
|
||||
failures++;
|
||||
}
|
||||
|
||||
if (!bn_poly_quartic_roots(roots, &scaled_quartic) ||
|
||||
!has_real_root(roots, 4, -3.0, 1.0e-8) ||
|
||||
!has_real_root(roots, 4, -1.0, 1.0e-8) ||
|
||||
!has_real_root(roots, 4, 1.0, 1.0e-8) ||
|
||||
!has_real_root(roots, 4, 3.0, 1.0e-8)) {
|
||||
report_failure(test, "bn_poly_quartic_roots rejected a scaled quartic");
|
||||
failures++;
|
||||
}
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_multipoly_ops(void)
|
||||
{
|
||||
int failures = 0;
|
||||
const char *test = "multipoly_ops";
|
||||
struct bn_multipoly *p1 = bn_multipoly_new(1, 1);
|
||||
struct bn_multipoly *p2 = bn_multipoly_new(3, 2);
|
||||
struct bn_multipoly *sum;
|
||||
struct bn_multipoly *product;
|
||||
|
||||
if (!p1 || !p2 || !bn_multipoly_set(p1, 1, 2, 3.0) ||
|
||||
!bn_multipoly_set(p2, 0, 0, 2.0) ||
|
||||
!bn_multipoly_set(p2, 2, 1, 5.0)) {
|
||||
report_failure(test, "could not construct bivariate polynomial operands");
|
||||
bn_multipoly_free(p1);
|
||||
bn_multipoly_free(p2);
|
||||
return failures + 1;
|
||||
}
|
||||
|
||||
sum = bn_multipoly_add(p1, p2);
|
||||
product = bn_multipoly_mul(p1, p2);
|
||||
if (!sum || sum->dgrs != 3 || sum->dgrt != 3 ||
|
||||
!scalar_close(sum->cf[0][0], 2.0, 0.0) ||
|
||||
!scalar_close(sum->cf[1][2], 3.0, 0.0) ||
|
||||
!scalar_close(sum->cf[2][1], 5.0, 0.0)) {
|
||||
report_failure(test, "bn_multipoly_add did not preserve differently sized operands");
|
||||
failures++;
|
||||
}
|
||||
|
||||
if (!product || product->dgrs != 4 || product->dgrt != 4 ||
|
||||
!scalar_close(product->cf[1][2], 6.0, 0.0) ||
|
||||
!scalar_close(product->cf[3][3], 15.0, 0.0)) {
|
||||
report_failure(test, "bn_multipoly_mul did not accumulate coefficient products");
|
||||
failures++;
|
||||
}
|
||||
|
||||
bn_multipoly_free(product);
|
||||
bn_multipoly_free(sum);
|
||||
bn_multipoly_free(p2);
|
||||
bn_multipoly_free(p1);
|
||||
return failures;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
test_poly_legacy(void)
|
||||
{
|
||||
@@ -553,6 +700,8 @@ test_poly_roots(void)
|
||||
|
||||
static const struct bn_api_case poly_cases[] = {
|
||||
{"ops", test_poly_ops},
|
||||
{"edge", test_poly_edge_cases},
|
||||
{"multipoly", test_multipoly_ops},
|
||||
{"legacy", test_poly_legacy},
|
||||
{"roots", test_poly_roots},
|
||||
{NULL, NULL}
|
||||
|
||||
@@ -244,6 +244,12 @@ test_qmath_interp(void)
|
||||
failures++;
|
||||
}
|
||||
|
||||
quat_bisect(out, qid, qanti);
|
||||
if (!quat_is_unit_finite(out, 1.0e-12)) {
|
||||
report_failure(test, "quat_bisect antipodal branch failed to return a finite unit quaternion");
|
||||
failures++;
|
||||
}
|
||||
|
||||
quat_double(exact, qdouble_same_in, qdouble_same_in);
|
||||
if (!hvect_close(exact, qdouble_same_expected, 1.0e-3)) {
|
||||
report_failure(test, "quat_double legacy same-input case failed");
|
||||
@@ -326,6 +332,8 @@ test_qmath_logexp(void)
|
||||
quat_t v = {0.2, -0.1, 0.3, 0.0};
|
||||
quat_t q = HINIT_ZERO;
|
||||
quat_t out = HINIT_ZERO;
|
||||
quat_t qidentity = {0.0, 0.0, 0.0, 1.0};
|
||||
quat_t qzero = HINIT_ZERO;
|
||||
quat_t qin = {0.3, 0.4, 0.1, 0.85};
|
||||
int i;
|
||||
|
||||
@@ -347,6 +355,12 @@ test_qmath_logexp(void)
|
||||
failures++;
|
||||
}
|
||||
|
||||
quat_log(out, qidentity);
|
||||
if (!hvect_close(out, qzero, 0.0)) {
|
||||
report_failure(test, "quat_log did not map identity to the zero quaternion");
|
||||
failures++;
|
||||
}
|
||||
|
||||
for (i = 0; i < (int)(sizeof(exp_cases) / sizeof(exp_cases[0])); i++) {
|
||||
quat_exp(q, exp_cases[i].in);
|
||||
if (!hvect_close(q, exp_cases[i].expected, 1.0e-3)) {
|
||||
|
||||
+652
-251
File diff suppressed because it is too large
Load Diff
+180
-262
@@ -21,6 +21,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cfloat>
|
||||
|
||||
#include "bu/log.h"
|
||||
#include "brep/brnode.h"
|
||||
@@ -34,32 +35,27 @@ BRNode::BRNode(
|
||||
int trim_index,
|
||||
int adj_face_index,
|
||||
const ON_BoundingBox &node,
|
||||
const ON_BrepFace *face,
|
||||
const ON_Interval &t,
|
||||
bool innerTrim,
|
||||
bool checkTrim,
|
||||
bool trimmed) :
|
||||
m_node(node),
|
||||
m_v(),
|
||||
bool innerTrim) :
|
||||
m_adj_face_index(adj_face_index),
|
||||
m_XIncreasing(false),
|
||||
m_Horizontal(false),
|
||||
m_Vertical(false),
|
||||
m_innerTrim(innerTrim),
|
||||
m_stl(new Stl),
|
||||
m_face(face),
|
||||
m_u(),
|
||||
m_trim(curve),
|
||||
m_trim_index(trim_index),
|
||||
m_t(t),
|
||||
m_checkTrim(checkTrim),
|
||||
m_trimmed(trimmed),
|
||||
m_estimate(),
|
||||
m_slope(0.0),
|
||||
m_bb_diag(0.0),
|
||||
m_start(curve->PointAt(m_t[0])),
|
||||
m_end(curve->PointAt(m_t[1]))
|
||||
m_bbox_min{node.m_min.x, node.m_min.y},
|
||||
m_bbox_max{node.m_max.x, node.m_max.y},
|
||||
m_start{0.0, 0.0},
|
||||
m_end{0.0, 0.0}
|
||||
{
|
||||
const ON_3dPoint start = curve->PointAt(m_t[0]);
|
||||
const ON_3dPoint end = curve->PointAt(m_t[1]);
|
||||
m_start[0] = start.x;
|
||||
m_start[1] = start.y;
|
||||
m_end[0] = end.x;
|
||||
m_end[1] = end.y;
|
||||
/* check for vertical segments they can be removed from trims
|
||||
* above (can't tell direction and don't need
|
||||
*/
|
||||
@@ -69,21 +65,6 @@ BRNode::BRNode(
|
||||
/*
|
||||
* should be okay since we split on Horz/Vert tangents
|
||||
*/
|
||||
if (m_end[X] < m_start[X]) {
|
||||
m_u[0] = m_end[X];
|
||||
m_u[1] = m_start[X];
|
||||
} else {
|
||||
m_u[0] = m_start[X];
|
||||
m_u[1] = m_end[X];
|
||||
}
|
||||
if (m_end[Y] < m_start[Y]) {
|
||||
m_v[0] = m_end[Y];
|
||||
m_v[1] = m_start[Y];
|
||||
} else {
|
||||
m_v[0] = m_start[Y];
|
||||
m_v[1] = m_end[Y];
|
||||
}
|
||||
|
||||
if (NEAR_EQUAL(m_end[X], m_start[X], BREP_UV_DIST_FUZZ)) {
|
||||
m_Vertical = true;
|
||||
if (m_innerTrim) {
|
||||
@@ -98,299 +79,236 @@ BRNode::BRNode(
|
||||
} else {
|
||||
m_XIncreasing = false;
|
||||
}
|
||||
m_slope = 0.0;
|
||||
} else {
|
||||
if ((m_end[X] - m_start[X]) > 0.0) {
|
||||
m_XIncreasing = true;
|
||||
} else {
|
||||
m_XIncreasing = false;
|
||||
}
|
||||
m_slope = (m_end[Y] - m_start[Y]) / (m_end[X] - m_start[X]);
|
||||
}
|
||||
m_bb_diag = DIST_PNT_PNT(m_start, m_end);
|
||||
}
|
||||
|
||||
|
||||
BRNode::BRNode(const ON_BoundingBox &node) :
|
||||
m_node(node),
|
||||
m_v(),
|
||||
m_adj_face_index(-1),
|
||||
m_XIncreasing(false),
|
||||
m_Horizontal(false),
|
||||
m_Vertical(false),
|
||||
m_innerTrim(false),
|
||||
m_stl(new Stl),
|
||||
m_face(NULL),
|
||||
m_u(),
|
||||
m_trim(NULL),
|
||||
m_trim_index(-1),
|
||||
m_t(),
|
||||
m_checkTrim(true),
|
||||
m_trimmed(false),
|
||||
m_estimate(),
|
||||
m_slope(0.0),
|
||||
m_bb_diag(0.0),
|
||||
m_start(ON_3dPoint::UnsetPoint),
|
||||
m_end(ON_3dPoint::UnsetPoint)
|
||||
{
|
||||
for (int i = 0; i < 3; i++) {
|
||||
double d = m_node.m_max[i] - m_node.m_min[i];
|
||||
if (NEAR_ZERO(d, ON_ZERO_TOLERANCE)) {
|
||||
m_node.m_min[i] -= 0.001;
|
||||
m_node.m_max[i] += 0.001;
|
||||
}
|
||||
}
|
||||
m_start = m_node.m_min;
|
||||
m_end = m_node.m_max;
|
||||
}
|
||||
|
||||
|
||||
BRNode::~BRNode()
|
||||
{
|
||||
/* delete the children */
|
||||
for (size_t i = 0; i < m_stl->m_children.size(); i++) {
|
||||
delete m_stl->m_children[i];
|
||||
}
|
||||
|
||||
delete m_stl;
|
||||
}
|
||||
|
||||
|
||||
BRNode::BRNode(Deserializer &deserializer, const ON_Brep &brep) :
|
||||
m_node(),
|
||||
m_v(),
|
||||
m_adj_face_index(-1),
|
||||
m_XIncreasing(false),
|
||||
m_Horizontal(false),
|
||||
m_Vertical(false),
|
||||
m_innerTrim(false),
|
||||
m_stl(new Stl),
|
||||
m_face(NULL),
|
||||
m_u(),
|
||||
m_trim(NULL),
|
||||
m_trim_index(-1),
|
||||
m_t(),
|
||||
m_checkTrim(false),
|
||||
m_trimmed(false),
|
||||
m_estimate(),
|
||||
m_slope(0.0),
|
||||
m_bb_diag(0.0),
|
||||
m_start(ON_3dPoint::UnsetPoint),
|
||||
m_end(ON_3dPoint::UnsetPoint)
|
||||
m_bbox_min{0.0, 0.0},
|
||||
m_bbox_max{0.0, 0.0},
|
||||
m_start{0.0, 0.0},
|
||||
m_end{0.0, 0.0}
|
||||
{
|
||||
deserializer.read(m_node);
|
||||
deserializer.read(m_u);
|
||||
deserializer.read(m_v);
|
||||
deserializer.read(m_t);
|
||||
deserializer.read(m_start);
|
||||
deserializer.read(m_end);
|
||||
deserializer.read(m_estimate);
|
||||
|
||||
m_slope = deserializer.read_double();
|
||||
m_bb_diag = deserializer.read_double();
|
||||
for (int direction = 0; direction < 2; ++direction) {
|
||||
m_bbox_min[direction] = deserializer.read_double();
|
||||
m_bbox_max[direction] = deserializer.read_double();
|
||||
m_start[direction] = deserializer.read_double();
|
||||
m_end[direction] = deserializer.read_double();
|
||||
}
|
||||
|
||||
const uint8_t bool_flags = deserializer.read_uint8();
|
||||
const int face_index = deserializer.read_int32();
|
||||
m_trim_index = deserializer.read_int32();
|
||||
m_adj_face_index = deserializer.read_uint32();
|
||||
const std::size_t num_children = deserializer.read_uint32();
|
||||
m_adj_face_index = deserializer.read_int32();
|
||||
|
||||
m_XIncreasing = bool_flags & (1 << 0);
|
||||
m_Horizontal = bool_flags & (1 << 1);
|
||||
m_Vertical = bool_flags & (1 << 2);
|
||||
m_innerTrim = bool_flags & (1 << 3);
|
||||
m_checkTrim = bool_flags & (1 << 4);
|
||||
m_trimmed = bool_flags & (1 << 5);
|
||||
|
||||
if (face_index != -1) {
|
||||
if (const ON_BrepFace * const face = brep.m_F.At(face_index))
|
||||
m_face = face;
|
||||
else
|
||||
bu_bomb("invalid face index");
|
||||
}
|
||||
|
||||
if (m_trim_index != -1) {
|
||||
if (const ON_BrepTrim * const trim = brep.m_T.At(m_trim_index))
|
||||
m_trim = trim->TrimCurveOf();
|
||||
else
|
||||
bu_bomb("invalid face index");
|
||||
bu_bomb("invalid trim index");
|
||||
}
|
||||
|
||||
m_stl->m_children.resize(num_children);
|
||||
for (std::vector<const BRNode *>::iterator it = m_stl->m_children.begin(); it != m_stl->m_children.end(); ++it)
|
||||
*it = new BRNode(deserializer, brep);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BRNode::serialize(Serializer &serializer) const
|
||||
{
|
||||
const uint8_t bool_flags = (m_XIncreasing << 0) | (m_Horizontal << 1) | (m_Vertical << 2) | (m_innerTrim << 3) | (m_checkTrim << 4) | (m_trimmed << 5);
|
||||
const uint8_t bool_flags = (m_XIncreasing << 0) |
|
||||
(m_Horizontal << 1) | (m_Vertical << 2) |
|
||||
(m_innerTrim << 3);
|
||||
|
||||
serializer.write(m_node);
|
||||
serializer.write(m_u);
|
||||
serializer.write(m_v);
|
||||
serializer.write(m_t);
|
||||
serializer.write(m_start);
|
||||
serializer.write(m_end);
|
||||
serializer.write(m_estimate);
|
||||
|
||||
serializer.write_double(m_slope);
|
||||
serializer.write_double(m_bb_diag);
|
||||
for (int direction = 0; direction < 2; ++direction) {
|
||||
serializer.write_double(m_bbox_min[direction]);
|
||||
serializer.write_double(m_bbox_max[direction]);
|
||||
serializer.write_double(m_start[direction]);
|
||||
serializer.write_double(m_end[direction]);
|
||||
}
|
||||
|
||||
serializer.write_uint8(bool_flags);
|
||||
serializer.write_int32(m_face ? m_face->m_face_index : -1);
|
||||
serializer.write_int32(m_trim_index);
|
||||
serializer.write_uint32(m_adj_face_index);
|
||||
serializer.write_uint32(m_stl->m_children.size());
|
||||
|
||||
for (std::vector<const BRNode *>::const_iterator it = m_stl->m_children.begin(); it != m_stl->m_children.end(); ++it)
|
||||
(*it)->serialize(serializer);
|
||||
serializer.write_int32(m_adj_face_index);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
BRNode::depth() const
|
||||
double
|
||||
BRNode::curveBBoxDistance(const ON_2dPoint &uv) const
|
||||
{
|
||||
int d = 0;
|
||||
for (size_t i = 0; i < m_stl->m_children.size(); i++) {
|
||||
d = 1 + std::max(d, m_stl->m_children[i]->depth());
|
||||
}
|
||||
return d;
|
||||
double du = 0.0;
|
||||
double dv = 0.0;
|
||||
if (uv.x < m_bbox_min[X])
|
||||
du = m_bbox_min[X] - uv.x;
|
||||
else if (uv.x > m_bbox_max[X])
|
||||
du = uv.x - m_bbox_max[X];
|
||||
if (uv.y < m_bbox_min[Y])
|
||||
dv = m_bbox_min[Y] - uv.y;
|
||||
else if (uv.y > m_bbox_max[Y])
|
||||
dv = uv.y - m_bbox_max[Y];
|
||||
return hypot(du, dv);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
BRNode::getLeaves(std::list<const BRNode *> &out_leaves) const
|
||||
double
|
||||
BRNode::curveDistance(const ON_2dPoint &uv) const
|
||||
{
|
||||
if (!m_stl->m_children.empty()) {
|
||||
for (size_t i = 0; i < m_stl->m_children.size(); i++) {
|
||||
m_stl->m_children[i]->getLeaves(out_leaves);
|
||||
if (!m_trim)
|
||||
return INFINITY;
|
||||
const size_t sample_count = 8;
|
||||
size_t best_sample = 0;
|
||||
double best_squared = INFINITY;
|
||||
for (size_t sample = 0; sample <= sample_count; ++sample) {
|
||||
const double fraction = (double)sample / (double)sample_count;
|
||||
const ON_3dPoint point = m_trim->PointAt(m_t.ParameterAt(fraction));
|
||||
const double dx = point.x - uv.x;
|
||||
const double dy = point.y - uv.y;
|
||||
const double squared = dx * dx + dy * dy;
|
||||
if (squared < best_squared) {
|
||||
best_squared = squared;
|
||||
best_sample = sample;
|
||||
}
|
||||
} else {
|
||||
out_leaves.push_back(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const BRNode *
|
||||
BRNode::closer(const ON_3dPoint &pt, const BRNode *left, const BRNode *right) const
|
||||
{
|
||||
double ldist = pt.DistanceTo(left->m_estimate);
|
||||
double rdist = pt.DistanceTo(right->m_estimate);
|
||||
TRACE("\t" << ldist << " < " << rdist);
|
||||
if (ldist < rdist) {
|
||||
return left;
|
||||
} else {
|
||||
return right;
|
||||
double low = best_sample ?
|
||||
(double)(best_sample - 1) / (double)sample_count : 0.0;
|
||||
double high = best_sample < sample_count ?
|
||||
(double)(best_sample + 1) / (double)sample_count : 1.0;
|
||||
const double golden = 0.6180339887498948482;
|
||||
double first = high - golden * (high - low);
|
||||
double second = low + golden * (high - low);
|
||||
auto squared_distance = [this, &uv](double fraction) {
|
||||
const ON_3dPoint point = m_trim->PointAt(m_t.ParameterAt(fraction));
|
||||
const double dx = point.x - uv.x;
|
||||
const double dy = point.y - uv.y;
|
||||
return dx * dx + dy * dy;
|
||||
};
|
||||
double first_squared = squared_distance(first);
|
||||
double second_squared = squared_distance(second);
|
||||
for (int iteration = 0; iteration < 24; ++iteration) {
|
||||
best_squared = std::min(best_squared,
|
||||
std::min(first_squared, second_squared));
|
||||
if (first_squared < second_squared) {
|
||||
high = second;
|
||||
second = first;
|
||||
second_squared = first_squared;
|
||||
first = high - golden * (high - low);
|
||||
first_squared = squared_distance(first);
|
||||
} else {
|
||||
low = first;
|
||||
first = second;
|
||||
first_squared = second_squared;
|
||||
second = low + golden * (high - low);
|
||||
second_squared = squared_distance(second);
|
||||
}
|
||||
}
|
||||
best_squared = std::min(best_squared,
|
||||
std::min(first_squared, second_squared));
|
||||
return sqrt(std::max(0.0, best_squared));
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
BRNode::isTrimmed(const ON_2dPoint &uv, double &trimdist) const
|
||||
BRNode::crossesAbove(const ON_2dPoint &uv, double &distance,
|
||||
double endpoint_tolerance) const
|
||||
{
|
||||
point_t bmin, bmax;
|
||||
BRNode::GetBBox(bmin, bmax);
|
||||
if ((bmin[X] <= uv[X]) && (uv[X] <= bmax[X])) /* if check trim and in BBox */
|
||||
{
|
||||
fastf_t v = getCurveEstimateOfV(uv[X], 0.0000001);
|
||||
trimdist = v - uv[Y];
|
||||
if (uv[Y] <= v) {
|
||||
if (m_XIncreasing) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (uv[Y] > v) {
|
||||
if (!m_XIncreasing) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
trimdist = -1.0;
|
||||
if (m_trimmed) {
|
||||
return true;
|
||||
} else {
|
||||
distance = -1.0;
|
||||
if (!m_trim || m_Vertical)
|
||||
return false;
|
||||
const double first_u = m_start[X];
|
||||
const double second_u = m_end[X];
|
||||
const bool exact =
|
||||
((first_u <= uv.x && uv.x < second_u) ||
|
||||
(second_u <= uv.x && uv.x < first_u));
|
||||
double query_u = uv.x;
|
||||
if (!exact) {
|
||||
if (!(endpoint_tolerance > 0.0))
|
||||
return false;
|
||||
/* A closed trim owns both coincident curve-domain endpoints. Its exact
|
||||
* half-open test already gives that vertex to one incident branch; the
|
||||
* imported-loop gap allowance must not admit the other branch too. */
|
||||
if (m_trim->IsClosed())
|
||||
return false;
|
||||
const double minimum_u = std::min(first_u, second_u);
|
||||
const double minimum_t = first_u < second_u ? m_t[0] : m_t[1];
|
||||
const ON_Interval curve_domain = m_trim->Domain();
|
||||
const double parameter_scale = std::max(1.0,
|
||||
std::max(fabs(curve_domain.Min()), fabs(curve_domain.Max())));
|
||||
const double parameter_fuzz = 64.0 * DBL_EPSILON * parameter_scale;
|
||||
const bool minimum_is_trim_endpoint =
|
||||
fabs(minimum_t - curve_domain.Min()) <= parameter_fuzz ||
|
||||
fabs(minimum_t - curve_domain.Max()) <= parameter_fuzz;
|
||||
const double endpoint_fuzz = std::max(BREP_UV_DIST_FUZZ,
|
||||
std::max(0.0, endpoint_tolerance));
|
||||
if (uv.x < minimum_u &&
|
||||
minimum_u - uv.x <= endpoint_fuzz &&
|
||||
minimum_is_trim_endpoint)
|
||||
query_u = minimum_u;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const double curve_v = getCurveEstimateOfV(query_u, 1.0e-7);
|
||||
distance = curve_v - uv.y;
|
||||
return distance >= 0.0;
|
||||
}
|
||||
|
||||
|
||||
ON_2dPoint
|
||||
BRNode::getClosestPointEstimate(const ON_3dPoint &pt) const
|
||||
bool
|
||||
BRNode::crossesRight(const ON_2dPoint &uv, double &distance,
|
||||
double endpoint_tolerance) const
|
||||
{
|
||||
ON_Interval u, v;
|
||||
return getClosestPointEstimate(pt, u, v);
|
||||
}
|
||||
|
||||
|
||||
ON_2dPoint
|
||||
BRNode::getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const
|
||||
{
|
||||
if (isLeaf()) {
|
||||
double uvs[5][2] = {{m_u.Min(), m_v.Min()}, /* include the corners for an easy refinement */
|
||||
{m_u.Max(), m_v.Min()},
|
||||
{m_u.Max(), m_v.Max()},
|
||||
{m_u.Min(), m_v.Max()},
|
||||
{m_u.Mid(), m_v.Mid()}
|
||||
}; /* include the estimate */
|
||||
ON_3dPoint corners[5];
|
||||
const ON_Surface *surf = m_face->SurfaceOf();
|
||||
|
||||
u = m_u;
|
||||
v = m_v;
|
||||
|
||||
/* ??? should we pass these in from SurfaceTree::curveBBox()
|
||||
* to avoid this recalculation?
|
||||
*/
|
||||
if (!surf->EvPoint(uvs[0][0], uvs[0][1], corners[0]) ||
|
||||
!surf->EvPoint(uvs[1][0], uvs[1][1], corners[1]) ||
|
||||
!surf->EvPoint(uvs[2][0], uvs[2][1], corners[2]) ||
|
||||
!surf->EvPoint(uvs[3][0], uvs[3][1], corners[3]))
|
||||
{
|
||||
throw std::exception(); /* FIXME */
|
||||
}
|
||||
corners[4] = BRNode::m_estimate;
|
||||
|
||||
/* find the point on the curve closest to pt */
|
||||
size_t mini = 0;
|
||||
double mindist = pt.DistanceTo(corners[mini]);
|
||||
double tmpdist;
|
||||
for (size_t i = 1; i < 5; i++) {
|
||||
tmpdist = pt.DistanceTo(corners[i]);
|
||||
TRACE("\t" << mindist << " < " << tmpdist);
|
||||
if (tmpdist < mindist) {
|
||||
mini = i;
|
||||
mindist = tmpdist;
|
||||
}
|
||||
}
|
||||
TRACE("Closest: " << mindist << "; " << PT2(uvs[mini]));
|
||||
return ON_2dPoint(uvs[mini][0], uvs[mini][1]);
|
||||
} else {
|
||||
if (!m_stl->m_children.empty()) {
|
||||
const BRNode *closestNode = m_stl->m_children[0];
|
||||
for (size_t i = 1; i < m_stl->m_children.size(); i++) {
|
||||
closestNode = closer(pt, closestNode, m_stl->m_children[i]);
|
||||
}
|
||||
return closestNode->getClosestPointEstimate(pt, u, v);
|
||||
} else {
|
||||
throw std::exception();
|
||||
}
|
||||
distance = -1.0;
|
||||
if (!m_trim || m_Horizontal)
|
||||
return false;
|
||||
const double first_v = m_start[Y];
|
||||
const double second_v = m_end[Y];
|
||||
const bool exact =
|
||||
((first_v <= uv.y && uv.y < second_v) ||
|
||||
(second_v <= uv.y && uv.y < first_v));
|
||||
double query_v = uv.y;
|
||||
if (!exact) {
|
||||
if (!(endpoint_tolerance > 0.0))
|
||||
return false;
|
||||
if (m_trim->IsClosed())
|
||||
return false;
|
||||
const double minimum_v = std::min(first_v, second_v);
|
||||
const double minimum_t = first_v < second_v ? m_t[0] : m_t[1];
|
||||
const ON_Interval curve_domain = m_trim->Domain();
|
||||
const double parameter_scale = std::max(1.0,
|
||||
std::max(fabs(curve_domain.Min()), fabs(curve_domain.Max())));
|
||||
const double parameter_fuzz = 64.0 * DBL_EPSILON * parameter_scale;
|
||||
const bool minimum_is_trim_endpoint =
|
||||
fabs(minimum_t - curve_domain.Min()) <= parameter_fuzz ||
|
||||
fabs(minimum_t - curve_domain.Max()) <= parameter_fuzz;
|
||||
const double endpoint_fuzz = std::max(BREP_UV_DIST_FUZZ,
|
||||
std::max(0.0, endpoint_tolerance));
|
||||
if (uv.y < minimum_v &&
|
||||
minimum_v - uv.y <= endpoint_fuzz &&
|
||||
minimum_is_trim_endpoint)
|
||||
query_v = minimum_v;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fastf_t
|
||||
BRNode::getLinearEstimateOfV(fastf_t u) const
|
||||
{
|
||||
fastf_t v = m_start[Y] + m_slope * (u - m_start[X]);
|
||||
return v;
|
||||
const double curve_u = getCurveEstimateOfU(query_v, 1.0e-7);
|
||||
distance = curve_u - uv.x;
|
||||
return distance >= 0.0;
|
||||
}
|
||||
|
||||
|
||||
@@ -401,13 +319,13 @@ BRNode::getCurveEstimateOfV(fastf_t u, fastf_t tol) const
|
||||
double Ta, Tb;
|
||||
|
||||
if (m_start[X] < m_end[X]) {
|
||||
VMOVE(A, m_start);
|
||||
VMOVE(B, m_end);
|
||||
VSET(A, m_start[X], m_start[Y], 0.0);
|
||||
VSET(B, m_end[X], m_end[Y], 0.0);
|
||||
Ta = m_t[0];
|
||||
Tb = m_t[1];
|
||||
} else {
|
||||
VMOVE(A, m_end);
|
||||
VMOVE(B, m_start);
|
||||
VSET(A, m_end[X], m_end[Y], 0.0);
|
||||
VSET(B, m_start[X], m_start[Y], 0.0);
|
||||
Ta = m_t[1];
|
||||
Tb = m_t[0];
|
||||
}
|
||||
@@ -493,13 +411,13 @@ BRNode::getCurveEstimateOfU(fastf_t v, fastf_t tol) const
|
||||
double Ta, Tb;
|
||||
|
||||
if (m_start[Y] < m_end[Y]) {
|
||||
VMOVE(A, m_start);
|
||||
VMOVE(B, m_end);
|
||||
VSET(A, m_start[X], m_start[Y], 0.0);
|
||||
VSET(B, m_end[X], m_end[Y], 0.0);
|
||||
Ta = m_t[0];
|
||||
Tb = m_t[1];
|
||||
} else {
|
||||
VMOVE(A, m_end);
|
||||
VMOVE(B, m_start);
|
||||
VSET(A, m_end[X], m_end[Y], 0.0);
|
||||
VSET(B, m_start[X], m_start[Y], 0.0);
|
||||
Ta = m_t[1];
|
||||
Tb = m_t[0];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ set(
|
||||
Subcurve.cpp
|
||||
Subsurface.cpp
|
||||
boolean.cpp
|
||||
cobb.cpp
|
||||
cdt/cdt.cpp
|
||||
cdt/fast.cpp
|
||||
cdt/surf.cpp
|
||||
|
||||
+565
-6
@@ -26,17 +26,426 @@
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "bg/chull.h"
|
||||
#include "bg/tri_pt.h"
|
||||
#include "bg/tri_tri.h"
|
||||
#include "./cdt.h"
|
||||
|
||||
#define BREP_PLANAR_TOL 0.05
|
||||
#define MAX_TRIANGULATION_ATTEMPTS 5
|
||||
|
||||
/* cpolyedge_t containers historically used the default pointer comparator.
|
||||
* That is adequate for membership, but it must not determine the order of
|
||||
* geometry-changing operations: allocator layout can then change the order in
|
||||
* which singular trims are subdivided and, consequently, the final surface
|
||||
* sampling. Every singular segment has an authoritative STEP trim index,
|
||||
* parameter interval, and pair of face-local topology vertices, which together
|
||||
* provide a stable processing order. */
|
||||
static bool
|
||||
singular_edge_process_less(const cpolyedge_t *a, const cpolyedge_t *b)
|
||||
{
|
||||
if (a == b)
|
||||
return false;
|
||||
if (!a)
|
||||
return true;
|
||||
if (!b)
|
||||
return false;
|
||||
if (a->trim_ind != b->trim_ind)
|
||||
return a->trim_ind < b->trim_ind;
|
||||
if (a->trim_start < b->trim_start)
|
||||
return true;
|
||||
if (b->trim_start < a->trim_start)
|
||||
return false;
|
||||
if (a->trim_end < b->trim_end)
|
||||
return true;
|
||||
if (b->trim_end < a->trim_end)
|
||||
return false;
|
||||
if (a->v2d[0] != b->v2d[0])
|
||||
return a->v2d[0] < b->v2d[0];
|
||||
return a->v2d[1] < b->v2d[1];
|
||||
}
|
||||
|
||||
/* Correct triangle winding only after the complete mesh proves closed and
|
||||
* manifold and misorientation is its sole defect. bg_trimesh_sync establishes
|
||||
* a consistent orientation for each connected component, but its seed may
|
||||
* choose either of the two possible component orientations. Preserve the
|
||||
* OpenNURBS face orientation by selecting the result that changes the fewest
|
||||
* source triangles in each component. A tied component has no authoritative
|
||||
* majority, so leave the mesh unchanged rather than guessing.
|
||||
*
|
||||
* This function is transactional: faces and face normals are not modified
|
||||
* unless the candidate subsequently passes the complete solid test. */
|
||||
static int
|
||||
closed_mesh_orientation_sync(int *faces, int face_count,
|
||||
fastf_t *vertices, int vertex_count, int *face_normals)
|
||||
{
|
||||
if (!faces || !vertices || face_count < 4 || vertex_count < 4)
|
||||
return -1;
|
||||
|
||||
struct bg_trimesh_solid_errors errors = BG_TRIMESH_SOLID_ERRORS_INIT_NULL;
|
||||
const int initial_invalid = bg_trimesh_solid2(vertex_count, face_count,
|
||||
vertices, faces, &errors);
|
||||
if (!initial_invalid) {
|
||||
bg_free_trimesh_solid_errors(&errors);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const bool only_misoriented = errors.misoriented.count > 0 &&
|
||||
errors.degenerate.count == 0 && errors.unmatched.count == 0 &&
|
||||
errors.excess.count == 0;
|
||||
bg_free_trimesh_solid_errors(&errors);
|
||||
if (!only_misoriented)
|
||||
return -1;
|
||||
|
||||
std::vector<int> original(faces, faces + 3 * face_count);
|
||||
std::vector<int> candidate(original);
|
||||
if (bg_trimesh_sync(candidate.data(), candidate.data(), face_count) <= 0)
|
||||
return -1;
|
||||
|
||||
/* Determine the connected closed-shell components from shared edges. */
|
||||
std::vector<std::vector<int>> adjacent((size_t)face_count);
|
||||
std::map<std::pair<int, int>, int> first_edge_face;
|
||||
for (int face = 0; face < face_count; ++face) {
|
||||
for (int edge = 0; edge < 3; ++edge) {
|
||||
int vertex_a = original[(size_t)face * 3 + edge];
|
||||
int vertex_b = original[(size_t)face * 3 + (edge + 1) % 3];
|
||||
if (vertex_a > vertex_b)
|
||||
std::swap(vertex_a, vertex_b);
|
||||
const std::pair<int, int> key(vertex_a, vertex_b);
|
||||
auto first = first_edge_face.find(key);
|
||||
if (first == first_edge_face.end()) {
|
||||
first_edge_face[key] = face;
|
||||
continue;
|
||||
}
|
||||
adjacent[(size_t)face].push_back(first->second);
|
||||
adjacent[(size_t)first->second].push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> component_id((size_t)face_count, -1);
|
||||
std::vector<std::vector<int>> components;
|
||||
for (int seed = 0; seed < face_count; ++seed) {
|
||||
if (component_id[(size_t)seed] >= 0)
|
||||
continue;
|
||||
const int current_id = (int)components.size();
|
||||
components.push_back(std::vector<int>());
|
||||
std::queue<int> work;
|
||||
work.push(seed);
|
||||
component_id[(size_t)seed] = current_id;
|
||||
while (!work.empty()) {
|
||||
const int face = work.front();
|
||||
work.pop();
|
||||
components[(size_t)current_id].push_back(face);
|
||||
for (int neighbor : adjacent[(size_t)face]) {
|
||||
if (component_id[(size_t)neighbor] >= 0)
|
||||
continue;
|
||||
component_id[(size_t)neighbor] = current_id;
|
||||
work.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::vector<int> &component : components) {
|
||||
size_t changed = 0;
|
||||
for (int face : component) {
|
||||
const size_t offset = (size_t)face * 3;
|
||||
if (candidate[offset] != original[offset] ||
|
||||
candidate[offset + 1] != original[offset + 1] ||
|
||||
candidate[offset + 2] != original[offset + 2])
|
||||
changed++;
|
||||
}
|
||||
if (changed * 2 == component.size())
|
||||
return -1;
|
||||
if (changed * 2 > component.size()) {
|
||||
for (int face : component)
|
||||
std::swap(candidate[(size_t)face * 3],
|
||||
candidate[(size_t)face * 3 + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (bg_trimesh_solid2(vertex_count, face_count, vertices,
|
||||
candidate.data(), NULL) != 0)
|
||||
return -1;
|
||||
|
||||
int changed_count = 0;
|
||||
for (int face = 0; face < face_count; ++face) {
|
||||
const size_t offset = (size_t)face * 3;
|
||||
if (candidate[offset] == original[offset] &&
|
||||
candidate[offset + 1] == original[offset + 1] &&
|
||||
candidate[offset + 2] == original[offset + 2])
|
||||
continue;
|
||||
changed_count++;
|
||||
if (face_normals)
|
||||
std::swap(face_normals[offset], face_normals[offset + 1]);
|
||||
}
|
||||
|
||||
std::copy(candidate.begin(), candidate.end(), faces);
|
||||
return changed_count;
|
||||
}
|
||||
|
||||
/* A failed local face remesh can leave a few open triangle islands in addition
|
||||
* to the useful tessellation of the source solid. Those islands are removed
|
||||
* only under one of two proofs: every retained component is independently
|
||||
* closed and solid, or (for a partial display mesh) every point of an island
|
||||
* is already covered within tolerance by retained triangles from the same
|
||||
* source BREP face. Valid disjoint shells are retained, and no change is made
|
||||
* if neither proof succeeds.
|
||||
*
|
||||
* The operation is transactional with respect to the caller's face arrays.
|
||||
* Vertex arrays may retain now-unused entries; that is legal for a BoT and
|
||||
* keeps the face-normal and point ownership maps stable. */
|
||||
static int
|
||||
closed_mesh_component_filter(int *faces, int *face_count,
|
||||
fastf_t *vertices, int vertex_count, int *face_normals,
|
||||
const int *source_faces = NULL, double overlap_tolerance = 0.0,
|
||||
bool source_is_solid = true, bool emit_diagnostics = true)
|
||||
{
|
||||
if (!faces || !face_count || !vertices || *face_count < 2 ||
|
||||
vertex_count < 4)
|
||||
return 0;
|
||||
|
||||
/* Validate all indices before making the first transactional edit. */
|
||||
for (int face = 0; face < *face_count; ++face) {
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
const int vertex = faces[(size_t)face * 3 + corner];
|
||||
if (vertex < 0 || vertex >= vertex_count)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Distinct face-mesh vertices may collapse to the same shared output
|
||||
* point. Cull the resulting zero-area index triangles before component
|
||||
* discovery; otherwise their repeated edge can falsely bridge two
|
||||
* components in this audit even though rt_bot_split correctly ignores
|
||||
* that bridge. */
|
||||
const int original_face_count = *face_count;
|
||||
std::vector<int> compact_faces;
|
||||
std::vector<int> compact_normals;
|
||||
std::vector<int> compact_sources;
|
||||
compact_faces.reserve((size_t)original_face_count * 3);
|
||||
if (face_normals)
|
||||
compact_normals.reserve((size_t)original_face_count * 3);
|
||||
if (source_faces)
|
||||
compact_sources.reserve((size_t)original_face_count);
|
||||
for (int face = 0; face < original_face_count; ++face) {
|
||||
const size_t offset = (size_t)face * 3;
|
||||
if (faces[offset] == faces[offset + 1] ||
|
||||
faces[offset + 1] == faces[offset + 2] ||
|
||||
faces[offset + 2] == faces[offset])
|
||||
continue;
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
compact_faces.push_back(faces[offset + corner]);
|
||||
if (face_normals)
|
||||
compact_normals.push_back(face_normals[offset + corner]);
|
||||
}
|
||||
if (source_faces)
|
||||
compact_sources.push_back(source_faces[face]);
|
||||
}
|
||||
int removed_count = original_face_count -
|
||||
(int)(compact_faces.size() / 3);
|
||||
if (removed_count) {
|
||||
std::copy(compact_faces.begin(), compact_faces.end(), faces);
|
||||
if (face_normals)
|
||||
std::copy(compact_normals.begin(), compact_normals.end(),
|
||||
face_normals);
|
||||
*face_count = (int)(compact_faces.size() / 3);
|
||||
if (source_faces)
|
||||
source_faces = compact_sources.data();
|
||||
}
|
||||
if (*face_count < 2)
|
||||
return removed_count;
|
||||
|
||||
std::map<std::pair<int, int>, std::vector<int>> edge_faces;
|
||||
for (int face = 0; face < *face_count; ++face) {
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
int first = faces[(size_t)face * 3 + corner];
|
||||
int second = faces[(size_t)face * 3 + (corner + 1) % 3];
|
||||
if (first > second)
|
||||
std::swap(first, second);
|
||||
edge_faces[std::make_pair(first, second)].push_back(face);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<int> component_id((size_t)*face_count, -1);
|
||||
std::vector<std::vector<int>> components;
|
||||
for (int seed = 0; seed < *face_count; ++seed) {
|
||||
if (component_id[(size_t)seed] >= 0)
|
||||
continue;
|
||||
const int current_id = (int)components.size();
|
||||
components.push_back(std::vector<int>());
|
||||
std::queue<int> work;
|
||||
work.push(seed);
|
||||
component_id[(size_t)seed] = current_id;
|
||||
while (!work.empty()) {
|
||||
const int face = work.front();
|
||||
work.pop();
|
||||
components[(size_t)current_id].push_back(face);
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
int first = faces[(size_t)face * 3 + corner];
|
||||
int second =
|
||||
faces[(size_t)face * 3 + (corner + 1) % 3];
|
||||
if (first > second)
|
||||
std::swap(first, second);
|
||||
const std::vector<int> &edge_neighbors =
|
||||
edge_faces[std::make_pair(first, second)];
|
||||
/* An edge used by more than two triangles is non-manifold. It
|
||||
* must not join an otherwise closed shell to a local remesh
|
||||
* artifact during component discovery. Restricting adjacency
|
||||
* to exactly two uses also matches the topological connectivity
|
||||
* required by a valid solid mesh. */
|
||||
if (edge_neighbors.size() != 2)
|
||||
continue;
|
||||
for (int neighbor : edge_neighbors) {
|
||||
if (component_id[(size_t)neighbor] >= 0)
|
||||
continue;
|
||||
component_id[(size_t)neighbor] = current_id;
|
||||
work.push(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (components.size() < 2)
|
||||
return removed_count;
|
||||
|
||||
std::vector<bool> retain(components.size(), false);
|
||||
size_t retained_face_count = 0;
|
||||
for (size_t component = 0;
|
||||
source_is_solid && component < components.size(); ++component) {
|
||||
std::vector<int> component_faces;
|
||||
component_faces.reserve(components[component].size() * 3);
|
||||
for (int face : components[component]) {
|
||||
const size_t offset = (size_t)face * 3;
|
||||
component_faces.push_back(faces[offset]);
|
||||
component_faces.push_back(faces[offset + 1]);
|
||||
component_faces.push_back(faces[offset + 2]);
|
||||
}
|
||||
if (component_faces.size() >= 12 &&
|
||||
bg_trimesh_solid2(vertex_count,
|
||||
(int)components[component].size(), vertices,
|
||||
component_faces.data(), NULL) == 0) {
|
||||
retain[component] = true;
|
||||
retained_face_count += components[component].size();
|
||||
}
|
||||
}
|
||||
if (retained_face_count == (size_t)*face_count)
|
||||
return removed_count;
|
||||
|
||||
/* A partial display tessellation may not itself be closed (for example,
|
||||
* another BREP face may have independently failed to tessellate). We can
|
||||
* still remove a disconnected local-remesh island when every one of its
|
||||
* vertices and its centroid lies on retained triangles from the same
|
||||
* source face. This is an exact redundancy proof within the tessellation
|
||||
* tolerance, not a largest-component guess. */
|
||||
if (!retained_face_count && source_faces &&
|
||||
overlap_tolerance > 0.0) {
|
||||
size_t largest = 0;
|
||||
for (size_t component = 1; component < components.size(); ++component) {
|
||||
if (components[component].size() > components[largest].size())
|
||||
largest = component;
|
||||
}
|
||||
double minimum_failed_distance = DBL_MAX;
|
||||
int failed_source_face = -1;
|
||||
const auto point_covered =
|
||||
[&](const point_t point, int source_face) {
|
||||
double minimum_distance = DBL_MAX;
|
||||
for (int candidate : components[largest]) {
|
||||
if (source_faces[candidate] != source_face)
|
||||
continue;
|
||||
point_t triangle[3];
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
const int vertex =
|
||||
faces[(size_t)candidate * 3 + corner];
|
||||
VMOVE(triangle[corner],
|
||||
&vertices[(size_t)vertex * 3]);
|
||||
}
|
||||
const double distance = bg_tri_closest_pt(NULL, point,
|
||||
triangle[0], triangle[1], triangle[2]);
|
||||
minimum_distance = std::min(minimum_distance, distance);
|
||||
if (distance <= overlap_tolerance)
|
||||
return true;
|
||||
}
|
||||
minimum_failed_distance = minimum_distance;
|
||||
failed_source_face = source_face;
|
||||
return false;
|
||||
};
|
||||
bool redundant = true;
|
||||
for (size_t component = 0;
|
||||
component < components.size() && redundant; ++component) {
|
||||
if (component == largest)
|
||||
continue;
|
||||
for (int face : components[component]) {
|
||||
point_t centroid = VINIT_ZERO;
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
const int vertex = faces[(size_t)face * 3 + corner];
|
||||
point_t point;
|
||||
VMOVE(point, &vertices[(size_t)vertex * 3]);
|
||||
if (!point_covered(point, source_faces[face])) {
|
||||
redundant = false;
|
||||
break;
|
||||
}
|
||||
VADD2(centroid, centroid, point);
|
||||
}
|
||||
VSCALE(centroid, centroid, 1.0 / 3.0);
|
||||
if (redundant &&
|
||||
!point_covered(centroid, source_faces[face]))
|
||||
redundant = false;
|
||||
if (!redundant)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!redundant) {
|
||||
if (!emit_diagnostics)
|
||||
return removed_count;
|
||||
bu_log("CDT retained an open disconnected component because source "
|
||||
"face %d was not redundantly covered (distance %.17g, "
|
||||
"tolerance %.17g)\n", failed_source_face,
|
||||
minimum_failed_distance, overlap_tolerance);
|
||||
return removed_count;
|
||||
}
|
||||
retain[largest] = true;
|
||||
retained_face_count = components[largest].size();
|
||||
}
|
||||
if (!retained_face_count)
|
||||
return removed_count;
|
||||
|
||||
std::vector<int> candidate_faces;
|
||||
std::vector<int> candidate_normals;
|
||||
candidate_faces.reserve(retained_face_count * 3);
|
||||
if (face_normals)
|
||||
candidate_normals.reserve(retained_face_count * 3);
|
||||
for (int face = 0; face < *face_count; ++face) {
|
||||
if (!retain[(size_t)component_id[(size_t)face]])
|
||||
continue;
|
||||
const size_t offset = (size_t)face * 3;
|
||||
for (int corner = 0; corner < 3; ++corner) {
|
||||
candidate_faces.push_back(faces[offset + corner]);
|
||||
if (face_normals)
|
||||
candidate_normals.push_back(face_normals[offset + corner]);
|
||||
}
|
||||
}
|
||||
if (!source_faces &&
|
||||
bg_trimesh_solid2(vertex_count, (int)retained_face_count,
|
||||
vertices, candidate_faces.data(), NULL) != 0)
|
||||
return removed_count;
|
||||
|
||||
const int removed = removed_count +
|
||||
*face_count - (int)retained_face_count;
|
||||
std::copy(candidate_faces.begin(), candidate_faces.end(), faces);
|
||||
if (face_normals)
|
||||
std::copy(candidate_normals.begin(), candidate_normals.end(),
|
||||
face_normals);
|
||||
*face_count = (int)retained_face_count;
|
||||
return removed;
|
||||
}
|
||||
|
||||
// TODO - get rid of all BN_TOL_DIST-only tolerances - if the object is
|
||||
// very small, that distance is too big (e.g. for linearity testing).
|
||||
|
||||
@@ -493,8 +902,12 @@ ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s_cdt, int face_cnt, int *faces
|
||||
int cnt = 0;
|
||||
wq = &w1;
|
||||
nq = &w2;
|
||||
nq->push(*(s_cdt->unsplit_singular_edges.begin()));
|
||||
s_cdt->unsplit_singular_edges.erase(s_cdt->unsplit_singular_edges.begin());
|
||||
std::set<cpolyedge_t *>::iterator first =
|
||||
std::min_element(s_cdt->unsplit_singular_edges.begin(),
|
||||
s_cdt->unsplit_singular_edges.end(),
|
||||
singular_edge_process_less);
|
||||
nq->push(*first);
|
||||
s_cdt->unsplit_singular_edges.erase(first);
|
||||
while (cnt < 6) {
|
||||
cnt = 0;
|
||||
tmpq = wq;
|
||||
@@ -504,8 +917,12 @@ ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s_cdt, int face_cnt, int *faces
|
||||
cpolyedge_t *ce = wq->front();
|
||||
wq->pop();
|
||||
std::set<cpolyedge_t *> nedges = split_singular_seg(s_cdt, ce, 0);
|
||||
std::set<cpolyedge_t *>::iterator n_it;
|
||||
for (n_it = nedges.begin(); n_it != nedges.end(); n_it++) {
|
||||
std::vector<cpolyedge_t *> ordered_edges(nedges.begin(),
|
||||
nedges.end());
|
||||
std::sort(ordered_edges.begin(), ordered_edges.end(),
|
||||
singular_edge_process_less);
|
||||
for (std::vector<cpolyedge_t *>::const_iterator n_it =
|
||||
ordered_edges.begin(); n_it != ordered_edges.end(); ++n_it) {
|
||||
nq->push(*n_it);
|
||||
cnt++;
|
||||
}
|
||||
@@ -558,6 +975,8 @@ ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s_cdt, int face_cnt, int *faces
|
||||
trimesh_error_report(s_cdt, valid_fcnt, valid_vcnt, valid_faces, valid_vertices, &se);
|
||||
}
|
||||
|
||||
bg_free_trimesh_solid_errors(&se);
|
||||
|
||||
bu_free(valid_faces, "faces");
|
||||
bu_free(valid_vertices, "vertices");
|
||||
|
||||
@@ -610,7 +1029,7 @@ ON_Brep_CDT_Mesh(
|
||||
std::set<ON_3dPoint *> vfnormals;
|
||||
std::set<ON_3dPoint *> flip_normals;
|
||||
for (size_t fi = 0; fi < active_faces.size(); fi++) {
|
||||
cdt_mesh_t *fmesh = &s_cdt->fmeshes[(int)fi];
|
||||
cdt_mesh_t *fmesh = &s_cdt->fmeshes[active_faces[fi]];
|
||||
RTree<size_t, double, 3>::Iterator tree_it;
|
||||
fmesh->tris_tree.GetFirst(tree_it);
|
||||
size_t t_ind;
|
||||
@@ -699,8 +1118,10 @@ ON_Brep_CDT_Mesh(
|
||||
// Iterate over faces, adding points and faces to BoT container. Note: all
|
||||
// 3D points should be geometrically unique in this final container.
|
||||
int face_cnt = 0;
|
||||
std::vector<int> output_face_ids;
|
||||
output_face_ids.reserve(triangle_cnt);
|
||||
for (size_t fi = 0; fi < active_faces.size(); fi++) {
|
||||
cdt_mesh_t *fmesh = &s_cdt->fmeshes[(int)fi];
|
||||
cdt_mesh_t *fmesh = &s_cdt->fmeshes[active_faces[fi]];
|
||||
RTree<size_t, double, 3>::Iterator tree_it;
|
||||
fmesh->tris_tree.GetFirst(tree_it);
|
||||
size_t t_ind;
|
||||
@@ -725,11 +1146,149 @@ ON_Brep_CDT_Mesh(
|
||||
}
|
||||
}
|
||||
|
||||
output_face_ids.push_back(fmesh->f_id);
|
||||
face_cnt++;
|
||||
++tree_it;
|
||||
}
|
||||
}
|
||||
|
||||
/* A complete, topologically solid BREP supplies authoritative shell
|
||||
* orientation. If tessellation produced an otherwise closed/manifold
|
||||
* mesh with inconsistent triangle winding, repair that winding only when
|
||||
* the fully synchronized candidate validates as a solid. Partial/open
|
||||
* meshes retain their original per-face ordering. */
|
||||
if (!exp_face_cnt || !exp_faces) {
|
||||
const bool source_is_solid = s_cdt->orig_brep &&
|
||||
s_cdt->orig_brep->IsSolid();
|
||||
const int removed = closed_mesh_component_filter(*faces, fcnt,
|
||||
*vertices, *vcnt, face_normals ? *face_normals : NULL,
|
||||
output_face_ids.data(), std::max(s_cdt->absmin,
|
||||
ON_ZERO_TOLERANCE), source_is_solid);
|
||||
if (removed > 0) {
|
||||
bu_log("%s: removed %d redundant open triangle artifact%s after "
|
||||
"transactional component validation\n",
|
||||
s_cdt->name ? s_cdt->name : "BREP", removed,
|
||||
removed == 1 ? "" : "s");
|
||||
}
|
||||
if (source_is_solid) {
|
||||
const int synchronized = closed_mesh_orientation_sync(*faces, *fcnt,
|
||||
*vertices, *vcnt, face_normals ? *face_normals : NULL);
|
||||
if (synchronized > 0) {
|
||||
bu_log("%s: synchronized %d triangle orientations after complete closed-mesh validation\n",
|
||||
s_cdt->name ? s_cdt->name : "BREP", synchronized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
cdt_test_spurious_components(void)
|
||||
{
|
||||
fastf_t vertices[] = {
|
||||
0.0, 0.0, 0.0,
|
||||
1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 1.0,
|
||||
2.0, 0.0, 0.0,
|
||||
3.0, 0.0, 0.0,
|
||||
2.0, 1.0, 0.0,
|
||||
4.0, 0.0, 0.0
|
||||
};
|
||||
int tetra_and_open[] = {
|
||||
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
|
||||
4, 5, 6
|
||||
};
|
||||
int face_count = 5;
|
||||
if (closed_mesh_component_filter(tetra_and_open, &face_count, vertices,
|
||||
8, NULL) != 1 || face_count != 4)
|
||||
return 1;
|
||||
|
||||
int two_open_components[] = {0, 1, 2, 4, 5, 6};
|
||||
face_count = 2;
|
||||
if (closed_mesh_component_filter(two_open_components, &face_count,
|
||||
vertices, 8, NULL) != 0 || face_count != 2)
|
||||
return 2;
|
||||
|
||||
fastf_t two_tetra_vertices[] = {
|
||||
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
|
||||
3.0, 0.0, 0.0, 4.0, 0.0, 0.0,
|
||||
3.0, 1.0, 0.0, 3.0, 0.0, 1.0
|
||||
};
|
||||
int two_tetra_faces[] = {
|
||||
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
|
||||
4, 6, 5, 4, 5, 7, 4, 7, 6, 5, 6, 7
|
||||
};
|
||||
face_count = 8;
|
||||
if (closed_mesh_component_filter(two_tetra_faces, &face_count,
|
||||
two_tetra_vertices, 8, NULL) != 0 || face_count != 8)
|
||||
return 3;
|
||||
|
||||
fastf_t open_duplicate_vertices[] = {
|
||||
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
|
||||
1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
|
||||
1.0, 1.0, 0.0
|
||||
};
|
||||
int open_duplicate_faces[] = {
|
||||
0, 1, 2, 0, 2, 3, 4, 5, 6
|
||||
};
|
||||
const int open_duplicate_sources[] = {7, 7, 7};
|
||||
face_count = 3;
|
||||
if (closed_mesh_component_filter(open_duplicate_faces, &face_count,
|
||||
open_duplicate_vertices, 7, NULL, open_duplicate_sources,
|
||||
1.0e-12) != 1 || face_count != 2)
|
||||
return 4;
|
||||
|
||||
open_duplicate_vertices[20] = 0.1;
|
||||
int distinct_faces[] = {0, 1, 2, 0, 2, 3, 4, 5, 6};
|
||||
face_count = 3;
|
||||
if (closed_mesh_component_filter(distinct_faces, &face_count,
|
||||
open_duplicate_vertices, 7, NULL, open_duplicate_sources,
|
||||
1.0e-6, true, false) != 0 || face_count != 3)
|
||||
return 5;
|
||||
|
||||
/* A redundant triangle attached to a closed shell through an edge with
|
||||
* three uses is not part of that manifold shell. This is the compact
|
||||
* form of the NIST spherical-cap shading regression: treating the
|
||||
* non-manifold edge as ordinary adjacency hid the extra component from
|
||||
* the cleanup audit even though downstream BoT topology detected it. */
|
||||
fastf_t nonmanifold_vertices[] = {
|
||||
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
|
||||
0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
|
||||
1.0, 0.0, 0.0
|
||||
};
|
||||
int nonmanifold_faces[] = {
|
||||
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
|
||||
0, 2, 4
|
||||
};
|
||||
face_count = 5;
|
||||
if (closed_mesh_component_filter(nonmanifold_faces, &face_count,
|
||||
nonmanifold_vertices, 5, NULL) != 1 || face_count != 4)
|
||||
return 6;
|
||||
|
||||
/* Face-local vertices can collapse to one shared output vertex. The
|
||||
* resulting index-degenerate triangle must not survive into a BoT or
|
||||
* distort component discovery. */
|
||||
int degenerate_faces[] = {
|
||||
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
|
||||
4, 4, 1
|
||||
};
|
||||
face_count = 5;
|
||||
if (closed_mesh_component_filter(degenerate_faces, &face_count,
|
||||
nonmanifold_vertices, 5, NULL) != 1 || face_count != 4)
|
||||
return 7;
|
||||
|
||||
int invalid_faces[] = {0, 1, 2, 3, 3, 99};
|
||||
const int invalid_original[] = {0, 1, 2, 3, 3, 99};
|
||||
face_count = 2;
|
||||
if (closed_mesh_component_filter(invalid_faces, &face_count,
|
||||
nonmanifold_vertices, 5, NULL) != 0 || face_count != 2 ||
|
||||
!std::equal(invalid_faces, invalid_faces + 6, invalid_original))
|
||||
return 8;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -861,4 +861,3 @@ int ON_Brep_CDT_VList(
|
||||
// c-file-style: "stroustrup"
|
||||
// End:
|
||||
// ex: shiftwidth=4 tabstop=8
|
||||
|
||||
|
||||
@@ -1675,4 +1675,3 @@ finalize_rtrees(struct ON_Brep_CDT_State *s_cdt)
|
||||
// c-file-style: "stroustrup"
|
||||
// End:
|
||||
// ex: shiftwidth=4 tabstop=8
|
||||
|
||||
|
||||
+125
-5
@@ -29,6 +29,7 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <stack>
|
||||
@@ -57,6 +58,8 @@
|
||||
#include "assert.h"
|
||||
|
||||
#include "vmath.h"
|
||||
#include "bu/parallel.h"
|
||||
#include "bu/env.h"
|
||||
|
||||
#include "bu/cv.h"
|
||||
#include "bu/opt.h"
|
||||
@@ -290,6 +293,55 @@ getEdgePoints(ON_BrepTrim &trim,
|
||||
|
||||
}
|
||||
|
||||
/* Boundary points shared by two faces must be taken from their common
|
||||
* 3-D BREP edge, not independently from the two surface lifts. Valid
|
||||
* trims may differ from that edge by their asserted edge tolerance; using
|
||||
* the lifts directly opens a visible crack in the fast shaded-display
|
||||
* mesh. A trim and its edge are not required to use identical parameter
|
||||
* domains, so verify the normalized correspondence at every sample before
|
||||
* changing any point. If it is not valid for the entire trim, leave the
|
||||
* trim on the existing general path rather than producing a mixture of
|
||||
* surface and edge points. */
|
||||
const ON_BrepEdge *edge = trim.Edge();
|
||||
const ON_Curve *edge_curve = edge ? edge->EdgeCurveOf() : NULL;
|
||||
const ON_Interval edge_domain = edge_curve ? edge_curve->Domain() :
|
||||
ON_Interval::EmptyInterval;
|
||||
const double edge_tolerance = edge ? std::max(tol->dist,
|
||||
edge->m_tolerance) : tol->dist;
|
||||
|
||||
bool edge_parameterization_matches = edge_curve && range.IsIncreasing() &&
|
||||
edge_domain.IsIncreasing();
|
||||
if (edge_parameterization_matches) {
|
||||
for (std::map<double, BrepTrimPoint *>::iterator sample =
|
||||
param_points->begin(); sample != param_points->end(); ++sample) {
|
||||
BrepTrimPoint *point = sample->second;
|
||||
if (!point || !point->p3d) {
|
||||
edge_parameterization_matches = false;
|
||||
break;
|
||||
}
|
||||
double fraction = range.NormalizedParameterAt(point->t);
|
||||
if (trim.m_bRev3d)
|
||||
fraction = 1.0 - fraction;
|
||||
const ON_3dPoint edge_point = edge_curve->PointAt(
|
||||
edge_domain.ParameterAt(fraction));
|
||||
if (!edge_point.IsValid() ||
|
||||
edge_point.DistanceTo(*point->p3d) > edge_tolerance) {
|
||||
edge_parameterization_matches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (edge_parameterization_matches) {
|
||||
for (std::map<double, BrepTrimPoint *>::iterator sample =
|
||||
param_points->begin(); sample != param_points->end(); ++sample) {
|
||||
BrepTrimPoint *point = sample->second;
|
||||
double fraction = range.NormalizedParameterAt(point->t);
|
||||
if (trim.m_bRev3d)
|
||||
fraction = 1.0 - fraction;
|
||||
*point->p3d = edge_curve->PointAt(edge_domain.ParameterAt(fraction));
|
||||
}
|
||||
}
|
||||
|
||||
return param_points;
|
||||
}
|
||||
|
||||
@@ -2298,6 +2350,55 @@ bg_CDT(std::vector<int> &faces, std::vector<fastf_t> &pnt_norms, std::vector<fas
|
||||
}
|
||||
|
||||
|
||||
struct fast_cdt_face_result {
|
||||
std::vector<int> faces;
|
||||
std::vector<fastf_t> norms;
|
||||
std::vector<fastf_t> pnts;
|
||||
};
|
||||
|
||||
struct fast_cdt_parallel_state {
|
||||
const ON_Brep *brep;
|
||||
const struct bg_tess_tol *ttol;
|
||||
const struct bn_tol *tol;
|
||||
std::vector<fast_cdt_face_result> *results;
|
||||
std::atomic<int> next_face;
|
||||
std::atomic<size_t> result_bytes;
|
||||
std::atomic<bool> over_budget;
|
||||
size_t result_budget;
|
||||
};
|
||||
|
||||
static void
|
||||
fast_cdt_face_worker(int UNUSED(cpu), void *data)
|
||||
{
|
||||
fast_cdt_parallel_state *state = (fast_cdt_parallel_state *)data;
|
||||
const int face_cnt = state->brep->m_F.Count();
|
||||
|
||||
for (;;) {
|
||||
if (state->over_budget.load())
|
||||
return;
|
||||
const int face_index = state->next_face.fetch_add(1);
|
||||
if (face_index >= face_cnt)
|
||||
return;
|
||||
|
||||
const ON_BrepFace& face = state->brep->m_F[face_index];
|
||||
bg_CDT((*state->results)[face_index].faces,
|
||||
(*state->results)[face_index].norms,
|
||||
(*state->results)[face_index].pnts,
|
||||
face, state->ttol, state->tol);
|
||||
|
||||
const fast_cdt_face_result &result = (*state->results)[face_index];
|
||||
const size_t result_bytes = result.faces.size() * sizeof(int) +
|
||||
(result.norms.size() + result.pnts.size()) * sizeof(fastf_t);
|
||||
if (state->result_bytes.fetch_add(result_bytes) + result_bytes > state->result_budget) {
|
||||
state->over_budget = true;
|
||||
(*state->results)[face_index].faces.clear();
|
||||
(*state->results)[face_index].norms.clear();
|
||||
(*state->results)[face_index].pnts.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -2326,10 +2427,30 @@ brep_cdt_fast(int **faces, int *face_cnt, vect_t **pnt_norms, point_t **pnts, in
|
||||
std::vector<fastf_t> all_pnts;
|
||||
|
||||
if (index == -1) {
|
||||
for (index = 0; index < brep->m_F.Count(); index++) {
|
||||
const ON_BrepFace& face = brep->m_F[index];
|
||||
bg_CDT(all_faces, all_norms, all_pnts, face, ttol, tol);
|
||||
}
|
||||
/* Each face produces an independent set of indices and points. Keep
|
||||
* those results separate while workers run, then concatenate them in
|
||||
* face order. This avoids locking the hot CDT path and keeps output
|
||||
* stable across runs. The BRep trim-user cleanup below remains serial. */
|
||||
std::vector<fast_cdt_face_result> face_results(brep->m_F.Count());
|
||||
ssize_t available = bu_mem(BU_MEM_AVAIL, NULL);
|
||||
size_t result_budget = (available > 0) ? (size_t)available / 4 :
|
||||
(size_t)512 * 1024 * 1024;
|
||||
fast_cdt_parallel_state state = {
|
||||
brep, ttol, tol, &face_results, 0, 0, false, result_budget
|
||||
};
|
||||
bu_parallel(fast_cdt_face_worker, 0, &state);
|
||||
if (state.over_budget.load()) {
|
||||
bu_log("brep_cdt_fast: tessellation result exceeded memory budget (%zu bytes)\n", result_budget);
|
||||
} else {
|
||||
for (size_t fi = 0; fi < face_results.size(); fi++) {
|
||||
const size_t point_offset = all_pnts.size() / 3;
|
||||
for (size_t i = 0; i < face_results[fi].faces.size(); i++) {
|
||||
all_faces.push_back((int)point_offset + face_results[fi].faces[i]);
|
||||
}
|
||||
all_norms.insert(all_norms.end(), face_results[fi].norms.begin(), face_results[fi].norms.end());
|
||||
all_pnts.insert(all_pnts.end(), face_results[fi].pnts.begin(), face_results[fi].pnts.end());
|
||||
}
|
||||
}
|
||||
} else if (index < brep->m_F.Count()) {
|
||||
const ON_BrepFaceArray& brep_faces = brep->m_F;
|
||||
if (index < brep_faces.Count()) {
|
||||
@@ -2387,4 +2508,3 @@ brep_cdt_fast(int **faces, int *face_cnt, vect_t **pnt_norms, point_t **pnts, in
|
||||
// c-file-style: "stroustrup"
|
||||
// End:
|
||||
// ex: shiftwidth=4 tabstop=8
|
||||
|
||||
|
||||
+297
-20
@@ -490,6 +490,27 @@ cpolygon_t::add_ordered_edge(const struct edge2d_t &e)
|
||||
return nedge;
|
||||
}
|
||||
|
||||
/* Return a stable boundary-loop starting edge. poly is a pointer set, so
|
||||
* dereferencing begin() makes traversal order depend on allocator layout.
|
||||
* Geometry algorithms which walk next/prev from that arbitrary entry can then
|
||||
* assign samples or exercise bounded refinements in a different order. */
|
||||
cpolyedge_t *
|
||||
cpolygon_t::first_edge() const
|
||||
{
|
||||
cpolyedge_t *first = NULL;
|
||||
for (std::set<cpolyedge_t *>::const_iterator edge = poly.begin();
|
||||
edge != poly.end(); ++edge) {
|
||||
cpolyedge_t *candidate = *edge;
|
||||
if (!candidate)
|
||||
continue;
|
||||
if (!first || candidate->v2d[0] < first->v2d[0] ||
|
||||
(candidate->v2d[0] == first->v2d[0] &&
|
||||
candidate->v2d[1] < first->v2d[1]))
|
||||
first = candidate;
|
||||
}
|
||||
return first;
|
||||
}
|
||||
|
||||
void
|
||||
cpolygon_t::remove_ordered_edge(const struct edge2d_t &e)
|
||||
{
|
||||
@@ -1150,7 +1171,17 @@ cpolygon_t::cdt(triangulation_t ttype)
|
||||
int *opoly = (int *)bu_calloc(poly.size()+1, sizeof(int), "polygon points");
|
||||
|
||||
size_t vcnt = 1;
|
||||
cpolyedge_t *pe = (*poly.begin());
|
||||
/* bg_nested_poly_triangulate may select a different valid diagonal when
|
||||
* the same closed boundary is cyclically rotated. Downstream face-mesh
|
||||
* stitching requires that choice to be independent of allocator layout,
|
||||
* so use the same stable topological start as the LSCM projection. */
|
||||
cpolyedge_t *pe = first_edge();
|
||||
last_cdt_start_vertex = pe ? pe->v2d[0] : -1;
|
||||
if (!pe) {
|
||||
bu_free(opoly, "polygon points");
|
||||
bu_free(bgp_2d, "free libbg 2d points array)");
|
||||
return false;
|
||||
}
|
||||
cpolyedge_t *first = pe;
|
||||
cpolyedge_t *next = pe->next;
|
||||
|
||||
@@ -3354,6 +3385,33 @@ loop_to_bgpoly(cpolygon_t *loop)
|
||||
return opoly;
|
||||
}
|
||||
|
||||
static bool
|
||||
point_on_polygon_boundary(const point2d_t *points, int point_index,
|
||||
const int *polygon, size_t polygon_point_count, double tolerance_sq)
|
||||
{
|
||||
if (!points || point_index < 0 || !polygon || polygon_point_count < 2)
|
||||
return false;
|
||||
const double px = points[point_index][X];
|
||||
const double py = points[point_index][Y];
|
||||
for (size_t edge = 0; edge + 1 < polygon_point_count; ++edge) {
|
||||
const int first = polygon[edge];
|
||||
const int second = polygon[edge + 1];
|
||||
const double ax = points[first][X];
|
||||
const double ay = points[first][Y];
|
||||
const double dx = points[second][X] - ax;
|
||||
const double dy = points[second][Y] - ay;
|
||||
const double length_sq = dx * dx + dy * dy;
|
||||
double parameter = length_sq > DBL_EPSILON ?
|
||||
((px - ax) * dx + (py - ay) * dy) / length_sq : 0.0;
|
||||
parameter = std::max(0.0, std::min(1.0, parameter));
|
||||
const double ex = px - (ax + parameter * dx);
|
||||
const double ey = py - (ay + parameter * dy);
|
||||
if (ex * ex + ey * ey <= tolerance_sq)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
cdt_mesh_t::cdt()
|
||||
{
|
||||
@@ -3440,8 +3498,10 @@ cdt_mesh_t::cdt()
|
||||
}
|
||||
}
|
||||
|
||||
// Build Steiner array, filtering out any points that fall inside a hole.
|
||||
// Sampled interior points inside trimmed-away hole regions confuse detria.
|
||||
// Build the Steiner array, filtering points in trimmed-away holes and
|
||||
// points numerically on a constrained boundary. Detria rejects a
|
||||
// Steiner point on a constrained segment; the boundary already supplies
|
||||
// the exact sample, so retaining that duplicate adds no geometry.
|
||||
// Pre-build per-hole 2D polygon arrays once (reused for each Steiner point test).
|
||||
std::vector<std::vector<double>> hole_polys_flat; // pairs of (x,y) stored flat
|
||||
std::vector<size_t> hole_polys_npts;
|
||||
@@ -3460,8 +3520,24 @@ cdt_mesh_t::cdt()
|
||||
|
||||
std::vector<int> steiner_vec;
|
||||
steiner_vec.reserve(m_interior_pnts.size());
|
||||
const double scaled_u_range = (umax - umin) * uscale;
|
||||
const double scaled_v_range = (vmax - vmin) * vscale;
|
||||
const double boundary_scale = std::max(1.0,
|
||||
hypot(scaled_u_range, scaled_v_range));
|
||||
const double boundary_tolerance =
|
||||
sqrt(DBL_EPSILON) * boundary_scale;
|
||||
const double boundary_tolerance_sq =
|
||||
boundary_tolerance * boundary_tolerance;
|
||||
for (auto p_it = m_interior_pnts.begin(); p_it != m_interior_pnts.end(); p_it++) {
|
||||
int idx = (int)*p_it;
|
||||
bool on_boundary = point_on_polygon_boundary(bgp_2d, idx, opoly,
|
||||
outer_loop.poly.size() + 1, boundary_tolerance_sq);
|
||||
for (int hi = 0; hi < holes_cnt && !on_boundary; ++hi) {
|
||||
on_boundary = point_on_polygon_boundary(bgp_2d, idx,
|
||||
holes_array[hi], holes_npts[hi], boundary_tolerance_sq);
|
||||
}
|
||||
if (on_boundary)
|
||||
continue;
|
||||
bool in_hole = false;
|
||||
for (int hi = 0; hi < holes_cnt && !in_hole; hi++) {
|
||||
point2d_t test_pnt;
|
||||
@@ -3824,7 +3900,77 @@ cdt_mesh_t::repair()
|
||||
|
||||
// Now that the out-and-out problem triangles have been handled,
|
||||
// remesh near singularities to try and produce more reasonable
|
||||
// triangles.
|
||||
// triangles. This is a quality refinement, not a topology repair. Keep
|
||||
// it transactional: process_seed_tri may have replaced several patches
|
||||
// before a later singular seed proves unmeshable. Returning with those
|
||||
// partial mutations used to leak isolated triangles into an otherwise
|
||||
// valid face mesh (notably the NIST MBE PMI 6 spherical cap).
|
||||
boundary_edges_stale = true;
|
||||
boundary_edges_update();
|
||||
const bool pre_singularity_mesh_valid = problem_edges.empty();
|
||||
std::vector<triangle_t> pre_singularity_triangle_store;
|
||||
std::vector<size_t> pre_singularity_active_triangles;
|
||||
decltype(v2edges) pre_singularity_v2edges;
|
||||
decltype(v2tris) pre_singularity_v2tris;
|
||||
decltype(edges2tris) pre_singularity_edges2tris;
|
||||
decltype(uedges2tris) pre_singularity_uedges2tris;
|
||||
decltype(boundary_edges) pre_singularity_boundary_edges;
|
||||
decltype(problem_edges) pre_singularity_problem_edges;
|
||||
if (pre_singularity_mesh_valid && has_singularities) {
|
||||
/* Preserve the complete indexed mesh state rather than rebuilding it
|
||||
* through tri_add. Coincident singularity triangles may intentionally
|
||||
* have the same three 3-D vertex indices but different orientations;
|
||||
* tri_add's duplicate filter would discard one and make rollback itself
|
||||
* non-transactional. */
|
||||
pre_singularity_triangle_store = tris_vect;
|
||||
pre_singularity_v2edges = v2edges;
|
||||
pre_singularity_v2tris = v2tris;
|
||||
pre_singularity_edges2tris = edges2tris;
|
||||
pre_singularity_uedges2tris = uedges2tris;
|
||||
pre_singularity_boundary_edges = boundary_edges;
|
||||
pre_singularity_problem_edges = problem_edges;
|
||||
RTree<size_t, double, 3>::Iterator snapshot_it;
|
||||
tris_tree.GetFirst(snapshot_it);
|
||||
while (!snapshot_it.IsNull()) {
|
||||
pre_singularity_active_triangles.push_back(*snapshot_it);
|
||||
++snapshot_it;
|
||||
}
|
||||
}
|
||||
const auto restore_pre_singularity_mesh = [&]() {
|
||||
if (!pre_singularity_mesh_valid ||
|
||||
pre_singularity_triangle_store.empty() ||
|
||||
pre_singularity_active_triangles.empty())
|
||||
return false;
|
||||
tris_vect = pre_singularity_triangle_store;
|
||||
tris_tree.RemoveAll();
|
||||
for (size_t triangle_index : pre_singularity_active_triangles) {
|
||||
if (triangle_index >= tris_vect.size()) return false;
|
||||
triangle_t &triangle = tris_vect[triangle_index];
|
||||
triangle.m = this;
|
||||
ON_3dPoint *point = pnts[triangle.v[0]];
|
||||
ON_BoundingBox bounds(*point, *point);
|
||||
for (int vertex = 1; vertex < 3; ++vertex) {
|
||||
point = pnts[triangle.v[vertex]];
|
||||
bounds.Set(*point, true);
|
||||
}
|
||||
const double minimum[3] = {bounds.Min().x, bounds.Min().y,
|
||||
bounds.Min().z};
|
||||
const double maximum[3] = {bounds.Max().x, bounds.Max().y,
|
||||
bounds.Max().z};
|
||||
tris_tree.Insert(minimum, maximum, triangle_index);
|
||||
}
|
||||
v2edges = pre_singularity_v2edges;
|
||||
v2tris = pre_singularity_v2tris;
|
||||
edges2tris = pre_singularity_edges2tris;
|
||||
uedges2tris = pre_singularity_uedges2tris;
|
||||
boundary_edges = pre_singularity_boundary_edges;
|
||||
problem_edges = pre_singularity_problem_edges;
|
||||
seed_tris.clear();
|
||||
new_tris.clear();
|
||||
boundary_edges_stale = false;
|
||||
bounding_box_stale = true;
|
||||
return true;
|
||||
};
|
||||
|
||||
if (has_singularities) {
|
||||
std::vector<triangle_t> s_tris = this->singularity_triangles();
|
||||
@@ -3839,6 +3985,11 @@ cdt_mesh_t::repair()
|
||||
bool pseed = process_seed_tri(seed, false, deg, NULL);
|
||||
|
||||
if (!pseed || seed_tris.size() >= st_size) {
|
||||
if (restore_pre_singularity_mesh()) {
|
||||
bu_log("Face %d: retained the valid pre-refinement mesh after singularity quality remeshing made no progress\n",
|
||||
f_id);
|
||||
return true;
|
||||
}
|
||||
std::cerr << f_id << ": Error - failed to process refinement seed triangle!\n";
|
||||
struct bu_vls fname = BU_VLS_INIT_ZERO;
|
||||
bu_vls_sprintf(&fname, "%d-failed_seed.plot3", f_id);
|
||||
@@ -3849,7 +4000,6 @@ cdt_mesh_t::repair()
|
||||
serialize(bu_vls_cstr(&fname));
|
||||
bu_vls_free(&fname);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
st_size = seed_tris.size();
|
||||
@@ -4597,13 +4747,13 @@ cdt_mesh_t::serialize(const char *fname)
|
||||
sfile << m_it->first << "," << m_it->second << "\n";
|
||||
}
|
||||
|
||||
sfile << "TRIANGLES_VECT" << tris_vect.size() << "\n";
|
||||
sfile << "TRIANGLES_VECT " << tris_vect.size() << "\n";
|
||||
std::vector<triangle_t>::iterator t_it;
|
||||
for (t_it = tris_vect.begin(); t_it != tris_vect.end(); t_it++) {
|
||||
sfile << (*t_it).v[0] << "," << (*t_it).v[1] << "," << (*t_it).v[2] << "," << (*t_it).ind << "\n";
|
||||
}
|
||||
|
||||
sfile << "TRIANGLES_TREE" << tris_tree.Count() << "\n";
|
||||
sfile << "TRIANGLES_TREE " << tris_tree.Count() << "\n";
|
||||
RTree<size_t, double, 3>::Iterator tree_it;
|
||||
size_t t_ind;
|
||||
triangle_t tri;
|
||||
@@ -4679,9 +4829,11 @@ cdt_mesh_t::deserialize(const char *fname)
|
||||
if (std::getline(sfile,switch_line)) {
|
||||
if (switch_line == std::string("V1")) {
|
||||
version = 1;
|
||||
} else if (switch_line == std::string("V2")) {
|
||||
version = 2;
|
||||
}
|
||||
}
|
||||
if (version < 1 || version > 1) {
|
||||
if (version < 1 || version > 2) {
|
||||
std::cerr << "Invalid deserialization file - format version " << switch_line << "\n";
|
||||
return false;
|
||||
}
|
||||
@@ -4716,11 +4868,27 @@ cdt_mesh_t::deserialize(const char *fname)
|
||||
problem_edges.clear();
|
||||
|
||||
while (std::getline(sfile,switch_line)) {
|
||||
std::cout << switch_line << "\n";
|
||||
size_t spos = switch_line.find_first_of(' ');
|
||||
std::string dtype = switch_line.substr(0, spos);
|
||||
switch_line.erase(0, spos+1);
|
||||
long lcnt = std::stol(switch_line);
|
||||
std::string dtype;
|
||||
std::string count_text;
|
||||
if (spos != std::string::npos) {
|
||||
dtype = switch_line.substr(0, spos);
|
||||
count_text = switch_line.substr(spos + 1);
|
||||
} else if (switch_line.compare(0, 14, "TRIANGLES_VECT") == 0) {
|
||||
/* V2 snapshots written before the delimiter fix concatenated these
|
||||
* two record names and their counts. Accept those diagnostics so
|
||||
* existing failure captures remain replayable. */
|
||||
dtype = "TRIANGLES_VECT";
|
||||
count_text = switch_line.substr(14);
|
||||
} else if (switch_line.compare(0, 14, "TRIANGLES_TREE") == 0) {
|
||||
dtype = "TRIANGLES_TREE";
|
||||
count_text = switch_line.substr(14);
|
||||
} else {
|
||||
std::cerr << "Malformed serialization record: " << switch_line
|
||||
<< "\nSerialization import failed.\n";
|
||||
return false;
|
||||
}
|
||||
long lcnt = std::stol(count_text);
|
||||
|
||||
if (dtype == std::string("POINTS")) {
|
||||
for (long i = 0; i < lcnt; i++) {
|
||||
@@ -4781,7 +4949,8 @@ cdt_mesh_t::deserialize(const char *fname)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dtype == std::string("TRIANGLES_VECT")) {
|
||||
if (dtype == std::string("TRIANGLES") ||
|
||||
dtype == std::string("TRIANGLES_VECT")) {
|
||||
for (long i = 0; i < lcnt; i++) {
|
||||
std::string tline;
|
||||
std::getline(sfile,tline);
|
||||
@@ -4796,9 +4965,17 @@ cdt_mesh_t::deserialize(const char *fname)
|
||||
long v2 = std::stol(v2str);
|
||||
long v3 = std::stol(v3str);
|
||||
triangle_t tri(v1, v2, v3);
|
||||
// The tree is loaded separately - just do the basic population
|
||||
tri.ind = tris_vect.size();
|
||||
tris_vect.push_back(tri);
|
||||
if (dtype == std::string("TRIANGLES")) {
|
||||
/* V1 stored only active triangles. Rebuild the spatial index and
|
||||
* adjacency maps through the normal insertion path. */
|
||||
tri.m = this;
|
||||
tri_add(tri);
|
||||
} else {
|
||||
/* V2 stores inactive vector entries as well; its following tree
|
||||
* record identifies and indexes the active subset. */
|
||||
tri.ind = tris_vect.size();
|
||||
tris_vect.push_back(tri);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -5285,10 +5462,17 @@ cdt_mesh_t::lscm_reproject(cpolygon_t *polygon)
|
||||
// ── Step 1: Walk the boundary loop ───────────────────────────────────────
|
||||
// Collect exactly poly.size() unique boundary vertices (one per edge,
|
||||
// using each edge's start vertex) so LSCMParameterization receives N
|
||||
// distinct vertices without a closing repeat.
|
||||
// distinct vertices without a closing repeat. polygon->poly is a set of
|
||||
// pointers, so its begin() depends on heap layout. The choice of starting
|
||||
// edge rotates the convex LSCM boundary and can change the triangulation
|
||||
// of difficult patches. Choose the lexicographically first topological
|
||||
// edge instead, making the result independent of allocation order and
|
||||
// unrelated inputs such as the requested output object name.
|
||||
std::vector<int32_t> bnd_loop;
|
||||
{
|
||||
cpolyedge_t *pe = *polygon->poly.begin();
|
||||
cpolyedge_t *pe = polygon->first_edge();
|
||||
if (!pe)
|
||||
return false;
|
||||
cpolyedge_t *cur = pe;
|
||||
do {
|
||||
bnd_loop.push_back((int32_t)cur->v2d[0]);
|
||||
@@ -6172,6 +6356,100 @@ void cdt_mesh_t::polygon_print_3d(cpolygon_t *polygon)
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
/* Exercise the allocator-independent polygon start used by the actual CDT
|
||||
* call. A square is deliberately co-circular, so rotating its boundary input
|
||||
* can select the opposite valid diagonal unless the triangulator receives a
|
||||
* stable starting vertex. Keep this compact guard here because cpolygon_t is
|
||||
* an internal type whose symbols are not exported from libbrep. */
|
||||
int
|
||||
cdt_test_boundary_start(void)
|
||||
{
|
||||
const auto initialize_regular_polygon = [](cpolygon_t &polygon,
|
||||
int point_count, int first_allocated_edge) {
|
||||
for (int point = 0; point < point_count; ++point) {
|
||||
const double angle = 2.0 * ON_PI * point / point_count;
|
||||
ON_2dPoint p(cos(angle), sin(angle));
|
||||
polygon.add_point(p, point);
|
||||
}
|
||||
for (int offset = 0; offset < point_count; ++offset) {
|
||||
const int start = (first_allocated_edge + offset) % point_count;
|
||||
edge2d_t edge(start, (start + 1) % point_count);
|
||||
polygon.add_ordered_edge(edge);
|
||||
}
|
||||
};
|
||||
const auto canonical_triangles = [](const cpolygon_t &polygon) {
|
||||
std::set<std::vector<long> > result;
|
||||
for (std::set<triangle_t>::const_iterator triangle =
|
||||
polygon.tris.begin(); triangle != polygon.tris.end(); ++triangle) {
|
||||
std::vector<long> vertices;
|
||||
vertices.push_back(triangle->v[0]);
|
||||
vertices.push_back(triangle->v[1]);
|
||||
vertices.push_back(triangle->v[2]);
|
||||
std::sort(vertices.begin(), vertices.end());
|
||||
result.insert(vertices);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const auto release_edges = [](cpolygon_t &polygon) {
|
||||
for (std::set<cpolyedge_t *>::iterator edge = polygon.poly.begin();
|
||||
edge != polygon.poly.end(); ++edge)
|
||||
delete *edge;
|
||||
polygon.poly.clear();
|
||||
};
|
||||
|
||||
for (int point_count = 4; point_count <= 12; ++point_count) {
|
||||
for (int first_allocated_edge = 1;
|
||||
first_allocated_edge < point_count; ++first_allocated_edge) {
|
||||
cpolygon_t edge_zero_first;
|
||||
cpolygon_t rotated_first;
|
||||
initialize_regular_polygon(edge_zero_first, point_count, 0);
|
||||
initialize_regular_polygon(rotated_first, point_count,
|
||||
first_allocated_edge);
|
||||
for (cpolyedge_t *edge : edge_zero_first.poly) {
|
||||
if (!edge || edge->trim_ind != -1 || edge->loop_type != 0 ||
|
||||
edge->defines_spnt || edge->split_status != 0 ||
|
||||
edge->eseg != NULL) {
|
||||
release_edges(edge_zero_first);
|
||||
release_edges(rotated_first);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
const bool valid = edge_zero_first.cdt() && rotated_first.cdt();
|
||||
const std::set<std::vector<long> > first =
|
||||
canonical_triangles(edge_zero_first);
|
||||
const std::set<std::vector<long> > rotated =
|
||||
canonical_triangles(rotated_first);
|
||||
release_edges(edge_zero_first);
|
||||
release_edges(rotated_first);
|
||||
if (!valid || edge_zero_first.last_cdt_start_vertex != 0 ||
|
||||
rotated_first.last_cdt_start_vertex != 0 ||
|
||||
first.size() != static_cast<size_t>(point_count - 2) ||
|
||||
first != rotated)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
cdt_test_boundary_steiner_filter(void)
|
||||
{
|
||||
point2d_t points[6] = {
|
||||
{0.0, 0.0}, {2.0, 0.0}, {2.0, 2.0}, {0.0, 2.0},
|
||||
{1.0, 0.0}, {1.0, 1.0}
|
||||
};
|
||||
const int polygon[] = {0, 1, 2, 3, 0};
|
||||
const double tolerance = sqrt(DBL_EPSILON) * sqrt(8.0);
|
||||
if (!point_on_polygon_boundary(points, 4, polygon, 5,
|
||||
tolerance * tolerance))
|
||||
return 1;
|
||||
if (point_on_polygon_boundary(points, 5, polygon, 5,
|
||||
tolerance * tolerance))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// PImpl exposure of some mesh operations for use in tests
|
||||
struct cdt_bmesh_impl {
|
||||
cdt_mesh_t fmesh;
|
||||
@@ -6199,8 +6477,7 @@ cdt_bmesh_deserialize(const char *fname, struct cdt_bmesh *m)
|
||||
{
|
||||
if (!fname || !m) return -1;
|
||||
if (!bu_file_exists(fname, NULL)) return -1;
|
||||
m->i->fmesh.deserialize(fname);
|
||||
return 0;
|
||||
return m->i->fmesh.deserialize(fname) ? 0 : -1;
|
||||
}
|
||||
|
||||
int
|
||||
|
||||
@@ -441,7 +441,15 @@ class cpolyedge_t
|
||||
polygon = NULL;
|
||||
prev = NULL;
|
||||
next = NULL;
|
||||
trim_ind = -1;
|
||||
loop_type = 0;
|
||||
defines_spnt = false;
|
||||
spnt = ON_2dPoint::UnsetPoint;
|
||||
trim_start = 0.0;
|
||||
trim_end = 0.0;
|
||||
split_status = 0;
|
||||
v1_dist = 0.0;
|
||||
v2_dist = 0.0;
|
||||
eseg = NULL;
|
||||
};
|
||||
|
||||
@@ -453,7 +461,15 @@ class cpolyedge_t
|
||||
polygon = NULL;
|
||||
prev = NULL;
|
||||
next = NULL;
|
||||
trim_ind = -1;
|
||||
loop_type = 0;
|
||||
defines_spnt = false;
|
||||
spnt = ON_2dPoint::UnsetPoint;
|
||||
trim_start = 0.0;
|
||||
trim_end = 0.0;
|
||||
split_status = 0;
|
||||
v1_dist = 0.0;
|
||||
v2_dist = 0.0;
|
||||
eseg = NULL;
|
||||
};
|
||||
|
||||
@@ -506,6 +522,7 @@ class cpolygon_t
|
||||
cpolyedge_t *add_edge(const struct uedge2d_t &e);
|
||||
void remove_edge(const struct uedge2d_t &e);
|
||||
std::set<cpolyedge_t *> replace_edges(std::set<uedge_t> &new_edges, std::set<uedge_t> &old_edges);
|
||||
cpolyedge_t *first_edge() const;
|
||||
|
||||
/* Means to update the point array if we're incrementally building. orig_index should
|
||||
* identify the same point in the parent's index, so cdt() knows what triangles to
|
||||
@@ -561,6 +578,7 @@ class cpolygon_t
|
||||
std::set<triangle_t> visited_triangles;
|
||||
std::set<uedge2d_t> active_edges;
|
||||
std::set<uedge2d_t> self_isect_edges;
|
||||
long last_cdt_start_vertex = -1;
|
||||
ON_Plane tplane;
|
||||
ON_Plane fit_plane;
|
||||
ON_3dVector pdir;
|
||||
|
||||
@@ -1050,4 +1050,3 @@ GetInteriorPoints(struct ON_Brep_CDT_State *s_cdt, int face_index)
|
||||
// c-file-style: "stroustrup"
|
||||
// End:
|
||||
// ex: shiftwidth=4 tabstop=8
|
||||
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
/* C O B B . C P P
|
||||
* BRL-CAD
|
||||
*
|
||||
* Copyright (c) 2013-2026 United States Government as represented by
|
||||
* the U.S. Army Research Laboratory.
|
||||
*
|
||||
* Based off of code from Ayam:
|
||||
*
|
||||
* This software is copyrighted (c) 1998-2012 by
|
||||
* Randolf Schultz (randolf.schultz@gmail.com).
|
||||
* All rights reserved.
|
||||
*
|
||||
* The author hereby grants permission to use, copy, modify, distribute,
|
||||
* and license this software and its documentation for any purpose, provided
|
||||
* that existing copyright notices are retained in all copies and that this
|
||||
* notice is included verbatim in any distributions. No written agreement,
|
||||
* license, or royalty fee is required for any of the authorized uses.
|
||||
* Modifications to this software may be copyrighted by their authors
|
||||
* and need not follow the licensing terms described here, provided that
|
||||
* the new terms are clearly indicated on the first page of each file where
|
||||
* they apply.
|
||||
*
|
||||
* IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY
|
||||
* FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
|
||||
* DERIVATIVES THEREOF, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
|
||||
* IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE
|
||||
* NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
|
||||
* MODIFICATIONS.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
/** @file cobb.cpp
|
||||
*
|
||||
* J. E. Cobb's six-patch rational Bezier sphere, with both the historical
|
||||
* independent-face topology and a shared solid topology for seam tests.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
#include "brep/cobb.h"
|
||||
#include "opennurbs.h"
|
||||
|
||||
|
||||
ON_BezierSurface *
|
||||
ON_Brep_CobbSphereFace(double rotation_x, double rotation_z)
|
||||
{
|
||||
const double t = sqrt(3.0);
|
||||
const double d = sqrt(2.0);
|
||||
const double s = sqrt(6.0);
|
||||
const double cvs[5][5][4] = {
|
||||
{
|
||||
{4.0*(1.0-t), 4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)},
|
||||
{-d, d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
|
||||
{0.0, 4.0*(1.0-2.0*t)/3.0, 4.0*(1.0-2.0*t)/3.0,
|
||||
4.0*(5.0-t)/3.0},
|
||||
{d, d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
|
||||
{4.0*(t-1.0), 4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)}
|
||||
},
|
||||
{
|
||||
{d*(t-4.0), -d, d*(t-4.0), d*(3.0*t-2.0)},
|
||||
{(2.0-3.0*t)/2.0, (2.0-3.0*t)/2.0, -(t+6.0)/2.0,
|
||||
(t+6.0)/2.0},
|
||||
{0.0, d*(2.0*t-7.0)/3.0, -5.0*s/3.0, d*(t+6.0)/3.0},
|
||||
{(3.0*t-2.0)/2.0, (2.0-3.0*t)/2.0, -(t+6.0)/2.0,
|
||||
(t+6.0)/2.0},
|
||||
{d*(4.0-t), -d, d*(t-4.0), d*(3.0*t-2.0)}
|
||||
},
|
||||
{
|
||||
{4.0*(1.0-2.0*t)/3.0, 0.0, 4.0*(1.0-2.0*t)/3.0,
|
||||
4.0*(5.0-t)/3.0},
|
||||
{d*(2.0*t-7.0)/3.0, 0.0, -5.0*s/3.0, d*(t+6.0)/3.0},
|
||||
{0.0, 0.0, 4.0*(t-5.0)/3.0, 4.0*(5.0*t-1.0)/9.0},
|
||||
{-d*(2.0*t-7.0)/3.0, 0.0, -5.0*s/3.0, d*(t+6.0)/3.0},
|
||||
{-4.0*(1.0-2.0*t)/3.0, 0.0, 4.0*(1.0-2.0*t)/3.0,
|
||||
4.0*(5.0-t)/3.0}
|
||||
},
|
||||
{
|
||||
{d*(t-4.0), d, d*(t-4.0), d*(3.0*t-2.0)},
|
||||
{(2.0-3.0*t)/2.0, -(2.0-3.0*t)/2.0, -(t+6.0)/2.0,
|
||||
(t+6.0)/2.0},
|
||||
{0.0, -d*(2.0*t-7.0)/3.0, -5.0*s/3.0, d*(t+6.0)/3.0},
|
||||
{(3.0*t-2.0)/2.0, -(2.0-3.0*t)/2.0, -(t+6.0)/2.0,
|
||||
(t+6.0)/2.0},
|
||||
{d*(4.0-t), d, d*(t-4.0), d*(3.0*t-2.0)}
|
||||
},
|
||||
{
|
||||
{4.0*(1.0-t), -4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)},
|
||||
{-d, -d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
|
||||
{0.0, -4.0*(1.0-2.0*t)/3.0, 4.0*(1.0-2.0*t)/3.0,
|
||||
4.0*(5.0-t)/3.0},
|
||||
{d, -d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
|
||||
{4.0*(t-1.0), -4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)}
|
||||
}
|
||||
};
|
||||
|
||||
ON_BezierSurface *surface = new ON_BezierSurface(3, true, 5, 5);
|
||||
surface->ReserveCVCapacity(100);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
for (int j = 0; j < 5; ++j)
|
||||
surface->SetCV(i, j, ON_4dPoint(cvs[i][j][0], cvs[i][j][1],
|
||||
cvs[i][j][2], cvs[i][j][3]));
|
||||
}
|
||||
|
||||
const ON_3dPoint center(0.0, 0.0, 0.0);
|
||||
surface->Rotate(rotation_x * ON_PI / 180.0,
|
||||
ON_3dVector(1.0, 0.0, 0.0), center);
|
||||
surface->Rotate(rotation_z * ON_PI / 180.0,
|
||||
ON_3dVector(0.0, 0.0, 1.0), center);
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
static ON_NurbsSurface *
|
||||
cobb_nurbs_face(double rotation_x, double rotation_z, double radius,
|
||||
const ON_3dPoint &origin)
|
||||
{
|
||||
ON_BezierSurface *bezier = ON_Brep_CobbSphereFace(rotation_x,
|
||||
rotation_z);
|
||||
if (!bezier)
|
||||
return NULL;
|
||||
ON_NurbsSurface *surface = ON_NurbsSurface::New();
|
||||
if (!bezier->GetNurbForm(*surface)) {
|
||||
delete surface;
|
||||
delete bezier;
|
||||
return NULL;
|
||||
}
|
||||
delete bezier;
|
||||
surface->Scale(radius);
|
||||
surface->Translate(ON_3dVector(origin.x, origin.y, origin.z));
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
cobb_face_outward(const ON_Surface *surface, const ON_3dPoint &origin)
|
||||
{
|
||||
const ON_Interval u = surface->Domain(0);
|
||||
const ON_Interval v = surface->Domain(1);
|
||||
ON_3dPoint point;
|
||||
ON_3dVector du;
|
||||
ON_3dVector dv;
|
||||
if (!surface->Ev1Der(u.Mid(), v.Mid(), point, du, dv))
|
||||
return true;
|
||||
return ON_CrossProduct(du, dv) * (point - origin) > 0.0;
|
||||
}
|
||||
|
||||
|
||||
ON_Brep *
|
||||
ON_Brep_CobbSphereUnsewn(double radius, const ON_3dPoint &origin)
|
||||
{
|
||||
if (!(radius > 0.0) || !std::isfinite(radius) || !origin.IsValid())
|
||||
return NULL;
|
||||
|
||||
const double rotations[6][2] = {
|
||||
{0.0, 0.0}, {90.0, 0.0}, {180.0, 0.0},
|
||||
{270.0, 0.0}, {90.0, 90.0}, {90.0, 270.0}
|
||||
};
|
||||
ON_Brep *brep = ON_Brep::New();
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
ON_NurbsSurface *surface = cobb_nurbs_face(rotations[i][0],
|
||||
rotations[i][1], radius, origin);
|
||||
if (!surface) {
|
||||
delete brep;
|
||||
return NULL;
|
||||
}
|
||||
ON_BrepFace *face = brep->NewFace(*surface);
|
||||
if (!face) {
|
||||
delete surface;
|
||||
delete brep;
|
||||
return NULL;
|
||||
}
|
||||
face->m_bRev = !cobb_face_outward(surface, origin);
|
||||
delete surface;
|
||||
}
|
||||
brep->Standardize();
|
||||
brep->Compact();
|
||||
return brep;
|
||||
}
|
||||
|
||||
|
||||
ON_Brep *
|
||||
ON_Brep_CobbSphereSewn(double radius, const ON_3dPoint &origin)
|
||||
{
|
||||
if (!(radius > 0.0) || !std::isfinite(radius) || !origin.IsValid())
|
||||
return NULL;
|
||||
|
||||
const double rotations[6][2] = {
|
||||
{0.0, 0.0}, {90.0, 0.0}, {180.0, 0.0},
|
||||
{270.0, 0.0}, {90.0, 90.0}, {90.0, 270.0}
|
||||
};
|
||||
const double coordinate_scale = std::max(std::max(radius, 1.0),
|
||||
std::max(std::max(fabs(origin.x), fabs(origin.y)), fabs(origin.z)));
|
||||
const double corner_tolerance = std::max(ON_ZERO_TOLERANCE,
|
||||
128.0 * std::numeric_limits<double>::epsilon() * coordinate_scale);
|
||||
ON_Brep *brep = ON_Brep::New();
|
||||
for (int face_index = 0; face_index < 6; ++face_index) {
|
||||
ON_NurbsSurface *surface = cobb_nurbs_face(rotations[face_index][0],
|
||||
rotations[face_index][1], radius, origin);
|
||||
if (!surface) {
|
||||
delete brep;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const ON_Interval u = surface->Domain(0);
|
||||
const ON_Interval v = surface->Domain(1);
|
||||
const ON_3dPoint corners[4] = {
|
||||
surface->PointAt(u.Min(), v.Min()),
|
||||
surface->PointAt(u.Max(), v.Min()),
|
||||
surface->PointAt(u.Max(), v.Max()),
|
||||
surface->PointAt(u.Min(), v.Max())
|
||||
};
|
||||
int vid[4] = {-1, -1, -1, -1};
|
||||
for (int corner = 0; corner < 4; ++corner) {
|
||||
for (int vertex = 0; vertex < brep->m_V.Count(); ++vertex) {
|
||||
if (corners[corner].DistanceTo(brep->m_V[vertex].Point()) <=
|
||||
corner_tolerance) {
|
||||
vid[corner] = vertex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Oriented outer-loop corner pairs for south, east, north, west. */
|
||||
const int side_corners[4][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}};
|
||||
int eid[4] = {-1, -1, -1, -1};
|
||||
bool reversed[4] = {false, false, false, false};
|
||||
for (int side = 0; side < 4; ++side) {
|
||||
const int from = vid[side_corners[side][0]];
|
||||
const int to = vid[side_corners[side][1]];
|
||||
if (from < 0 || to < 0)
|
||||
continue;
|
||||
for (int edge = 0; edge < brep->m_E.Count(); ++edge) {
|
||||
const int edge_from = brep->m_E[edge].m_vi[0];
|
||||
const int edge_to = brep->m_E[edge].m_vi[1];
|
||||
if ((edge_from == from && edge_to == to) ||
|
||||
(edge_from == to && edge_to == from)) {
|
||||
eid[side] = edge;
|
||||
reversed[side] = edge_from != from;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool outward = cobb_face_outward(surface, origin);
|
||||
ON_BrepFace *face = brep->NewFace(surface, vid, eid, reversed);
|
||||
if (!face) {
|
||||
delete surface;
|
||||
delete brep;
|
||||
return NULL;
|
||||
}
|
||||
face->m_bRev = !outward;
|
||||
for (int corner = 0; corner < 4; ++corner)
|
||||
brep->m_V[vid[corner]].m_tolerance = 0.0;
|
||||
for (int side = 0; side < 4; ++side)
|
||||
brep->m_E[eid[side]].m_tolerance = 0.0;
|
||||
}
|
||||
|
||||
for (int trim = 0; trim < brep->m_T.Count(); ++trim) {
|
||||
brep->m_T[trim].m_tolerance[0] = 0.0;
|
||||
brep->m_T[trim].m_tolerance[1] = 0.0;
|
||||
}
|
||||
brep->Standardize();
|
||||
brep->Compact();
|
||||
return brep;
|
||||
}
|
||||
|
||||
|
||||
double
|
||||
ON_Brep_CobbSphereMaxRadialError(const ON_Brep *brep, double radius,
|
||||
const ON_3dPoint &origin, int samples_per_direction)
|
||||
{
|
||||
if (!brep || !(radius > 0.0) || !std::isfinite(radius) ||
|
||||
!origin.IsValid() || samples_per_direction < 2)
|
||||
return std::numeric_limits<double>::infinity();
|
||||
|
||||
double maximum_error = 0.0;
|
||||
for (int si = 0; si < brep->m_S.Count(); ++si) {
|
||||
const ON_Surface *surface = brep->m_S[si];
|
||||
if (!surface)
|
||||
return std::numeric_limits<double>::infinity();
|
||||
const ON_Interval u = surface->Domain(0);
|
||||
const ON_Interval v = surface->Domain(1);
|
||||
for (int i = 0; i < samples_per_direction; ++i) {
|
||||
const double up = u.ParameterAt((double)i /
|
||||
(double)(samples_per_direction - 1));
|
||||
for (int j = 0; j < samples_per_direction; ++j) {
|
||||
const double vp = v.ParameterAt((double)j /
|
||||
(double)(samples_per_direction - 1));
|
||||
const double sample_radius = surface->PointAt(up, vp).
|
||||
DistanceTo(origin);
|
||||
maximum_error = std::max(maximum_error,
|
||||
fabs(sample_radius - radius));
|
||||
}
|
||||
}
|
||||
}
|
||||
return maximum_error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local Variables:
|
||||
* tab-width: 8
|
||||
* mode: C++
|
||||
* c-basic-offset: 4
|
||||
* indent-tabs-mode: t
|
||||
* End:
|
||||
* ex: shiftwidth=4 tabstop=8
|
||||
*/
|
||||
+476
-284
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,29 @@ find_package(Threads REQUIRED)
|
||||
brlcad_addexec(test_brep_ppx ppx.cpp "libbrep" TEST)
|
||||
brlcad_add_test(NAME brep_ppx COMMAND test_brep_ppx)
|
||||
|
||||
brlcad_addexec(test_brep_assembly assembly.cpp "libbrep" TEST)
|
||||
brlcad_add_test(NAME brep_assembly COMMAND test_brep_assembly)
|
||||
# Replay serialized face-mesh failures without rerunning a complete BRep
|
||||
# tessellation. mesh.cpp emits this format at the exact repair failure site,
|
||||
# making it useful both for diagnosis and for compact regression fixtures.
|
||||
brlcad_addexec(test_brep_cdt_mesh brep_cdt_mesh.cpp "libbrep" TEST)
|
||||
brlcad_add_test(
|
||||
NAME brep_cdt_mesh_single_bad_edge
|
||||
COMMAND test_brep_cdt_mesh "${CMAKE_CURRENT_SOURCE_DIR}/single_bad_edge.cdtmesh"
|
||||
)
|
||||
|
||||
# A cyclic rotation of the same co-circular boundary must not change the CDT
|
||||
# diagonal merely because its cpolyedge allocations have different addresses.
|
||||
brlcad_addexec(test_brep_cdt_boundary_start brep_cdt_boundary_start.cpp "libbrep" TEST)
|
||||
brlcad_add_test(NAME brep_cdt_boundary_start COMMAND test_brep_cdt_boundary_start)
|
||||
|
||||
# An interior sample exactly on a constrained face boundary must not be passed
|
||||
# to the triangulator a second time as a Steiner point.
|
||||
brlcad_addexec(test_brep_cdt_boundary_steiner brep_cdt_boundary_steiner.cpp "libbrep" TEST)
|
||||
brlcad_add_test(NAME brep_cdt_boundary_steiner COMMAND test_brep_cdt_boundary_steiner)
|
||||
|
||||
# A closed source solid may not retain redundant open local-remesh islands
|
||||
# after either retained-solid or same-source-face coverage validation.
|
||||
brlcad_addexec(test_brep_cdt_spurious_components brep_cdt_spurious_components.cpp "libbrep" TEST)
|
||||
brlcad_add_test(NAME brep_cdt_spurious_components COMMAND test_brep_cdt_spurious_components)
|
||||
|
||||
# Standalone CDT hole-triangulation tests: verify that bg_nested_poly_triangulate
|
||||
# produces no triangles whose centroid falls inside a hole polygon, for two
|
||||
@@ -26,13 +47,20 @@ endif()
|
||||
brlcad_addexec(test_pullback_context pullback_context.cpp "libbrep;Threads::Threads" TEST)
|
||||
brlcad_add_test(NAME brep_pullback_context COMMAND test_pullback_context)
|
||||
|
||||
# The adaptive SurfaceTree depth budget starts only after closed seams and all
|
||||
# internal NURBS knot boundaries have established well-behaved subdomains.
|
||||
brlcad_addexec(test_surfacetree_knots surfacetree_knots.cpp "libbrep" TEST)
|
||||
brlcad_add_test(NAME brep_surfacetree_knots COMMAND test_surfacetree_knots)
|
||||
|
||||
cmakefiles(
|
||||
CMakeLists.txt
|
||||
ayam_hyperbolid.3dm
|
||||
assembly.cpp
|
||||
brep_cdt_invalid.cpp
|
||||
brep_cdt_hole_face17.c
|
||||
brep_cdt_hole_face27.c
|
||||
brep_cdt_boundary_start.cpp
|
||||
brep_cdt_boundary_steiner.cpp
|
||||
brep_cdt_spurious_components.cpp
|
||||
brep_cdt_mesh.cpp
|
||||
cyl_hole.g
|
||||
cyls_holes.g
|
||||
@@ -43,6 +71,7 @@ cmakefiles(
|
||||
plate_mode_samples_rhino.stp
|
||||
pullback_context.cpp
|
||||
single_bad_edge.cdtmesh
|
||||
surfacetree_knots.cpp
|
||||
test_curve_intersect.cpp
|
||||
test_point_intersect.cpp
|
||||
vsp.stp
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user