Compare commits

..
52 Commits
Author SHA1 Message Date
Clifford Yapp 2923050080 Preserve primitive edits across failed saves and restores
A failed checkpoint could discard the last saved state, and a
failed revert could discard the current primitive. Restoring
selected pipe or NMG geometry could also leave stale handles.

Stage exports and imports before replacing live state, clear
selections and refresh the keypoint after restore, and preserve
SPH and REC types. Consume rejected edit input so a later command
cannot reuse it, and prevent view-update counts from accumulating.

Cover failed saves and restores, repeated revert, selected geometry,
and default and non-default database units.
2026-09-24 14:25:53 -04:00
Clifford Yapp 9dd4023aa5 Prevent rejected primitive edits from changing geometry
Rejected height moves in TGC, CLINE, and EXTRUDE could replace
valid geometry; NMG edge moves could partly change faces while
reporting success. BOT edge splits could leave unused vertices, and
malformed parameter text could partly overwrite seven primitives.

Stage edits before applying them. Reject invalid REC and uniform
scale inputs without changing geometry, and report scale failures.
Cover these paths in default and non-default units, including CRLF
parameter text and NMG selection remapping.
2026-09-24 14:25:53 -04:00
Clifford Yapp d39ca30602 Prevent rejected ARB edits from corrupting geometry
Mouse face drags and several ARB edit helpers could change points or
planes before detecting invalid geometry. Subsequent edits then began
from inconsistent state and could produce unexpected shape changes.

Validate candidate geometry before committing it, restore state on
rejected edits, and cover repeated mouse drags with non-default units.
2026-09-24 14:25:53 -04:00
Clifford Yapp b714d88185 See if we can optionally also write out pre-boolweave segment data for comparison. 2026-09-24 17:45:40 +00:00
Clifford Yapp eff3fea479 Add LIBRT level rtcmp debug printing option 2026-09-24 16:25:34 +00:00
Clifford Yapp 7358e90b54 Keep temporary files binary on Windows
Set binary mode on the temporary file descriptor before wrapping it
in a stream. This prevents newline translation from enlarging DSP
sample files, which otherwise makes DSP edits and raster operation
tests fail on Windows.

Add a byte-for-byte temporary-file regression test covering newline
and DOS EOF bytes.
2026-09-24 14:30:55 +00:00
Clifford Yapp 3c2b0de9d2 Fix unit-sensitive primitive edits and invalid edit reporting
Keep numeric edit values in database-local units while applying
geometry changes in base units. Correct parameter readers and edit
paths that could misplace solids or silently accept invalid changes,
including ARB, ARBN, BOT, pipe, sketch, metaball, and NMG.

Add mm/in operation matrices with exact-result checks across
primitive edits, parameter parsing, and repair paths.
2026-09-24 11:08:20 +00:00
Clifford Yapp a384f838fb Reject invalid BREP control-vertex edits and verify units
Propagate selection and CV edit errors through librt so callers do not
report rejected edits as successful. Validate indices and coordinates
before changing geometry, and mark selection indices as unitless.

Exercise persisted CV moves and sets in an inch-unit GED database,
and verify rejected commands leave stored geometry unchanged.
2026-09-24 02:03:12 +00:00
Clifford Yapp 1053594e9a Fix cursor state and BREP control-vertex mouse edits
Preserve cursor-relative translation knobs for ARB, BOT, CLINE,
extrusion, NMG, and TGC mouse edits, including in non-mm
databases. Prevent BREP control-vertex drags from moving the
whole solid. Report invalid ARB edit indices and geometry
failures to callers.

Add mouse and error-path regressions for the affected primitives.
2026-09-24 01:36:28 +00:00
Clifford Yapp 72cde263cc Fix ARB face rotation errors and endpoint drag state
ARB face rotation changed geometry but reported failure, causing GED
to reject the edit. Return success and let the shared edit path replot.

CLINE and extrusion mouse endpoint drags also reset translation
knob state. Keep endpoint moves separate from translation handling
and cover these workflows in primitive and GED edit tests.
2026-09-24 01:00:50 +00:00
Clifford Yapp b670a19aba Preserve local-unit inputs during primitive editing
Repeated edits in non-mm databases could compound unit conversions,
moving points and faces or resizing primitives unexpectedly. Mouse
coordinates could also be converted a second time.

Convert numeric values into temporary base-unit targets in librt
without rewriting the supplied parameters. Cover translation,
dimension changes, vertex and face moves, picks, and NMG extrusion
with non-mm and repeated-input tests.
2026-09-24 00:37:03 +00:00
Clifford Yapp 2d9fa5c033 Fix non-mm primitive edits and invalid scale handling
Numeric scale lengths now convert once in librt, so edits in
non-mm databases produce the requested geometry and repeated
inputs do not compound conversions. Cover torus, ellipsoid,
superellipsoid, particle, RPC, RHC, ETO, and TGC scaling,
plus TGC endpoint moves.

Reject impossible torus, ETO, and coupled TGC edits without
changing the solid. Add inch-unit, repeat-input, and rejection
coverage in librt and libged.
2026-09-23 23:59:19 +00:00
Clifford Yapp 8be0f0ae85 Fix EPA/EHY length edits and lock in ARB edit behavior
EPA and EHY length edits now convert local units without changing the
input, so repeating an edit cannot apply the conversion again. Invalid
semiaxis requests now fail visibly and leave the primitive unchanged;
empty parameter edits remain no-ops.

Add inch-unit MGED extrusion checks for both direct and interactive
editing, exercise ARB geometry helpers, and run multiline MGED tests
as sourced scripts so failures cannot be skipped.
2026-09-23 23:04:29 +00:00
Clifford Yapp 00ffa4bd80 Fix VOL edits with non-default database units
Convert voxel cell sizes to base units without modifying input
parameters, so repeated edits use the requested size. Keep file
sizes and density thresholds unitless, validate invalid dimensions,
and prevent threshold underflow and file-size overflow.

Cover direct librt edits and persisted GED edits in inch databases.
2026-09-23 22:27:58 +00:00
Clifford Yapp e5e0905d03 Fix pipe editing units, zero dimensions, and failures
Pipe point coordinates and dimensions entered in non-mm databases were
not consistently converted to base units. Some commands reconverted
their input on repeat, while invalid point operations could report
success without changing the pipe.

Convert numeric input once in librt, preserve base-unit mouse points,
and propagate operation failures. Reuse shared pipe point helpers and
allow all-zero pipe dimensions to receive an absolute value. Add
direct and GED tests for inch units, repeated edits, failures, and
database persistence.
2026-09-23 21:55:41 +00:00
Clifford Yapp 10d13793b0 Fix revolve sketch edits, error reporting, and ownership
Changing a revolve's sketch now loads the new geometry and rejects
missing or non-sketch references without persisting a broken object.
Invalid numeric edits report errors instead of appearing to succeed.

Transforms retain the start vector, angle, and sketch. Imported and
prepared objects own independent sketches and names, avoiding stale
geometry, leaks, and dangling references.

Cover inch-database edits, sketch replacement, transform ownership,
and prep/free lifecycles in edit tests.
2026-09-23 21:26:37 +00:00
Clifford Yapp 70d5180438 Fix sketch and extrusion edits and report invalid operations
Honor indexed sketch descriptor inputs and convert vertex and radius
lengths in non-mm databases. Replace extrusion sketch references only
after the new sketch is validated, so failed selections leave the
existing extrusion intact.

Return descriptor edit failures to GED instead of reporting success.
Remove two unimplemented TGC moves from advertised operations and
strengthen persisted sketch, extrusion, HRT, and pipe tests.
2026-09-23 20:55:57 +00:00
Clifford Yapp 50fe626694 Fix NMG edge deletion and strengthen edit operation checks
Deleting an NMG edge could also split another edge when stale edit
parameters were present. Stop after deletion and release temporary
extrusion loops on mode changes and reset.

Exercise NMG selection, traversal, edge edits, and extrusion using
non-mm input. Check that DSP file selection and sample counts are
independent of length units, sharing the filename test callback.
2026-09-23 20:11:14 +00:00
Clifford Yapp ca403c92ed Correct native primitive edits and broaden result coverage
Keep combination edit sessions valid after database writes, move BOT
plate thickness edits into librt, and repair ARS topology changes.
Correct unit-sensitive edits, malformed-input handling, and NMG
imports on unaligned data so geometry edits remain reliable.

Add primitive-operation inventory, outcome and inch-unit tests, and
bounded edit-sequence coverage. Use portable temporary data files
so DSP and EBM tests no longer require /tmp.
2026-09-23 19:49:07 +00:00
Clifford Yapp bab959474f Add Clang version of coverage tracking compilation flags 2026-09-23 19:49:07 +00:00
Clifford Yapp 27b522c155 Updates for OpenCV v5 2026-09-23 16:50:52 +00:00
Clifford Yapp 875231c335 Consolidate librt primitive edit tests into one executable
Run all 34 edit cases through a selectable rt_edit_test binary.
Register each case independently with CTest so failures remain
isolated and previously unregistered tests run routinely.
2026-09-23 16:01:04 +00:00
Clifford Yapp 89810b06ca Correct non-mm primitive edits and expand regression coverage
Keep MGED and gsh edits consistent in non-mm databases by fixing
length and dimensionless parameter handling in librt and libbrep.
Correct DSP absolute cell sizing and inverse transforms, preserve
rational BREP control-vertex weights, and retain NMG session units.

Add non-mm tests for affected primitives and update stale ARB8
descriptor and HRT translation assertions.
2026-09-23 16:01:04 +00:00
Clifford Yapp 90e512b5d6 Fix non-mm units across primitive edit commands
MGED tra moved primitives too far by passing a base-unit target
to handlers expecting local units. The libged edit commands used
by GSH mixed local numeric positions with base-unit keypoints.
This affected tra, absolute and relative translate, and rotation
and scale centers in non-mm databases.

Convert translation targets at the librt edit boundary and parse
numeric positions in base units. Keep scale reference factors
dimensionless and add inch-database regression coverage.
2026-09-23 16:01:04 +00:00
Clifford Yapp 19fe382208 Revert "Add opt-in forward parity shots for solid BoTs"
This was just an experiment - remove.

This reverts commit 90e5ede2b3.
2026-09-23 10:00:05 -04:00
Clifford Yapp 994c56b05f typo, ws 2026-09-23 09:34:21 -04:00
Christopher Sean Morrison db76164ea8 update docs with restored behavior and comparison with bwdiff 2026-09-23 07:36:04 -04:00
Christopher Sean Morrison 58c5ed3681 pixscale and bwscale improved 2026-09-23 07:35:28 -04:00
Christopher Sean Morrison 05bd5c7470 restored pixdiff 2026-09-23 07:34:02 -04:00
Christopher Sean Morrison 2276f4ef8c replaces seek-based scanline loading with a forward rolling buffer so large piped inputs behave like regular files. 2026-09-23 07:32:55 -04:00
Christopher Sean Morrison 188e72eb32 maps the first and last output pixels exactly onto source edges and clamps interpolation neighbors so enlargements retain every corner 2026-09-23 07:32:55 -04:00
Christopher Sean Morrison 03306043ea scaling test 2026-09-23 07:32:55 -04:00
Christopher Sean Morrison 28038fe0ea isolated pixdiff pix test 2026-09-23 07:32:55 -04:00
Christopher Sean Morrison 5b5cb8c538 now bw mode need -b option 2026-09-23 07:32:55 -04:00
Christopher Sean Morrison 1c20d18156 restore previous pixdiff behavior where we apply an ntsc-grayscaling damper on same pixels so differences are accentuated with color 2026-09-23 07:32:55 -04:00
Clifford Yapp 97920fbc90 license info 2026-09-21 22:07:58 -04:00
Clifford Yapp 37fb7d928a Not flipping these to const yet. 2026-09-21 22:06:05 -04:00
Clifford Yapp 2056b359bd Use EQUAL/ZERO 2026-09-21 22:05:39 -04:00
Clifford Yapp b4cf00292a ged_cmd_valid is deprecated, so don't need a test for it. 2026-09-21 20:08:03 -04:00
Clifford Yapp 6748dd37ab libged: restore ged_cmd_valid compatibility wrapper
The 2024 command API migration replaced its implementation with
ged_cmd_exists and ged_cmd_same, but retained the deprecated exported
ged_cmd_valid declaration.  This left source callers compiling against a
symbol that libged did not provide.

Restore the wrapper with its established status mapping and add regression
coverage for valid, alias, mismatch, invalid, and NULL command inputs.
2026-09-21 20:06:52 -04:00
Clifford Yapp ef35c1dca1 fix bu_scan_fastf_t delimiter parsing
Replace dynamically constructed scanf formats with direct numeric and
delimiter parsing.  The old implementation accepted only one literal
delimiter sequence, mishandled percent delimiters, and used an
uninitialized length when scans failed.

Define empty delimiter strings as invalid, reset optional consumed-count
outputs for invalid configurations, and document the delimiter and
destination-pointer contract.  Add regression coverage for repeated,
mixed, and percent delimiters, deterministic prefix counts, null
destinations, and invalid configurations.
2026-09-21 20:06:30 -04:00
Clifford Yapp 8cdedd8752 libbn: correct polynomial edge cases
Correct descending-order polynomial API documentation and make basic
arithmetic helpers safe when their output aliases an input.  Preserve
the output when multiplication exceeds the fixed degree limit.

Handle constant and higher-degree divisors without unsigned degree
underflow.  Normalize small nonzero leading coefficients before the
direct quadratic, cubic, and quartic solvers.

Repair the unreferenced bivariate polynomial implementation: expose its
API, add release support, preserve both dimensions while growing, and
correct its addition and convolution.  Add focused regressions for all
corrected cases.
2026-09-21 18:40:54 -04:00
Clifford Yapp e4ba0b166c libbn: correct matrix and quaternion edge cases
Use bn_tol's angular predicate in bn_mat_fromto instead of the
duplicated line-segment ratio test, which compared dimensionless
slopes against a distance tolerance.  Normalize inputs and return an
identity matrix when either cannot be normalized.

Make bn_mat_lookat support non-unit and Z-axis directions.  Correct
bn_wrt_point_direc's translation-back sign and replace its shared
scratch storage with local temporaries.

Define quaternion behavior for antipodal bisection and identity
logarithms.  Document related angle and input contracts, and add
regressions for the corrected cases.
2026-09-21 18:28:12 -04:00
Clifford Yapp 586ac06c7e libbg: correct geometry query contracts
Normalize point/normal planes without mutating caller input, and
correct coordinate frames for non-unit plane equations.

Support rank-deficient PCA input, correct 3D polygon centroids and
CCW ordering, honor polygon holes in view bounds, and gather every
mesh diagnostic while preserving the no-diagnostics fast path.

Fix Creo's inverted solidity result, make segment clipping reentrant,
and clarify geometry API contracts.  Add focused plane, PCA, polygon,
and trimesh regressions.
2026-09-21 18:17:12 -04:00
Clifford Yapp 0d745fca35 Use bu_vls for VFONT paths
The VFONT loader used a 128-byte buffer when constructing the bundled
font path. Odd-pathname distcheck directories exceeded that limit in CI
environments, causing fblabel to reject the default font and all
dependent rtwizard tests to fail. Use a bu_vls path so the installed
font can be opened regardless of build prefix length, and release it on
all loader exits.
2026-09-18 16:39:52 -04:00
Clifford Yapp 5270e67abe List files for distcheck 2026-09-18 15:47:02 -04:00
Clifford Yapp 14051845a3 facetize: add source-guided adaptive SPSR
Fixed-size point dumps make Poisson reconstruction unreliable as a
last-resort facetization method.  They can miss thin partitions while
oversampling surfaces that are already well resolved, and an arbitrary
point count does not show whether source characterization is complete.

Seed reconstruction with stabilized Crofton samples.  Retain paired
entry and exit normals, partition thickness, and ray identities so thin
geometry contributes explicit local information instead of unrelated
points.

Add a bounded adaptive libbg driver around the existing upstream
Poisson solver.  Each solve reports residual, density, normal, and
surface-variation hints to a caller callback.  Facetize responds with
exact source rays and validates partitions, endpoints, chord length,
area, volume, and closed-manifold topology before accepting a result.

Bound refinement by time, point, pass, and convergence limits.  Relax
only chord and thin-partition thresholds when the source BoT is already
known to be non-manifold.  Deprecate the fixed-array API, remove the
intermediate point-sampling path, adapt pnts callers, and add focused
coverage.  The vendored SPSR implementation remains unchanged.

Use configured method priority for perturbation variants so an MDC-only
run never invokes NMG.  Report finalization progress and retry counts
while the fallback hierarchy is evaluated.
2026-09-18 16:14:16 +00:00
Clifford Yapp d6fa937212 libanalyze: replace marching cubes with adaptive MDC
The legacy marching-cubes polygonizer sampled a uniform grid and could
miss thin or high-aspect features, merge undersampled tunnels, and emit
surfaces whose shared-edge winding was inconsistent.  Increasing the
global resolution made those failures prohibitively expensive rather
than addressing their local causes.

Add a ray-driven manifold dual contouring API built on an adaptive
octree.  Cache exact signs and Hermite edge crossings from librt
partitions, place feature-preserving vertices with bounded QEF solves,
and refine locally for thin walls, multiple crossings, interior
surfaces, and ambiguous topology.  Build faces from shared crossings
with deterministic winding and require closed manifold output.

Back analyze_polygonize with the new implementation, expose MDC as a
facetize method, and remove the obsolete polygonizer code, licensing,
continuation sampler, and CM default.  Keep NMG available when polygon
preservation is more important and it can successfully process the
input.

Document the Ju, Schaefer, and Warren algorithmic basis and the
bottom-up Euler-characteristic escalation path.  Add coverage for thin
walls, tunnels, overlapping CSG, toroidal cases, winding, resource
limits, and the compatibility polygonalize entry point.
2026-09-18 16:14:16 +00:00
Clifford Yapp 37aee260c1 facetize: retire the legacy pipeline and harden region results
The compatibility implementation duplicated command behavior and
retained unreliable paths that had been superseded by the planned
worker pipeline.  Keeping both implementations made option handling and
failure behavior needlessly difficult to reason about.

Remove the old facetize sources and MGED registration so all command
modes use the maintained orchestrator.  Preserve implicitly selected
region roots in the output database and keep region retries scoped to
validated perturbation inputs.

Tighten source and result validation, reject empty preparation results,
and suppress expected import or preparation diagnostics only inside a
scoped log hook.  Keep routine details out of the terminal summary and
write complete detail logs when concise reporting is not possible.

Extend regression and validation coverage for implicit roots, tolerated
failures, and malformed or unsupported inputs.
2026-09-18 16:14:16 +00:00
Clifford Yapp 56d64428ae facetize: separate orchestration and isolate NMG evaluation
The command implementation mixed option handling, mode selection,
database setup, scheduling, and result publication.  NMG Boolean work
also ran in the parent process, allowing known NMG failures to terminate
the overall conversion.

Introduce an explicit planning and orchestration layer so each command
mode follows one visible execution path.  Replace positional worker
arguments with typed request and result messages that are validated
before they can alter the working database.

Move NMG Boolean evaluation into the supervised subprocess framework.
Transfer trees and BoTs through the existing transaction protocol,
report failures without killing the parent, and reject primitives that
do not provide the required tessellation callback.

Update facetize regression and worker-protocol coverage for the new
boundaries and failure semantics.
2026-09-18 16:14:16 +00:00
Clifford Yapp 0f6db7016d facetize: add supervised parallel primitive workers
Primitive tessellation previously paid process and database setup
costs per object and left the parent with limited visibility into work
that timed out or died.  Large models consequently spent substantial
time outside the tessellation algorithms themselves.

Add long-lived supervised workers and a bounded scheduler for
independent objects.  Divide CPUs between worker processes and each
worker internal parallelism, reuse opened databases, and retain cache
state across requests.

Make result publication transactional.  A WRITE_STARTED acknowledgment
closes the READY and PROCEED race, while the parent detects timeouts and
abnormal exits, restarts workers, and records only completed object
results.  Reuse temporary result databases without leaking state between
requests.

Add process and database-transfer helpers together with worker,
recovery, and validation tests.  Document the parallel job control and
its resource behavior.
2026-09-18 16:14:15 +00:00
Clifford Yapp cbffc1171b libbu: support resource-aware worker processes
Large subprocess pools need to divide machine resources without allowing
each worker internal parallelism to consume every CPU.  Add a
process-local limit to bu_avail_cpus() and bu_parallel(), with atomic
updates and coverage for applying and removing the limit.

Make BU_MEM_AVAIL count FreeBSD reclaimable inactive and cache queues
in addition to immediately free pages.  This prevents idle systems from
appearing memory-starved to resource-aware algorithms and adds native
sysctl checks for the reported values.

Rename the private process.h to process_private.h.  This prevents the
source header from shadowing the MSVC CRT header used by std::thread,
which otherwise leaves _beginthreadex undeclared.
2026-09-18 16:14:15 +00:00
281 changed files with 32681 additions and 17175 deletions
+31 -16
View File
@@ -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)
+2
View File
@@ -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
-246
View File
@@ -1,246 +0,0 @@
Annotation and view-object rendering notes
==========================================
Scope
-----
BRL-CAD has two related, but deliberately distinct, ways to represent visual
markup:
* An annotation is an ID_ANNOT database primitive. It is persistent model
content with enough semantic and presentation information to be reproduced
outside the application that created it.
* A view object is application-owned scene state. It is useful for interactive
construction and manipulation, but is intentionally not backed by a database
object.
They share portions of the interactive display infrastructure, but they do not
currently have interchangeable ownership, lifetime, or rendering semantics.
Annotation capabilities
-----------------------
An rt_annot_internal stores a model-space anchor, a set of 2D vertices, and
either a model plane basis or screen-space placement. Its segment types cover
lines, circular arcs, NURBS, Beziers, text, and filled polygons. Segment style
records include a semantic role, line pattern and width, RGBA color, font or
symbol, text scaling, and decorative flags.
The libged annotate command can create styled text, leaders, linear/angular/
radius/diameter/ordinate dimensions, and bounding-box autodim groups. It can
regenerate derived annotations from saved attributes and associate them with
source model objects without making them part of those objects' CSG trees.
Annotations therefore have the properties needed for persistent markup:
* database naming, paths, transforms, and instances;
* survival across save/reopen and transfer to another application;
* semantic segment roles and per-segment presentation data;
* model-space and view-dependent screen-space placement; and
* high-level generation metadata and source-object associations.
An annotation is informational coverage, not a CSG solid. The raytracing
implementation preserves that distinction. It prepares annotation coverage
from active database paths and composites it on primary samples after ordinary
geometry has established color and depth. Model-space annotation coverage is
occluded by nearer solid geometry. Screen-space annotation coverage uses the
render view and image dimensions. Annotations do not acquire artificial
volume, alter model bounds, cast shadows, or participate in reflection and
refraction rays.
Visible coverage is returned in back-to-front layers. Model-space layers are
ordered by ray distance, followed by view-space layers ordered by projected
anchor depth; segment draw order breaks ties within an annotation. This lets
the image renderers apply source-over alpha consistently while retaining the
single-frontmost-hit query for compatibility. NIRT continues to report one
frontmost segment per model-space annotation rather than exposing these
presentation layers as solid partitions.
Color follows normal database tree state. An annotation segment with an
explicit RGBA style wins; otherwise it inherits the resolved combination-path
color; otherwise it is white. The legacy region-ID color table is not applied
to annotation display or coverage, since annotations are informational leaves
rather than regions.
Prepared coverage uses the existing libbg RTree implementation. A 3D RTree
culls model-space annotation instances and a 2D local RTree culls segments
inside every annotation. Screen-space instance extents cannot be placed in a
static world-space index because they change with the view, so those instances
remain in a compact list while still using their local segment RTree. This
avoids a new spatial data structure and keeps prepared scenes immutable and
safe for concurrent sample queries.
The rt, ART, and ADRT/ISST integrations follow this overlay model. ERT invokes
rt with the database, displayed database paths, and view, so database-backed
annotations can follow that route as well. ERT does not currently serialize
or pass application-owned view objects to its subprocess.
NIRT keeps annotation diagnostics opt-in. `useannot 1` enables model-space
annotation intersections, and the `fmt a` action reports them. Default NIRT
output is unchanged. Screen-space annotations are deliberately omitted
because a NIRT ray does not define an image size, pixel footprint, or complete
model-to-view mapping.
View-object capabilities
------------------------
The core representation is bv_scene_obj. A view set owns shared view objects,
and a particular bview can also own local objects. Independent views force
local ownership. bv_obj_put recycles objects when their application lifetime
ends; the objects are not written to the .g database.
The `view obj` command exposes the principal markup types:
* Lines are model-coordinate polylines represented by vlists. Generic scene
rendering can add arrowheads to segment ends.
* Labels have a model anchor and optional model-space target/leader. Their text
is projected and drawn as screen-facing HUD text. If the command omits Z,
screen X/Y coordinates are projected onto the current view plane at creation
time.
* Polygons include general polygons, circles, ellipses, rectangles, and
squares. Creation and editing operate on a saved view plane with a view-Z
offset. Selection, movement, open/close, area, overlap, and 2D boolean
operations are supported. A "fill" is currently a set of hatch lines, not
an opaque surface. Polygons can be explicitly imported from or exported to
persistent sketch primitives.
Scene objects also provide object color, transparency, pixel line width, dash
style, and arrow dimensions. Not all of these properties have a uniform
command interface for every object type.
There are other visual layers that should not be conflated with these objects.
Legacy gv_tcl data/sdata arrows, axes, lines, labels, and polygons have a
separate representation. Faceplate/HUD elements are screen UI. Database
objects temporarily cloned for editing are also transient, but retain database
primitive semantics and live in the database-object scene containers.
Comparison
----------
Both annotations and view objects eventually produce display-manager pixels,
often through bv_scene_obj and vlists. That common final path does not make
their source representations equivalent.
Annotations are the canonical choice when markup must be saved, named in a
database, instanced, shared, raytraced from a .g file, or understood by another
program. View objects are the right choice for application/session state that
must be created and manipulated without changing the database.
Some view objects have a direct annotation analogue, but conversion is not
lossless without type-aware logic:
* A view polyline maps naturally to line segments, including arrowhead style.
* A polygon outline and its hatch lines can map to annotation coverage, but the
existing view "fill" must not silently become an opaque filled polygon.
* A label is hybrid. Its target and leader are model-space geometry while its
glyphs remain screen-facing at a projected model anchor.
* Arbitrary vlist extraction is insufficient because display-matrix commands,
point and line sizes, child objects, glyph behavior, and semantic roles are
otherwise lost or ambiguous.
View-object line width and annotation line width also need an explicit unit
policy. View-object widths are display pixels. Screen-space annotation widths
are view-dependent coverage widths. A model-space annotation's line_width is
used as a scale on the automatically selected model-space stroke width. In
other words, removing a separate raytracer "stroke-width" setting does not
remove width control: the annotation's existing line_width/`--line-width`
style remains the single user-facing control. The renderer interprets it in
the appropriate coordinate system rather than exposing a second competing
knob.
Recommended transient rendering design
--------------------------------------
Do not make librt or the raytracers depend on bv_scene_obj, libbv application
containers, or libged. Do not manufacture tubes, TGCs, meshes, or geometry
just in front of the view plane. Artificial geometry would affect bounds,
depth, shadows, and secondary rays, and would make antialiasing and perspective
behavior depend on a fabricated distance from the eye.
If transient view objects need to appear in raytraced output, use an explicit
application-to-renderer snapshot boundary:
1. The GED/application layer snapshots selected live view objects when a render
is launched. This is the only layer that owns their lifetime and semantics.
2. A type-aware adapter in libged or libbv translates supported objects into a
renderer-neutral overlay/coverage description. It should reuse annotation
coverage preparation through an in-memory annotation-instance interface,
rather than duplicate line, glyph, and arrow intersection algorithms.
3. The snapshot travels with the exact view state: model-to-view transform,
perspective, image width and height, and sampling convention. In-process
renderers can receive it directly; subprocess rt/ERT needs an explicit
serialized side channel.
4. Raytracers composite this coverage only on primary image samples, after
geometry color and depth are known. It remains outside CSG, model bounds,
shadows, reflections, and refractions.
5. Initial support should be deliberately finite: polyline strokes and
arrowheads, label glyphs and leaders, polygon outlines, and polygon hatches.
Faceplate elements and legacy gv_tcl overlays should be separately specified
opt-in layers rather than being included accidentally.
6. Snapshot records should preserve object name and type, RGBA, visibility,
local/shared ownership, presentation units, and source identity. Unsupported
object types should produce a deliberate diagnostic or documented omission.
Application-side compositing of the interactive display-manager layer over an
ERT framebuffer is a viable smaller feature if only ERT screen captures need
view objects. It would closely reproduce the interactive view, but it would
not make the objects available to standalone rt, ART, or ISST output and should
not be presented as the general renderer solution.
Conversion between the two systems should remain explicit. Automatically
turning view objects into database annotations would violate their deliberately
transient contract. A user command to promote selected view markup to a
persistent annotation would be reasonable; polygon-to-sketch export is an
existing precedent. Conversely, a view-object editor may be used as a front
end for constructing an annotation, provided the persistence action is clear.
Maintenance findings before implementing the adapter
----------------------------------------------------
Several representation issues should be corrected before type-driven snapshot
conversion is relied upon:
* Container-selection flags (BV_DB_OBJS, BV_VIEW_OBJS, BV_LOCAL_OBJS, and
BV_CHILD_OBJS) reuse numeric bits also used for semantic s_type_flags
(BV_DBOBJ_BASED, BV_VIEWONLY, BV_LINES, and others). bv_obj_create currently
assigns its type argument to s_type_flags. A local view object consequently
acquires BV_LINES incidentally, while the line command does not explicitly
tag lines. The namespaces should be separated and every object explicitly
typed.
* Lines, labels, and polygons should have unambiguous type tags and per-type
lifecycle/extraction callbacks. Snapshot conversion must inspect typed data,
not infer semantics from a rendered vlist.
* Label construction should initialize every bv_label field deliberately,
including size, line_flag, anchor, arrow, and target. Current construction
does not establish all of those values on every path.
* The status of legacy gv_tcl overlay data should be decided explicitly:
migrate it, support it through a separate adapter, or document its exclusion.
* Screen-space annotation display and raytrace units need a parity test. The
legacy display-matrix vlist path and the ray coverage path currently express
screen offsets through different mechanisms; their apparent agreement must
be verified rather than assumed.
* Per-segment annotation colors exceed the legacy interactive vlist object's
single-color model. Uniform annotate-command colors work today, but true
multicolor interactive parity may require split scene objects or a richer
display representation.
Tests for a future adapter should cover shared and local ownership, cleanup,
label anchors and leaders, polygon holes/hatches/view-Z, perspective, pixel
width, model-space occlusion, screen-space overlay, and serialization across
the ERT subprocess boundary.
Conclusion
----------
Persistent annotations and transient view objects should continue to coexist.
The annotation coverage scene is the correct renderer-side abstraction for
both saved annotations and a future snapshot of transient markup. The
application should perform the semantic conversion and transfer a view-complete
snapshot; the renderer should treat it as primary-ray coverage, never as
invented scene geometry.
+1 -53
View File
@@ -132,10 +132,6 @@ Command to set the backout flag. With no option, prints the current value. When
Sets the _useair_ member of the __rt_i__ structure to the integer _n_. If _n_ is 0, then *nirt* ignores any air in the geometry. If this command is invoked with no arguments, *nirt* prints the current value of _useair_. Default is 0.
*useannot [* _n_*]*::
Enables (_n_=1) or disables (_n_=0) diagnostic annotation intersections. When enabled, *nirt* evaluates all model-space annotations below the active database paths and invokes the *a* output action for each crossing. Screen-space annotations are not evaluated because an individual NIRT ray does not supply a pixel size or model-to-view transform. If this command is invoked with no arguments, *nirt* prints the current value. Default is 0.
*overlap_claims [* _n_*]*::
Specifies how to handle multiple regions' claims to segments of a ray. If _n_ is 0 or "resolve", then all overlaps are resolved in favor of a single region and any other claimants are ignored. If _n_ is 2 or "rebuild_all", then all overlaps are rebuilt, so any overlapping regions along the ray create individual (geometrically intersecting) partitions. If _n_ is 3 or "retain", then all overlaps are retained. In this case, the resulting partitions are always geometrically disjoint, each one is owned by a single region according to the current overlap resolution strategy, but every claimant is recorded. If _n_ is 1 or "rebuild_fastgen", then *nirt* takes on _FASTGEN_ behavior, so overlaps of plate-mode primitives are rebuilt, but other overlaps are retained. This command is useful with the __claimant_count__, __claimant_list__, and __claimant_listn__ output items. Default is "resolve".
@@ -201,11 +197,7 @@ Quits *nirt*.
[[output_statements]]
== OUTPUT STATEMENTS
*nirt* allows the user a high degree of control, via the _fmt_ command, over what information gets printed out for each ray and in what format. There are eight types of output statements, each of which is executed under appropriate circumstances. The types and their use are:
*a*::
Annotation. Executed once for each model-space annotation crossing when *useannot* is enabled. Crossings are reported in ray-distance order after the ordinary solid shotline output. The default format is empty.
*nirt* allows the user a high degree of control, via the _fmt_ command, over what information gets printed out for each ray and in what format. There are six types of output statements, each of which is executed under appropriate circumstances. The types and their use are:
*r*::
@@ -286,41 +278,6 @@ azimuth of view (i.e., of ray direction).
elevation of view (i.e., of ray direction).
Annotation Information
*annot_path*::
full database path of the annotation primitive.
*annot_segment*::
zero-based annotation segment index.
*annot_role*::
semantic role of the annotation segment, such as `text`, `leader`, `dimension`, or `arrowhead`.
*annot_x*::
_x_ coordinate of the annotation hit point.
*annot_y*::
_y_ coordinate of the annotation hit point.
*annot_z*::
_z_ coordinate of the annotation hit point.
*annot_d*::
_d_ grid-plane coordinate of the annotation hit point.
*annot_visible*::
1 if no non-air solid surface is closer to the ray origin, otherwise 0. Occluded annotation crossings are still reported so they can be inspected during debugging.
Partition Information
@@ -564,15 +521,6 @@ nirt -A material_id -A los_density model.g all
Each region the ray passes through is reported together with its _material_id_ and _los_density_ attribute values via the *attributes* output item, which is useful for vulnerability and mass-properties analyses.
====
.Reporting annotation crossings for debugging
====
[source,bash]
----
nirt -s -e 'useannot 1;fmt a "%s %d (%g %g %g) visible=%d\n" annot_path annot_segment annot_x annot_y annot_z annot_visible;s' model.g component dimensions
----
This opt-in format reports every model-space annotation crossing and whether it is in front of the first solid surface. Ordinary NIRT output is unchanged unless *useannot* is enabled and an *a* format is supplied.
====
[[hints]]
== HINTS
+34 -18
View File
@@ -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.
====
+3 -2
View File
@@ -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.
+12
View File
@@ -200,6 +200,18 @@ ____
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) appends
one high-precision rtcmp JSON record per ray with final hit partitions to the
file named by LIBRT_RTCMP_FILE. Misses produce no record. librt never clears
the 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. 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 the environment variable LIBRT_RTCMP_PRIMITIVES
is set to 1.
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.
+2 -32
View File
@@ -25,7 +25,7 @@ where the "char" argument is replaced by a single character identifying the cate
[[nirt_format_categories]]
== Output Categories
NIRT offers eight categories of output reporting:
NIRT offers seven categories of output reporting:
[cols="3*"]
[%noheader]
@@ -33,9 +33,6 @@ NIRT offers eight categories of output reporting:
|*Category Name*
|*Char*
|*Description*
|*Annotation*
|*a*
|Output for each model-space annotation crossing when annotation reporting is enabled with *useannot 1*. The default annotation format is empty.
|*Ray*
|*r*
|Report of ray information. The formatting associated with r will print once per ray, regardless of whether a region is encountered.
@@ -65,7 +62,7 @@ NIRT offers eight categories of output reporting:
[[nirt_format_str]]
== Format Strings
NIRT's format specifications are a subset of those found in printf implementations. The d, i, f, e, E, g, G, and s types are supported, as well as width and precision specifiers.
NIRT's format specifications are a subset of those found in printf implementations. Specifically, the d, i, and f types are supported, as well as width and precision specifiers.
[[nirt_format_keys]]
@@ -103,33 +100,6 @@ Unlike the full C/C++ printf, NIRT accepts only pre-defined string keys that ide
Footnote and Miss categories have no particular keys unique to them, although they (like all categories) can use the Ray keys if desired. Partition, Overlap, and Gap, on the other hand, have keys that are unique to their contexts and valid only within those contexts:
.Annotation Variables
[cols="2*"]
[%noheader]
|===
|annot_path
|Full database path of the annotation primitive.
|annot_segment
|Zero-based annotation segment index.
|annot_role
|Semantic role of the annotation segment, such as `text`, `leader`, `dimension`, or `arrowhead`.
|annot_x
|x coordinate of the annotation hit point.
|annot_y
|y coordinate of the annotation hit point.
|annot_z
|z coordinate of the annotation hit point.
|annot_d
|d grid-plane coordinate of the annotation hit point.
|annot_visible
|1 if no non-air solid surface is closer to the ray origin, otherwise 0.
|===
Annotation reporting is diagnostic and opt-in. It returns all model-space
annotation crossings in ray-distance order, including occluded crossings.
Screen-space annotations are not reported because a NIRT shot does not define
the pixel dimensions and model-to-view transform needed to evaluate them.
.Partition Variables
[cols="2*"]
[%noheader]
+9 -8
View File
@@ -40,11 +40,12 @@ Coordinates and explicit sizes use the database's current modeling units.
Creation commands draw the new annotation immediately. Use *--no-draw* to
create it without changing the display. Names must not already exist.
Ray-traced annotations are zero-thickness informational coverage rather than
solid regions. Model-space annotations are depth-tested against ordinary
geometry. Screen-space annotations are generated for the current camera and
composited in the foreground; they do not participate in shadows, reflections,
line-of-sight thickness, or Boolean evaluation.
Model-space annotations also participate in raytracing. Librt expands their
strokes and filled areas into transient mesh geometry during preparation; the
generated geometry is not written to the database. It uses the normal region
material and color resolved from the containing combination tree. Screen-space
annotations remain display-only because primitive preparation has no render
view or image dimensions with which to place them.
The supported dimensions follow the general presentation conventions of ISO
129-1. Lettering defaults to OSIFont, an ISO 3098-style technical font shipped
@@ -208,9 +209,9 @@ Set the persistent annotation color using integer red, green, and blue values
from 0 through 255.
*--line-width width*::
Set the presentation line width. Display and screen-space ray rendering use
the value as a pixel width. Model-space ray rendering uses it to scale the
automatic stroke width derived from the annotation's text height or extent.
Set the display line width. For a model-space raytrace, this value scales the
physical stroke width inferred from the annotation's text height or overall
extent.
*--line-style style*::
Set semantic line-pattern metadata to *continuous*, *dashed*, *dotted*,
+24 -16
View File
@@ -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
View File
@@ -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
-1
View File
@@ -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)
-9
View File
@@ -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
+1
View File
@@ -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
View File
@@ -1,5 +1,6 @@
set(
analyze_headers
contour.h
debug.h
defines.h
density.h
+118
View File
@@ -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
*/
+22 -2
View File
@@ -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
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+1 -1
View File
@@ -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,
+5 -1
View File
@@ -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
View File
@@ -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.
*
+31
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+16 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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 Append rays to LIBRT_RTCMP_FILE; 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
View File
@@ -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 {
-85
View File
@@ -29,19 +29,10 @@
#include "bu/list.h"
#include "bu/vls.h"
#include "bn/tol.h"
#include "rt/geom.h"
#include "rt/defines.h"
__BEGIN_DECLS
struct db_i;
struct xray;
#define RT_ANNOT_SCREEN_DPI 96.0
#define RT_ANNOT_MILLIMETERS_PER_INCH 25.4
#define RT_ANNOT_SCREEN_PIXELS_PER_MM \
(RT_ANNOT_SCREEN_DPI / RT_ANNOT_MILLIMETERS_PER_INCH)
RT_EXPORT extern struct rt_annot_internal *rt_copy_annot(const struct rt_annot_internal *annot_ip);
/** Validate annotation topology, model-space placement, and optional segment
@@ -49,82 +40,6 @@ RT_EXPORT extern struct rt_annot_internal *rt_copy_annot(const struct rt_annot_i
RT_EXPORT extern int rt_annot_validate(const struct rt_annot_internal *annot_ip,
struct bu_vls *messages);
/** Opaque, immutable collection of prepared annotation instances. */
struct rt_annot_scene;
/** Camera state needed to evaluate view-space annotations. Pixel sample
* coordinates use a lower-left origin. */
struct rt_annot_view {
mat_t model2view;
size_t width;
size_t height;
fastf_t perspective;
};
/** A zero-thickness annotation coverage event. path is owned by the scene. */
struct rt_annot_hit {
const char *path;
size_t segment;
uint32_t role;
point_t point;
fastf_t distance;
unsigned char color[4];
int screen_space;
int visible;
};
/** Prepare annotations referenced by the supplied top-level database paths.
* Boolean operations are intentionally ignored: annotations are
* informational leaves, not solids. */
RT_EXPORT extern struct rt_annot_scene *rt_annot_scene_create(
struct db_i *dbip, int path_count, const char * const *paths,
const struct bg_tess_tol *ttol, const struct bn_tol *tol);
RT_EXPORT extern void rt_annot_scene_destroy(struct rt_annot_scene *scene);
RT_EXPORT extern size_t rt_annot_scene_count(const struct rt_annot_scene *scene);
/** Return model-space bounds for prepared annotations. Screen-space geometry
* contributes its model-space anchor because its extent is view-dependent. */
RT_EXPORT extern int rt_annot_scene_bounds(const struct rt_annot_scene *scene,
point_t minimum, point_t maximum);
/** Return the top visible coverage event for a primary sample. */
RT_EXPORT extern int rt_annot_scene_query(const struct rt_annot_scene *scene,
const struct rt_annot_view *view, const struct xray *ray,
fastf_t sample_x, fastf_t sample_y, fastf_t scene_distance,
struct rt_annot_hit *hit);
/** Return all visible primary-sample coverage events in back-to-front order.
* Model-space events precede screen-space events. When hits is NULL or
* capacity is zero, only the required result count is returned. */
RT_EXPORT extern size_t rt_annot_scene_query_layers(
const struct rt_annot_scene *scene, const struct rt_annot_view *view,
const struct xray *ray, fastf_t sample_x, fastf_t sample_y,
fastf_t scene_distance, struct rt_annot_hit *hits, size_t capacity);
/** Return all model-space coverage events, sorted by ray distance. When hits
* is NULL or capacity is zero, only the required result count is returned. */
RT_EXPORT extern size_t rt_annot_scene_query_model(const struct rt_annot_scene *scene,
const struct xray *ray, fastf_t scene_distance,
struct rt_annot_hit *hits, size_t capacity);
/** Alpha-composite a coverage result over a linear RGB sample. */
RT_EXPORT extern void rt_annot_hit_blend(fastf_t color[3],
const struct rt_annot_hit *hit);
/** Alpha-composite all visible primary-sample coverage over color. If
* front_hit is non-NULL, it receives the frontmost event. */
RT_EXPORT extern int rt_annot_scene_composite(const struct rt_annot_scene *scene,
const struct rt_annot_view *view, const struct xray *ray,
fastf_t sample_x, fastf_t sample_y, fastf_t scene_distance,
fastf_t color[3], struct rt_annot_hit *front_hit);
/** Generate one annotation segment in annotation-local XY coordinates. This
* is shared by plotting and the coverage preparer to keep curve and font
* expansion identical. */
RT_EXPORT extern int rt_annot_segment_vlist(struct bu_list *vlfree,
struct bu_list *vhead, const struct bg_tess_tol *ttol,
const struct rt_annot_internal *annot_ip, size_t segment);
__END_DECLS
/** @} */
-3
View File
@@ -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;
+1
View File
@@ -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) */
+3
View File
@@ -55,6 +55,9 @@
__BEGIN_DECLS
/** 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
+5 -1
View File
@@ -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
-1
View File
@@ -1,6 +1,5 @@
# default.nrt
# Description: default - Standard NIRT Reporting Format
fmt a ""
fmt r "Origin (x y z) = (%.8f %.8f %.8f) (h v d) = (%.4f %.4f %.4f)\nDirection (x y z) = (%.8f %.8f %.8f) (az el) = (%.8f %.8f)\n" x_orig y_orig z_orig h v d_orig x_dir y_dir z_dir a e
fmt h " Region Name Entry (x y z) LOS Obliq_in Attrib\n"
fmt p "%-20s (%9.4f %9.4f %9.4f) %8.4f %8.4f%s\n" reg_name x_in y_in z_in los obliq_in attributes
+14
View File
@@ -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
+259 -16
View File
@@ -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");
+8
View File
@@ -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
+33
View 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 ***"
+2 -3
View File
@@ -53,7 +53,7 @@ set(
)
brlcad_addlib(librender "${LIBRENDER_SRCS}" "" "${ADRT_LOCAL_INCLUDE_DIRS}"
PRIVATE_LIBS libgcv librt ZLIB::ZLIB ${M_LIBRARY}
PRIVATE_LIBS libgcv ZLIB::ZLIB ${M_LIBRARY}
)
set_target_properties(librender PROPERTIES VERSION 20.0.1 SOVERSION 20)
add_dependencies(librender gcv_plugins)
@@ -109,9 +109,8 @@ cmakefiles(isst.c)
add_subdirectory(master)
add_subdirectory(slave)
add_subdirectory(tests)
set(adrt_ignore_files load.h load_g.h tienet.h)
set(adrt_ignore_files load.h tienet.h)
cmakefiles(${adrt_ignore_files})
cmakefiles(CMakeLists.txt)
-8
View File
@@ -90,19 +90,11 @@ enum
#define ADRT_NAME_SIZE 256
/* TIE geometry is normalized from millimeters to meters while loading. */
#define ADRT_MODEL_UNITS_PER_TIE_UNIT 1000.0
#define ADRT_LOAD_FORMAT_G 42
#define ADRT_LOAD_FORMAT_REG 43
#define ADRT_LOAD_FORMAT_KDTREE 44
struct rt_annot_scene;
RENDER_EXPORT extern int load_g(struct tie_s *tie, const char *db, int argc, const char **argv, struct adrt_mesh_s **);
RENDER_EXPORT extern int load_g_annotations(struct tie_s *tie, const char *db,
int argc, const char **argv, struct adrt_mesh_s **,
struct rt_annot_scene **annotations);
#endif
+38 -23
View File
@@ -59,7 +59,6 @@ struct isst_s {
struct render_camera_s camera;
struct camera_tile_s tile;
struct adrt_mesh_s *meshes;
struct rt_annot_scene *annotations;
tienet_buffer_t buffer_image;
int ogl, sflags, w, h, gs, ui;
double dt, fps, uic;
@@ -68,7 +67,6 @@ struct isst_s {
void *texdata;
vect_t camera_pos_init;
vect_t camera_focus_init;
fastf_t scene_radius;
int64_t t1;
int64_t t2;
int dirty;
@@ -121,7 +119,13 @@ resize_isst(struct isst_s *isstp)
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
isstp->texdata = realloc(isstp->texdata, isstp->camera.w * isstp->camera.h * 3);
{
/* realloc through a temporary so a failure does not leak (and null out)
* the existing buffer. */
void *tmp = realloc(isstp->texdata, isstp->camera.w * isstp->camera.h * 3);
if (tmp)
isstp->texdata = tmp;
}
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, isstp->camera.w, isstp->camera.h, 0, GL_RGB, GL_UNSIGNED_BYTE, isstp->texdata);
glDisable(GL_LIGHTING);
@@ -154,14 +158,11 @@ isst_load_g(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc,
argv = (char **)malloc(sizeof(char *) * (strlen(Tcl_GetString(objv[3])) + 1)); /* allocate way too much. */
argc = (int)bu_argv_from_string(argv, strlen(Tcl_GetString(objv[3])), Tcl_GetString(objv[3]));
rt_annot_scene_destroy(isst->annotations);
isst->annotations = NULL;
load_g_annotations(isst->tie, Tcl_GetString(objv[2]), argc,
(const char **)argv, &(isst->meshes), &isst->annotations);
load_g(isst->tie, Tcl_GetString(objv[2]), argc, (const char **)argv, &(isst->meshes));
free(argv);
isst->scene_radius = render_camera_fit_scene(&isst->camera, isst->tie,
isst->annotations, ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSETALL(isst->camera.pos, isst->tie->radius);
VMOVE(isst->camera.focus, isst->tie->mid);
VMOVE(isst->camera_pos_init, isst->camera.pos);
VMOVE(isst->camera_focus_init, isst->camera.focus);
@@ -238,9 +239,7 @@ paint_window(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Ob
isst->buffer_image.ind = 0;
render_camera_prep(&isst->camera);
render_camera_render_annotations(&isst->camera, isst->tie, &isst->tile,
&isst->buffer_image, isst->annotations,
ADRT_MODEL_UNITS_PER_TIE_UNIT);
render_camera_render(&isst->camera, isst->tie, &isst->tile, &isst->buffer_image);
isst->t1 = bu_gettime();
@@ -283,6 +282,7 @@ set_resolution(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_
}
CLAMP(resolution, 1, 20);
/* gs is the render grid size; 0 means render at full window resolution */
if (resolution == 20)
isst->gs = 0;
else
@@ -326,10 +326,13 @@ isst_zap(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *c
return TCL_ERROR;
}
/* Release the buffers owned directly by the workspace before the struct
* itself. (The tie and mesh list have a more involved lifecycle and are
* left for a dedicated teardown.) */
TIENET_BUFFER_FREE(isst->buffer_image);
if (isst->texdata)
free(isst->texdata);
rt_annot_scene_destroy(isst->annotations);
bu_free(isst, "isst free");
isst = NULL;
@@ -351,7 +354,7 @@ render_mode(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj
if (objc == 4)
buf = Tcl_GetString(objv[3]);
/* pack the 'rest' into buf - probably should use a vls for this*/
/* pack the 'rest' into buf - probably should use a vls for this */
if ( strlen(mode) == 3 && bu_strncmp("cut", mode, 3) == 0 ) {
struct adrt_mesh_s *mesh;
@@ -388,22 +391,26 @@ zero_view(ClientData UNUSED(clientData), Tcl_Interp *UNUSED(interp), int UNUSED(
static int
move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj *const *objv)
move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
vect_t vec;
int flag;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "pathName flag");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[2], &flag) != TCL_OK)
return TCL_ERROR;
if (flag >= 0) {
VSUB2(vec, isst->camera.focus, isst->camera.pos);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.focus, vec);
} else {
VSUB2(vec, isst->camera.pos, isst->camera.focus);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.focus, vec);
}
@@ -412,12 +419,16 @@ move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), T
}
static int
move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj *const *objv)
move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
vect_t vec, dir, up;
int flag;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "pathName flag");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[2], &flag) != TCL_OK)
return TCL_ERROR;
@@ -426,13 +437,13 @@ move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc),
if (flag >= 0) {
VSUB2(dir, isst->camera.focus, isst->camera.pos);
VCROSS(vec, dir, up);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.pos, dir);
} else {
VSUB2(dir, isst->camera.focus, isst->camera.pos);
VCROSS(vec, dir, up);
VSCALE(vec, vec, -0.1 * isst->scene_radius);
VSCALE(vec, vec, -0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.pos, dir);
}
@@ -540,10 +551,14 @@ aerotate(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *c
VSCALE(vecdfoc, vecdfoc, mag_focus);
VADD2(isst->camera.focus, isst->camera_focus_init, vecdfoc);
}
/* Update the tcl copies of the az/el vars */
/* Update the tcl copies of the az/el vars. Store them in the same units
* as isst_load_g (radians via -DEG2RAD); previously aerotate wrote the raw
* degrees from bn_ae_vec, leaving the Tcl vars inconsistent after a drag. */
VSUB2(vec, isst->camera.focus, isst->camera.pos);
VUNITIZE(vec);
bn_ae_vec(&az, &el, vec);
az = az * -DEG2RAD;
el = el * -DEG2RAD;
bu_vls_sprintf(&tclstr, "%f", az);
Tcl_SetVar(interp, "az", bu_vls_addr(&tclstr), 0);
bu_vls_sprintf(&tclstr, "%f", el);
@@ -561,7 +576,7 @@ open_dm(ClientData UNUSED(cdata), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj
dmp = dm_open(NULL, (void *)interp, dm_default_type(), sizeof(av)/sizeof(void*)-1, (const char **)av);
if (dmp == DM_NULL) {
printf("dm failed?\n");
printf("Failed to open display manager\n");
return TCL_ERROR;
}
@@ -639,7 +654,7 @@ const char *fullname;
argv = __argv;
#endif
/* initialize progname for run-tim resource finding */
/* initialize progname for run-time resource finding */
bu_setprogname(argv[0]);
#ifdef HAVE_WINDOWS_H
+21 -225
View File
@@ -29,7 +29,6 @@
#include "bu/dylib.h"
#include "bu/log.h"
#include "bu/str.h"
#include "rt/primitives/annot.h"
#include "./camera.h"
@@ -44,27 +43,13 @@ struct render_shader_s {
static struct render_shader_s *shaders = NULL;
struct render_camera_annotation_data {
const struct rt_annot_scene *scene;
fastf_t model_units_per_tie_unit;
struct rt_annot_view view;
};
struct render_camera_parallel_data {
render_camera_thread_data_t thread;
struct render_camera_annotation_data annotation;
};
static const fastf_t render_camera_default_scene_radius = 1.0;
/* number of leading characters compared when matching a shader by name */
#define RENDER_SHADER_NAME_CMP_LEN 8
void render_camera_render_thread(int cpu, void *ptr); /* for bu_parallel */
static void render_camera_prep_ortho(render_camera_t *camera);
static void render_camera_prep_persp(render_camera_t *camera);
static void render_camera_prep_persp_dof(render_camera_t *camera);
static void render_camera_annotation_view(const render_camera_t *camera,
fastf_t model_units_per_tie_unit, struct rt_annot_view *view);
static struct render_shader_s *render_shader_register (const char *name, int (*init)(render_t *, const char *));
@@ -408,147 +393,20 @@ render_camera_prep(render_camera_t *camera)
}
fastf_t
render_camera_fit_scene(render_camera_t *camera, const struct tie_s *tie,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
point_t annotation_min, annotation_max;
point_t scene_min, scene_max;
vect_t offset;
fastf_t scene_radius;
if (!camera || !tie)
return 0.0;
if (!annotations || !rt_annot_scene_bounds(annotations,
annotation_min, annotation_max)) {
VSETALL(camera->pos, tie->radius);
VMOVE(camera->focus, tie->mid);
return tie->radius;
}
if (!isfinite(model_units_per_tie_unit) ||
model_units_per_tie_unit <= 0.0)
model_units_per_tie_unit = 1.0;
VSCALE(annotation_min, annotation_min,
1.0 / model_units_per_tie_unit);
VSCALE(annotation_max, annotation_max,
1.0 / model_units_per_tie_unit);
VMOVE(scene_min, annotation_min);
VMOVE(scene_max, annotation_max);
if (tie->tri_num) {
VMINMAX(scene_min, scene_max, tie->amin);
VMINMAX(scene_min, scene_max, tie->amax);
}
VADD2SCALE(camera->focus, scene_min, scene_max, 0.5);
VSUB2(offset, scene_max, scene_min);
scene_radius = 0.5 * MAGNITUDE(offset);
if (!isfinite(scene_radius) || scene_radius <= SMALL_FASTF)
scene_radius = render_camera_default_scene_radius;
/* Keep the established isometric initial direction while positioning it
* relative to the combined scene center. */
VSETALL(offset, scene_radius);
VADD2(camera->pos, camera->focus, offset);
return scene_radius;
}
static void
render_camera_annotation_view(const render_camera_t *camera,
fastf_t model_units_per_tie_unit, struct rt_annot_view *view)
{
vect_t look, up, side, temp, right;
point_t model_position;
fastf_t angle, sine, cosine, xy_scale = 1.0;
mat_t *matrix = &view->model2view;
VSUB2(look, camera->focus, camera->pos);
VUNITIZE(look);
VSET(up, 0.0, 0.0, 1.0);
VMOVE(temp, look);
angle = VDOT(up, temp);
VSCALE(temp, temp, angle);
VSUB2(up, up, temp);
VUNITIZE(up);
VCROSS(side, up, look);
sine = sin(-camera->tilt * DEG2RAD);
cosine = cos(-camera->tilt * DEG2RAD);
VSCALE(up, up, cosine);
VSCALE(side, side, sine);
VADD2(up, up, side);
VCROSS(side, up, look);
VREVERSE(right, side);
MAT_IDN(*matrix);
if (camera->type == RENDER_CAMERA_ORTHOGRAPHIC &&
camera->aspect > 0.0 && camera->gridsize > 0.0) {
xy_scale = 2.0 / (camera->aspect * camera->gridsize *
model_units_per_tie_unit);
} else if (camera->aspect > 0.0) {
xy_scale = 1.0 / camera->aspect;
}
for (size_t axis = 0; axis < 3; ++axis) {
(*matrix)[axis] = right[axis] * xy_scale;
(*matrix)[4 + axis] = up[axis] * xy_scale;
(*matrix)[8 + axis] = -look[axis];
}
VSCALE(model_position, camera->pos, model_units_per_tie_unit);
(*matrix)[3] = -VDOT(&(*matrix)[0], model_position);
(*matrix)[7] = -VDOT(&(*matrix)[4], model_position);
(*matrix)[11] = -VDOT(&(*matrix)[8], model_position);
view->width = camera->w;
view->height = camera->h;
view->perspective =
camera->type == RENDER_CAMERA_PERSPECTIVE ? 2.0 * camera->fov : 0.0;
}
static void
render_camera_composite_annotation(render_camera_thread_data_t *td,
const struct render_camera_annotation_data *annotation,
const struct tie_ray_s *tie_ray, fastf_t sample_x, fastf_t sample_y,
vect_t *pixel)
{
struct rt_annot_hit back_layer = {0};
struct xray ray;
fastf_t scene_distance = INFINITY;
if (!annotation || !annotation->scene)
return;
VSCALE(ray.r_pt, tie_ray->pos, annotation->model_units_per_tie_unit);
VMOVE(ray.r_dir, tie_ray->dir);
if (!rt_annot_scene_query_layers(annotation->scene, &annotation->view,
&ray, sample_x, sample_y, INFINITY, &back_layer, 1))
return;
if (!back_layer.screen_space) {
struct tie_id_s id;
struct tie_ray_s depth_ray = *tie_ray;
if (TIE_WORK(td->tie, &depth_ray, &id, render_hit, NULL))
scene_distance = id.dist * annotation->model_units_per_tie_unit;
}
(void)rt_annot_scene_composite(annotation->scene, &annotation->view,
&ray, sample_x, sample_y, scene_distance, *pixel, NULL);
}
static void
render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *td,
const struct render_camera_annotation_data *annotation)
void
render_camera_render_thread(int UNUSED(cpu), void *ptr)
{
render_camera_thread_data_t *td;
int d, n, res_ind, scanline, v_scanline;
vect_t pixel, accum;
vect_t v1 = VINIT_ZERO;
vect_t v2 = VINIT_ZERO;
struct tie_ray_s ray;
struct tie_ray_s annotation_ray;
fastf_t view_inv;
VSETALL(v1, 0);
td = (render_camera_thread_data_t *)ptr;
view_inv = 1.0 / td->camera->view_num;
td->camera->render.tie = td->tie;
@@ -599,15 +457,9 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VMOVE(ray.pos, td->camera->view_list[d].pos);
ray.depth = 0;
VUNITIZE(ray.dir);
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
render_camera_composite_annotation(td, annotation,
&annotation_ray,
(fastf_t)n + 0.5,
(fastf_t)td->camera->h - (fastf_t)v_scanline - 0.5,
&pixel);
VADD2(accum, accum, pixel);
}
@@ -624,7 +476,6 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VMOVE(ray.pos, td->camera->view_list[0].pos);
ray.depth = 0;
VUNITIZE(ray.dir);
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
@@ -639,19 +490,12 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VSET(pixel, (TFLOAT)RENDER_CAMERA_BGR, (TFLOAT)RENDER_CAMERA_BGG, (TFLOAT)RENDER_CAMERA_BGB);
ray.depth = 0;
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
}
}
if (td->camera->view_num == 1)
render_camera_composite_annotation(td, annotation,
&annotation_ray, (fastf_t)n + 0.5,
(fastf_t)td->camera->h - (fastf_t)v_scanline - 0.5,
&pixel);
if (td->tile->format == RENDER_CAMERA_BIT_DEPTH_24) {
V_MIN(pixel[0], 1);
@@ -679,30 +523,9 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
void
render_camera_render_thread(int cpu, void *ptr)
render_camera_render(render_camera_t *camera, struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result)
{
render_camera_render_thread_impl(cpu, (render_camera_thread_data_t *)ptr,
NULL);
}
static void
render_camera_render_annotations_thread(int cpu, void *ptr)
{
struct render_camera_parallel_data *data =
(struct render_camera_parallel_data *)ptr;
render_camera_render_thread_impl(cpu, &data->thread, &data->annotation);
}
static void
render_camera_render_internal(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
struct render_camera_parallel_data data = {0};
render_camera_thread_data_t td;
unsigned int scanline;
uint32_t ind;
@@ -720,47 +543,19 @@ render_camera_render_internal(render_camera_t *camera, struct tie_s *tie,
TCOPY(camera_tile_t, tile, 0, result->data, result->ind);
result->ind += sizeof(camera_tile_t);
data.thread.tie = tie;
data.thread.camera = camera;
data.thread.tile = tile;
data.thread.res_buf = &((char *)result->data)[result->ind];
td.tie = tie;
td.camera = camera;
td.tile = tile;
td.res_buf = &((char *)result->data)[result->ind];
scanline = 0;
data.thread.scanline = &scanline;
data.thread.sem_tie_worker = bu_semaphore_register("sem_tie_worker");
data.annotation.scene = annotations;
data.annotation.model_units_per_tie_unit =
model_units_per_tie_unit > 0.0 ? model_units_per_tie_unit : 1.0;
if (annotations)
render_camera_annotation_view(camera,
data.annotation.model_units_per_tie_unit, &data.annotation.view);
td.scanline = &scanline;
td.sem_tie_worker = bu_semaphore_register("sem_tie_worker");
if (annotations)
bu_parallel(render_camera_render_annotations_thread,
camera->thread_num, &data);
else
bu_parallel(render_camera_render_thread, camera->thread_num,
&data.thread);
bu_parallel(render_camera_render_thread, camera->thread_num, &td);
result->ind = ind;
}
void
render_camera_render(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result)
{
render_camera_render_internal(camera, tie, tile, result, NULL, 1.0);
}
void
render_camera_render_annotations(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
render_camera_render_internal(camera, tie, tile, result, annotations,
model_units_per_tie_unit);
return;
}
@@ -825,9 +620,9 @@ render_shader_unload_plugin(render_t *r, const char *name)
{
#ifdef HAVE_DLFCN_H
struct render_shader_s *t, *s = shaders, *meh;
if (!bu_strncmp(s->name, name, 8)) {
if (!bu_strncmp(s->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
t = s->next;
if (r && r->shader && !bu_strncmp(r->shader, name, 8)) {
if (r && r->shader && !bu_strncmp(r->shader, name, RENDER_SHADER_NAME_CMP_LEN)) {
meh = s->next;
while (meh) {
if (render_shader_init(r, meh->name, NULL) != -1)
@@ -846,7 +641,7 @@ LOADED:
}
while (s->next) {
if (!bu_strncmp(s->next->name, name, 8)) {
if (!bu_strncmp(s->next->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
if (r)
render_shader_init(r, s->name, NULL);
if (s->next->dlh)
@@ -856,6 +651,7 @@ LOADED:
bu_free(t, "unload shader");
return 0;
}
s = s->next;
}
bu_log("Could not find shader \"%s\"\n", name);
@@ -871,7 +667,7 @@ render_shader_init(render_t *r, const char *name, const char *buf)
{
struct render_shader_s *s = shaders;
while (s) {
if (!bu_strncmp(s->name, name, 8)) {
if (!bu_strncmp(s->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
s->init(r, buf);
r->shader = s->name;
return 0;
-11
View File
@@ -99,18 +99,7 @@ __BEGIN_DECLS
RENDER_EXPORT extern void render_camera_init(render_camera_t *camera, size_t threads);
RENDER_EXPORT extern void render_camera_free(render_camera_t *camera);
RENDER_EXPORT extern void render_camera_prep(render_camera_t *camera);
/** Center a camera on TIE geometry and any prepared annotations. Returns the
* effective scene radius in TIE coordinates. */
RENDER_EXPORT extern fastf_t render_camera_fit_scene(render_camera_t *camera,
const struct tie_s *tie, const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit);
RENDER_EXPORT extern void render_camera_render(render_camera_t *camera, struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result);
/** Render with an externally owned annotation scene. The coordinate scale
* converts TIE coordinates to database model coordinates. */
RENDER_EXPORT extern void render_camera_render_annotations(render_camera_t *camera,
struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit);
RENDER_EXPORT extern int render_shader_init(render_t *, const char *name, const char *buf);
RENDER_EXPORT extern const char *render_shader_load_plugin(const char *filename);
+10 -86
View File
@@ -40,13 +40,11 @@
#include "vmath.h"
#include "nmg.h"
#include "rt/geom.h"
#include "rt/search.h"
#include "raytrace.h"
#include "rt/tie.h"
#include "adrt.h"
#include "adrt_struct.h"
#include "load_g.h"
static struct bn_tol tol; /* calculation tolerance */
@@ -106,42 +104,6 @@ struct gcv_data {
static struct gcv_data gcvwriter = {{load_nmg_to_adrt_gcvwrite, NULL, NULL}, NULL};
int
adrt_path_has_geometry(struct db_i *database, const char *path)
{
static const char *geometry_search = "! -type comb ! -type annot";
struct directory *root;
if (!database || !path)
return 0;
root = db_lookup(database, path, LOOKUP_QUIET);
/* Let db_walk_tree report a missing path rather than hiding the error. */
if (!root)
return 1;
return db_search(NULL, DB_SEARCH_QUIET, geometry_search, 1, &root,
database, NULL, NULL, NULL) != 0;
}
union tree *
adrt_leaf_tess(struct db_tree_state *tsp, const struct db_full_path *pathp,
struct rt_db_internal *ip, void *client_data)
{
union tree *result;
if (!ip || ip->idb_type != ID_ANNOT)
return rt_booltree_leaf_tess(tsp, pathp, ip, client_data);
/* Annotations are rendered directly and have no NMG representation. */
BU_GET(result, union tree);
RT_TREE_INIT(result);
result->tr_op = OP_NOP;
return result;
}
/* load the region into the tie image */
static void
load_nmg_to_adrt_internal(struct adrt_mesh_s *mesh, struct nmgregion *r)
@@ -191,8 +153,7 @@ load_nmg_to_adrt_internal(struct adrt_mesh_s *mesh, struct nmgregion *r)
NMG_CK_VERTEX(v);
/* convert mm to m */
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord,
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord, 1.0/1000.0);
vert_count++;
}
if (vert_count > 3)
@@ -287,12 +248,9 @@ load_nmg_to_adrt_regstart(struct db_tree_state *ts, const struct db_full_path *p
for (i=0;i<bot->num_faces;i++)
{
VSCALE((*tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]), 1.0/1000.0);
VSCALE((*tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]), 1.0/1000.0);
VSCALE((*tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]), 1.0/1000.0);
TIE_VAL(tie_push)(cur_tie, tribuf, 1, mesh, 0);
}
@@ -340,16 +298,11 @@ load_nmg_to_adrt_gcvwrite(struct nmgregion *r, const struct db_full_path *pathp,
int
load_g_annotations(struct tie_s *tie, const char *db, int argc,
const char **argv, struct adrt_mesh_s **meshes,
struct rt_annot_scene **annotations)
load_g(struct tie_s *tie, const char *db, int argc, const char **argv, struct adrt_mesh_s **meshes)
{
struct model *the_model;
struct bg_tess_tol ttol; /* tessellation tolerance in mm */
struct db_tree_state tree_state; /* includes tol & model */
const char **geometry_paths = NULL;
int geometry_count = 0;
int walk_result = 0;
cur_tie = tie; /* NOTE: cur_tie is a file-scope global; this is not
* reentrant and would need locking to be thread-safe. */
@@ -385,21 +338,13 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
if ((dbip = db_open(db, DB_OPEN_READONLY)) == DBI_NULL) {
perror(db);
bu_log("Unable to open geometry database file (%s)\n", db);
nmg_km(the_model);
return -1;
}
if (db_dirbuild(dbip)) {
bu_log("ERROR: db_dirbuild failed\n");
db_close(dbip);
dbip = DBI_NULL;
nmg_km(the_model);
return -1;
}
if (annotations) {
*annotations = rt_annot_scene_create(dbip, argc, argv, &ttol, &tol);
}
BN_CK_TOL(tree_state.ts_tol);
BG_CK_TESS_TOL(tree_state.ts_ttol);
@@ -417,26 +362,15 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
tribuf[1] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
tribuf[2] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
if (argc > 0) {
geometry_paths = (const char **)bu_calloc((size_t)argc,
sizeof(const char *), "ADRT geometry paths");
for (int i = 0; i < argc; ++i) {
if (adrt_path_has_geometry(dbip, argv[i]))
geometry_paths[geometry_count++] = argv[i];
}
}
if (geometry_count) {
walk_result = db_walk_tree(dbip,
geometry_count, /* number of toplevel regions */
geometry_paths, /* region names */
(void) db_walk_tree(dbip,
argc, /* number of toplevel regions */
argv, /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
load_nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
adrt_leaf_tess, /* leaf func */
rt_booltree_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
}
/* Release dynamic storage */
nmg_km(the_model);
@@ -446,20 +380,10 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
bu_free(tribuf[1], "vert");
bu_free(tribuf[2], "vert");
bu_free(tribuf, "tri");
if (geometry_paths)
bu_free(geometry_paths, "ADRT geometry paths");
TIE_VAL(tie_prep)(cur_tie);
return walk_result;
}
int
load_g(struct tie_s *tie, const char *db, int argc, const char **argv,
struct adrt_mesh_s **meshes)
{
return load_g_annotations(tie, db, argc, argv, meshes, NULL);
return 0;
}
/*
-43
View File
@@ -1,43 +0,0 @@
/* L O A D _ G . H
* BRL-CAD / ADRT
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ADRT_LOAD_G_H
#define ADRT_LOAD_G_H
#include "common.h"
#include "adrt.h"
struct db_i;
struct db_tree_state;
struct db_full_path;
struct rt_db_internal;
union tree;
__BEGIN_DECLS
RENDER_EXPORT extern int adrt_path_has_geometry(struct db_i *dbip,
const char *path);
RENDER_EXPORT extern union tree *adrt_leaf_tess(struct db_tree_state *tsp,
const struct db_full_path *pathp, struct rt_db_internal *ip,
void *client_data);
__END_DECLS
#endif /* ADRT_LOAD_G_H */
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
-16
View File
@@ -1,16 +0,0 @@
brlcad_addexec(adrt_annotation_test annotation.c
"librender;libwdb;librt;libbu;${M_LIBRARY}" TEST)
target_include_directories(adrt_annotation_test PRIVATE
${BRLCAD_SOURCE_DIR}/src/adrt
${BRLCAD_SOURCE_DIR}/src/adrt/librender
)
brlcad_add_test(NAME adrt_annotation_test COMMAND adrt_annotation_test)
cmakefiles(annotation.c CMakeLists.txt)
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
-179
View File
@@ -1,179 +0,0 @@
/* A N N O T A T I O N . C
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "common.h"
#include <math.h>
#include <stdio.h>
#include "bu/app.h"
#include "bu/file.h"
#include "raytrace.h"
#include "rt/primitives/annot.h"
#include "wdb.h"
#include "load_g.h"
#include "librender/camera.h"
static void
write_test_database(struct db_i *dbip)
{
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DISK);
struct rt_annot_internal annot = {0};
point2d_t vertices[2];
struct line_seg line = {0};
void *segments[1];
int reverse[1] = {0};
point_t center = VINIT_ZERO;
struct wmember members;
if (wdbp == RT_WDB_NULL)
bu_exit(1, "unable to open ADRT annotation test database\n");
annot.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(annot.V, 10.0, 0.0, 0.0);
annot.flags = RT_ANNOT_MODEL_SPACE;
VSET(annot.u_vec, 0.0, 1.0, 0.0);
VSET(annot.v_vec, 0.0, 0.0, 1.0);
annot.vert_count = 2;
annot.verts = vertices;
V2SET(vertices[0], -1.0, 0.0);
V2SET(vertices[1], 1.0, 0.0);
annot.ant.count = 1;
annot.ant.reverse = reverse;
annot.ant.segments = segments;
line.magic = CURVE_LSEG_MAGIC;
line.start = 0;
line.end = 1;
segments[0] = &line;
if (rt_annot_validate(&annot, NULL) ||
mk_annot(wdbp, "test.annot", &annot) < 0 ||
mk_sph(wdbp, "test.sph", center, 1.0) < 0)
bu_exit(1, "unable to write ADRT annotation test objects\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("test.annot", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "annotation-only.c", &members, 0, NULL, NULL,
NULL, 0))
bu_exit(1, "unable to write annotation-only test combination\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("test.annot", &members.l, NULL, WMOP_UNION) ||
!mk_addmember("test.sph", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "mixed.c", &members, 0, NULL, NULL, NULL, 0))
bu_exit(1, "unable to write mixed test combination\n");
}
static void
check_geometry_filter(struct db_i *dbip)
{
struct rt_db_internal intern;
union tree *leaf;
if (adrt_path_has_geometry(dbip, "test.annot") ||
adrt_path_has_geometry(dbip, "annotation-only.c") ||
!adrt_path_has_geometry(dbip, "test.sph") ||
!adrt_path_has_geometry(dbip, "mixed.c"))
bu_exit(1, "ADRT annotation geometry filtering failed\n");
RT_DB_INTERNAL_INIT(&intern);
intern.idb_type = ID_ANNOT;
leaf = adrt_leaf_tess(NULL, NULL, &intern, NULL);
if (!leaf || leaf->tr_op != OP_NOP)
bu_exit(1, "ADRT annotation leaf did not become a no-op\n");
db_free_tree(leaf);
}
static void
check_camera_fit(struct db_i *dbip)
{
const char *paths[] = {"test.annot"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
render_camera_t camera = {0};
struct tie_s tie = {0};
fastf_t radius;
const fastf_t combined_radius = 0.5 * sqrt(41.0);
if (!scene || rt_annot_scene_count(scene) != 1)
bu_exit(1, "unable to prepare ADRT annotation test scene\n");
tie.radius = 2.0;
VSET(tie.mid, 1.0, 2.0, 3.0);
radius = render_camera_fit_scene(&camera, &tie, NULL, 1.0);
if (!NEAR_EQUAL(radius, 2.0, BN_TOL_DIST) ||
!VNEAR_EQUAL(camera.focus, tie.mid, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[X], 2.0, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Y], 2.0, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Z], 2.0, BN_TOL_DIST))
bu_exit(1, "ADRT geometry-only camera framing changed\n");
radius = render_camera_fit_scene(&camera, &tie, scene, 10.0);
if (radius <= SMALL_FASTF ||
!NEAR_EQUAL(camera.focus[X], 1.0, BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Y], BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Z], BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[X] - camera.focus[X], radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Y] - camera.focus[Y], radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Z] - camera.focus[Z], radius, BN_TOL_DIST))
bu_exit(1, "ADRT annotation-only camera framing failed: radius=%g "
"focus=(%g %g %g) pos=(%g %g %g)\n", radius,
V3ARGS(camera.focus), V3ARGS(camera.pos));
tie.tri_num = 1;
VSET(tie.amin, -2.0, -2.0, -2.0);
VSET(tie.amax, 0.0, 2.0, 2.0);
radius = render_camera_fit_scene(&camera, &tie, scene, 10.0);
if (!NEAR_EQUAL(radius, combined_radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.focus[X], -0.5, BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Y], BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Z], BN_TOL_DIST))
bu_exit(1, "ADRT combined geometry and annotation framing failed\n");
rt_annot_scene_destroy(scene);
}
int
main(int argc, char **argv)
{
char path[MAXPATHLEN];
struct db_i *dbip;
bu_setprogname(argv[0]);
if (argc != 1)
return 1;
{
FILE *fp = bu_temp_file(path, MAXPATHLEN);
if (!fp)
bu_exit(1, "unable to create ADRT annotation test path\n");
fclose(fp);
}
dbip = db_create(path, 5);
if (dbip == DBI_NULL)
bu_exit(1, "unable to create ADRT annotation test database\n");
write_test_database(dbip);
check_geometry_filter(dbip);
check_camera_fit(dbip);
db_close(dbip);
bu_file_delete(path);
return 0;
}
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+17 -4
View File
@@ -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}
+480 -399
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -64,17 +64,16 @@ ArtTileCallback::on_tile_end(const renderer::Frame* frame, const size_t tile_x,
{
foundation::Tile& t = frame->image().tile(tile_x, tile_y);
const foundation::Tile rgb(t, PixelFormatUInt8);
const foundation::CanvasProperties& properties = frame->image().properties();
// fb_write(fbp, tile_x, tile_y, rgb.get_storage(), rgb.get_size());
// printf("yay!\n");
// printf("%lu %lu \n", tile_x, tile_y);
// printf("%lu %lu \n", rgb.get_width(), rgb.get_height());
for (size_t y = 0; y < rgb.get_height(); y++) {
for (size_t x = 0; x < rgb.get_width(); x++) {
size_t x_coord = tile_x * properties.m_tile_width + x;
size_t y_coord = tile_y * properties.m_tile_height + y;
size_t img_h = properties.m_canvas_height;
fb_write(fbp, (int)x_coord, (int)(img_h - y_coord - 1), rgb.get_storage()+((y * rgb.get_width() * 4) + (x * 4)), 1);
size_t x_coord = tile_x * rgb.get_width() + x;
size_t y_coord = tile_y * rgb.get_height() + y;
size_t img_h = frame->image().properties().m_canvas_height;
fb_write(fbp, (int)x_coord, (int)(img_h - y_coord), rgb.get_storage()+((y * rgb.get_width() * 4) + (x * 4)), 1);
}
}
}
+1 -1
View File
@@ -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... */
+3
View File
@@ -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
View File
@@ -42,6 +42,7 @@ if(BRLCAD_ENABLE_QT AND OPENGL_TARGETS)
Qt6::Widgets
Qt6::OpenGL
Qt6::OpenGLWidgets
${OPENGL_TARGETS}
)
else(Qt6Widgets_FOUND)
qt5_wrap_cpp(moc_srcs ${isst_headers})
+15 -52
View File
@@ -26,7 +26,6 @@
#include <QFile>
#include <QPlainTextEdit>
#include <QTextStream>
#include <vector>
#include "gfile.h"
#include "bu/malloc.h"
@@ -34,7 +33,6 @@
#include "gcv.h"
#include "nmg.h"
#include "rt/geom.h"
#include "load_g.h"
/* Replace the load_g globals from adrt with a struct that is
* passed through the callers. */
@@ -104,8 +102,7 @@ nmg_to_adrt_internal(TIE_3 **tribuf, struct tie_s *cur_tie, struct adrt_mesh_s *
NMG_CK_VERTEX(v);
/* convert mm to m */
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord,
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord, 1.0/1000.0);
vert_count++;
}
if (vert_count > 3)
@@ -188,12 +185,9 @@ nmg_to_adrt_regstart(struct db_tree_state *ts, const struct db_full_path *path,
for (i=0;i<bot->num_faces;i++)
{
VSCALE((*d->tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]), 1.0/1000.0);
VSCALE((*d->tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]), 1.0/1000.0);
VSCALE((*d->tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]), 1.0/1000.0);
TIE_VAL(tie_push)(d->cur_tie, d->tribuf, 1, mesh, 0);
}
@@ -239,13 +233,6 @@ nmg_to_adrt_gcvwrite(struct nmgregion *r, const struct db_full_path *pathp, stru
nmg_to_adrt_internal(d->tribuf, d->cur_tie, mesh, r);
}
GFile::~GFile()
{
closedb();
}
int
GFile::load_g(const char *filename, int argc, const char *argv[])
{
@@ -254,9 +241,6 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
struct bg_tess_tol ttol; /* tessellation tolerance in mm */
struct db_tree_state tree_state; /* includes tol & model */
struct isst_nmg_data d;
std::vector<const char *> geometry_paths;
closedb();
RT_DBTS_INIT(&tree_state);
tree_state.ts_tol = &tol;
@@ -294,8 +278,6 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
}
if (db_dirbuild(dbip)) {
bu_log("ERROR: db_dirbuild failed\n");
db_close(dbip);
dbip = NULL;
return -1;
}
d.dbip = dbip;
@@ -322,30 +304,20 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
tribuf[2] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
d.tribuf = this->tribuf;
annotations = rt_annot_scene_create(dbip, argc, argv, &ttol, &tol);
for (int i = 0; i < argc; ++i) {
if (adrt_path_has_geometry(dbip, argv[i]))
geometry_paths.push_back(argv[i]);
}
if (!geometry_paths.empty()) {
(void) db_walk_tree(dbip,
(int)geometry_paths.size(), /* number of toplevel regions */
geometry_paths.data(), /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
adrt_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
}
(void) db_walk_tree(dbip,
argc, /* number of toplevel regions */
argv, /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
rt_booltree_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
/* Release dynamic storage */
nmg_km(the_model);
rt_vlist_cleanup();
db_close(dbip);
dbip = NULL;
bu_free(tribuf[0], "vert");
bu_free(tribuf[1], "vert");
bu_free(tribuf[2], "vert");
@@ -359,17 +331,7 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
void
GFile::closedb()
{
rt_annot_scene_destroy(annotations);
annotations = NULL;
if (tie) {
TIE_FREE(tie);
bu_free(tie, "free tie");
tie = NULL;
}
if (dbip) {
db_close(dbip);
dbip = NULL;
}
bu_free(tie, "free tie");
current_file.clear();
}
@@ -383,3 +345,4 @@ GFile::closedb()
* End:
* ex: shiftwidth=4 tabstop=8
*/
+5 -8
View File
@@ -32,7 +32,6 @@
extern "C" {
#include "bn/tol.h"
#include "raytrace.h"
#include "rt/primitives/annot.h"
#include "rt/tie.h"
#include "adrt.h"
@@ -44,16 +43,13 @@ class GFile : public QObject
Q_OBJECT
public:
~GFile() override;
int load_g(const char *filename, int argc, const char **argv);
void closedb();
struct tie_s *tie = NULL;
struct adrt_mesh_s *meshes = NULL;
struct db_i *dbip = NULL;
struct rt_annot_scene *annotations = NULL;
TIE_3 **tribuf = NULL;
struct tie_s *tie;
struct adrt_mesh_s *meshes;
struct db_i *dbip;
TIE_3 **tribuf;
QString current_file;
};
@@ -70,3 +66,4 @@ class GFile : public QObject
* End:
* ex: shiftwidth=4 tabstop=8
*/
+2 -4
View File
@@ -28,9 +28,6 @@
int
ISSTApp::load_g(const char *filename, int argc, const char *argv[])
{
/* Synchronize with the render thread before GFile releases the old
* annotation scene and TIE data. */
w.canvas->clear_tie();
if (g.load_g(filename, argc, argv)) {
w.statusBar()->showMessage("open failed");
return -1;
@@ -38,7 +35,7 @@ ISSTApp::load_g(const char *filename, int argc, const char *argv[])
// The OpenGL widget manages the rendering, so let it know about the
// TIE data structure associated with the current model
w.canvas->set_tie(g.tie, g.annotations);
w.canvas->set_tie(g.tie);
return 0;
}
@@ -54,3 +51,4 @@ ISSTApp::load_g(const char *filename, int argc, const char *argv[])
* End:
* ex: shiftwidth=4 tabstop=8
*/
+4 -1
View File
@@ -36,14 +36,16 @@
class ISSTApp : public QApplication
{
Q_OBJECT
public:
ISSTApp(int &argc, char *argv[]) :QApplication(argc, argv) {};
~ISSTApp() {};
int load_g(const char *filename, int argc, const char **argv);
GFile g;
ISST_MainWindow w;
GFile g;
};
#endif // ISSTAPP_H
@@ -58,3 +60,4 @@ class ISSTApp : public QApplication
* End:
* ex: shiftwidth=4 tabstop=8
*/
+9 -28
View File
@@ -159,13 +159,8 @@ void TIERenderer::render()
m_grabCond.wait(&m_grabMutex);
QMutexLocker lock(&m_renderMutex);
m_grabMutex.unlock();
/* The GUI may clear a scene while a queued render is waiting for the
* context. Return the context before leaving that stale request. */
if (m_exiting || !tie) {
ctx->moveToThread(qGuiApp->thread());
if (m_exiting)
return;
}
Q_ASSERT(ctx->thread() == QThread::currentThread());
@@ -188,8 +183,7 @@ void TIERenderer::render()
// Core TIE render
render_camera_prep(&camera);
render_camera_render_annotations(&camera, tie, &tile, &buffer_image,
annotations, ADRT_MODEL_UNITS_PER_TIE_UNIT);
render_camera_render(&camera, tie, &tile, &buffer_image);
glDisable(GL_LIGHTING);
@@ -303,31 +297,17 @@ void isstGL::grabContext()
}
void
isstGL::clear_tie()
isstGL::set_tie(struct tie_s *in_tie)
{
m_renderer->lockRenderer();
m_renderer->tie = NULL;
m_renderer->annotations = NULL;
m_renderer->changed = false;
m_renderer->unlockRenderer();
}
void
isstGL::set_tie(struct tie_s *in_tie, struct rt_annot_scene *annotations)
{
m_renderer->lockRenderer();
m_renderer->tie = in_tie;
m_renderer->annotations = annotations;
render_camera_fit_scene(&m_renderer->camera, m_renderer->tie, annotations,
ADRT_MODEL_UNITS_PER_TIE_UNIT);
// Initialize the camera position
VSETALL(m_renderer->camera.pos, m_renderer->tie->radius);
VMOVE(m_renderer->camera.focus, m_renderer->tie->mid);
// Record the initial settings for use in subsequent calculations
VMOVE(m_renderer->camera_pos_init, m_renderer->camera.pos);
VMOVE(m_renderer->camera_focus_init, m_renderer->camera.focus);
m_renderer->changed = true;
m_renderer->unlockRenderer();
VSETALL(m_renderer->camera_pos_init, m_renderer->tie->radius);
VMOVE(m_renderer->camera_focus_init, m_renderer->tie->mid);
// Having just loaded a new TIE scene,
// we need a new image
@@ -389,3 +369,4 @@ void isstGL::save_image() {
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+2 -3
View File
@@ -77,7 +77,6 @@ class TIERenderer : public QObject, protected QOpenGLFunctions
public:
struct tie_s *tie = NULL; // From parent app
struct rt_annot_scene *annotations = NULL; // Owned by the parent app
struct render_camera_s camera;
vect_t camera_pos_init;
vect_t camera_focus_init;
@@ -114,8 +113,7 @@ class isstGL : public QOpenGLWidget
explicit isstGL(QWidget *parent = nullptr);
~isstGL();
void clear_tie();
void set_tie(struct tie_s *in_tie, struct rt_annot_scene *annotations);
void set_tie(struct tie_s *in_tie);
void save_image();
@@ -155,3 +153,4 @@ class isstGL : public QOpenGLWidget
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+2 -1
View File
@@ -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
+3 -196
View File
@@ -480,22 +480,6 @@ _nirt_get_resource(struct nirt_state *nss)
}
static void
_nirt_annotation_result_clear(struct nirt_state *nss)
{
delete nss->i->vals->annotation;
nss->i->vals->annotation = NULL;
}
static void
_nirt_annotation_scene_clear(struct nirt_state *nss)
{
rt_annot_scene_destroy(nss->i->annotation_scene);
nss->i->annotation_scene = NULL;
}
int
_nirt_raytrace_prep(struct nirt_state *nss)
{
@@ -533,17 +517,11 @@ _nirt_raytrace_prep(struct nirt_state *nss)
bu_free(attrs, "objs");
return -1;
}
bu_free(objs, "objs");
bu_free(attrs, "objs");
nmsg(nss, "Prepping the geometry...\n");
rt_prep(nss->i->ap->a_rt_i);
_nirt_annotation_scene_clear(nss);
if (nss->i->use_annotations) {
nss->i->annotation_scene = rt_annot_scene_create(nss->i->dbip,
ocnt, objs, &nss->i->ap->a_rt_i->rti_ttol,
&nss->i->ap->a_rt_i->rti_tol);
}
bu_free(objs, "objs");
nmsg(nss, "%s ", (nss->i->active_paths.size() == 1) ? "Object" : "Objects");
for (size_t i = 0; i < nss->i->active_paths.size(); i++) {
if (i == 0) {
@@ -982,17 +960,6 @@ _nirt_print_fmt_substr(struct nirt_state *nss, struct bu_vls *ostr, const char *
nirt_print_key("a", r->a);
nirt_print_key("e", r->e);
if (r->annotation) {
nirt_print_key("annot_path", r->annotation->path.c_str());
nirt_print_key("annot_segment", r->annotation->segment);
nirt_print_key("annot_role", r->annotation->role.c_str());
nirt_print_key("annot_x", r->annotation->point[X] * base2local);
nirt_print_key("annot_y", r->annotation->point[Y] * base2local);
nirt_print_key("annot_z", r->annotation->point[Z] * base2local);
nirt_print_key("annot_d", r->annotation->d * base2local);
nirt_print_key("annot_visible", r->annotation->visible);
}
if (!r->seg || r->seg->type == 0) return;
if (r->seg->type == NIRT_PARTITION_SEG || r->seg->type == NIRT_ALL_SEG) {
@@ -1089,9 +1056,6 @@ _nirt_report(struct nirt_state *nss, char type, struct nirt_output_record *r)
std::vector<std::pair<std::string,std::string> >::iterator f_it;
switch (type) {
case 'a':
fmt_vect = &nss->i->fmt.annotation;
break;
case 'r':
fmt_vect = &nss->i->fmt.ray;
break;
@@ -1127,75 +1091,6 @@ _nirt_report(struct nirt_state *nss, char type, struct nirt_output_record *r)
}
static const char *
_nirt_annotation_role(uint32_t role)
{
switch (role) {
case RT_ANNOT_ROLE_UNSPECIFIED:
return "unspecified";
case RT_ANNOT_ROLE_GEOMETRY:
return "geometry";
case RT_ANNOT_ROLE_TEXT:
return "text";
case RT_ANNOT_ROLE_LEADER:
return "leader";
case RT_ANNOT_ROLE_EXTENSION:
return "extension";
case RT_ANNOT_ROLE_DIMENSION:
return "dimension";
case RT_ANNOT_ROLE_ARROWHEAD:
return "arrowhead";
case RT_ANNOT_ROLE_SYMBOL:
return "symbol";
case RT_ANNOT_ROLE_MASK:
return "mask";
case RT_ANNOT_ROLE_CENTERMARK:
return "centermark";
case RT_ANNOT_ROLE_PLACEHOLDER:
return "placeholder";
case RT_ANNOT_ROLE_TEXT_DECORATION:
return "text-decoration";
default:
return "unknown";
}
}
static void
_nirt_report_annotations(struct nirt_state *nss)
{
const size_t count = rt_annot_scene_query_model(nss->i->annotation_scene,
&nss->i->ap->a_ray, nss->i->first_solid_distance, NULL, 0);
if (!count)
return;
std::vector<struct rt_annot_hit> hits(count);
const size_t found = rt_annot_scene_query_model(nss->i->annotation_scene,
&nss->i->ap->a_ray, nss->i->first_solid_distance, hits.data(),
hits.size());
nirt_seg *saved_segment = nss->i->vals->seg;
nss->i->vals->seg = NULL;
for (size_t i = 0; i < std::min(found, hits.size()); ++i) {
const struct rt_annot_hit &hit = hits[i];
if (hit.segment > (size_t)std::numeric_limits<int>::max()) {
nerr(nss, "Annotation segment index is too large for NIRT reporting\n");
continue;
}
if (!nss->i->vals->annotation)
nss->i->vals->annotation = new nirt_annotation_record;
nirt_annotation_record *record = nss->i->vals->annotation;
record->path = hit.path ? hit.path : "";
record->segment = (int)hit.segment;
record->role = _nirt_annotation_role(hit.role);
VMOVE(record->point, hit.point);
record->d = d_calc(nss, record->point);
record->visible = hit.visible;
_nirt_report(nss, 'a', nss->i->vals);
}
nss->i->vals->seg = saved_segment;
}
/************************
* Raytracing Callbacks *
************************/
@@ -1318,21 +1213,6 @@ _nirt_if_hit(struct application *ap, struct partition *part_head, struct seg *UN
int ev_odd = 1; /* first partition is colored as "odd" */
point_t out_old = VINIT_ZERO;
double d_out_old = 0.0;
nss->i->first_solid_distance = INFINITY;
for (part = part_head->pt_forw; part != part_head; part = part->pt_forw) {
if (part->pt_regionp->reg_aircode)
continue;
if (part->pt_inhit->hit_dist >= 0.0) {
nss->i->first_solid_distance = part->pt_inhit->hit_dist;
break;
}
if (part->pt_outhit->hit_dist >= 0.0) {
nss->i->first_solid_distance = part->pt_outhit->hit_dist;
break;
}
}
nirt_seg *s = new nirt_seg;
if (vals->seg) {
delete vals->seg;
@@ -1669,10 +1549,9 @@ static const struct nirt_cmd_desc nirt_descs[] = {
{ "s", "shoot a ray at the target", NULL },
{ "backout", "back out of model", NULL },
{ "useair", "set/query use of air", "<0|1|2|...>" },
{ "useannot", "set/query annotation reporting", "<0|1>" },
{ "units", "set/query local units", "<mm|cm|m|in|ft>" },
{ "overlap_claims", "set/query overlap rebuilding/retention", "<0|1|2|3>" },
{ "fmt", "set/query output formats", "{arhpfmog} format item item ..." },
{ "fmt", "set/query output formats", "{rhpfmog} format item item ..." },
{ "plotfile", "designate an output file to hold plot data", "file" },
{ "print", "query an output item", "item" },
{ "debug", "set/query nirt debug flags", "[-h] [-l [lib]] [-C [lib]] [-V [lib] [val]] [lib [flag]]" },
@@ -1949,9 +1828,6 @@ _nirt_cmd_shoot(void *ns, int argc, const char **UNUSED(argv))
return -1;
}
_nirt_annotation_result_clear(nss);
nss->i->first_solid_distance = INFINITY;
/* If we have no active rtip, we don't need to prep or shoot */
if (!_nirt_get_rtip(nss)) {
return 0;
@@ -1984,10 +1860,9 @@ _nirt_cmd_shoot(void *ns, int argc, const char **UNUSED(argv))
nss->i->ap->a_ray.r_pt[2] * nss->i->base2local,
V3ARGS(nss->i->ap->a_ray.r_dir));
// TODO - any necessary initialization for data collection by callbacks
_nirt_init_ovlp(nss);
(void)rt_shootray(nss->i->ap);
if (nss->i->use_annotations)
_nirt_report_annotations(nss);
// Undo backout
for (i = 0; i < 3; ++i) {
@@ -2090,45 +1965,6 @@ _nirt_cmd_use_air(void *ns, int argc, const char *argv[])
}
extern "C" int
_nirt_cmd_use_annotations(void *ns, int argc, const char *argv[])
{
struct nirt_state *nss = (struct nirt_state *)ns;
struct bu_vls optparse_msg = BU_VLS_INIT_ZERO;
int enabled = 0;
if (!ns) return -1;
if (argc == 1) {
nout(nss, "use_annotations = %d\n", nss->i->use_annotations);
return 0;
}
if (argc > 2) {
nerr(nss, "Usage: useannot %s\n", _nirt_get_desc_args("useannot"));
return -1;
}
if (bu_opt_int(&optparse_msg, 1, (const char **)&argv[1],
(void *)&enabled) == -1) {
nerr(nss, "Error: bu_opt value read failure: %s\n\nUsage: useannot %s\n",
bu_vls_cstr(&optparse_msg), _nirt_get_desc_args("useannot"));
bu_vls_free(&optparse_msg);
return -1;
}
bu_vls_free(&optparse_msg);
if (enabled != 0 && enabled != 1) {
nerr(nss, "useannot must be set to 0 (off) or 1 (on)\n");
return -1;
}
if (enabled != nss->i->use_annotations) {
nss->i->use_annotations = enabled;
_nirt_annotation_scene_clear(nss);
_nirt_annotation_result_clear(nss);
if (enabled)
nss->i->need_reprep = 1;
}
return 0;
}
extern "C" int
_nirt_cmd_units(void *ns, int argc, const char *argv[])
{
@@ -2249,9 +2085,6 @@ _nirt_cmd_format_output(void *ns, int argc, const char **argv)
// print out current fmt str for the specified type
struct bu_vls ostr = BU_VLS_INIT_ZERO;
switch (type) {
case 'a':
_nirt_print_fmt_str(&ostr, nss->i->fmt.annotation);
break;
case 'r':
_nirt_print_fmt_str(&ostr, nss->i->fmt.ray);
break;
@@ -2358,10 +2191,6 @@ _nirt_cmd_format_output(void *ns, int argc, const char **argv)
* formatting instructions with the new. */
set_fmt:
switch (type) {
case 'a':
nss->i->fmt.annotation.clear();
nss->i->fmt.annotation = fmt_tmp;
break;
case 'r':
nss->i->fmt.ray.clear();
nss->i->fmt.ray = fmt_tmp;
@@ -2714,7 +2543,6 @@ _nirt_cmd_state(void *ns, int argc, const char *argv[])
bu_vls_printf(&dumpstr, "xyz %g %g %g\n", V3ARGS(nss->i->vals->orig));
bu_vls_printf(&dumpstr, "dir %g %g %g\n", V3ARGS(nss->i->vals->dir));
bu_vls_printf(&dumpstr, "useair %d\n", nss->i->use_air);
bu_vls_printf(&dumpstr, "useannot %d\n", nss->i->use_annotations);
bu_vls_printf(&dumpstr, "units %s\n", bu_units_string(nss->i->local2base));
bu_vls_printf(&dumpstr, "overlap_claims ");
switch (nss->i->overlap_claims) {
@@ -2735,8 +2563,6 @@ _nirt_cmd_state(void *ns, int argc, const char *argv[])
break;
}
struct bu_vls ostr = BU_VLS_INIT_ZERO;
_nirt_print_fmt_cmd(&ostr, 'a', nss->i->fmt.annotation);
bu_vls_printf(&dumpstr, "%s", bu_vls_cstr(&ostr));
_nirt_print_fmt_cmd(&ostr, 'r', nss->i->fmt.ray);
bu_vls_printf(&dumpstr, "%s", bu_vls_cstr(&ostr));
_nirt_print_fmt_cmd(&ostr, 'h', nss->i->fmt.head);
@@ -2791,7 +2617,6 @@ const struct bu_cmdtab _libanalyze_nirt_cmds[] = {
{ "state", _nirt_cmd_state},
{ "units", _nirt_cmd_units},
{ "useair", _nirt_cmd_use_air},
{ "useannot", _nirt_cmd_use_annotations},
{ "xyz", _nirt_cmd_target_coor},
{ (char *)NULL, NULL}
};
@@ -2958,14 +2783,6 @@ static const char *nirt_cmd_tbl_defs =
"z_dir, FNOUNIT, Ray direction unit vector z component,"
"a, FNOUNIT, Azimuth,"
"e, FNOUNIT, Elevation,"
"annot_path, STRING, Annotation database path,"
"annot_segment, INT, Annotation segment index,"
"annot_role, STRING, Annotation segment role,"
"annot_x, FLOAT, Annotation hit X coordinate,"
"annot_y, FLOAT, Annotation hit Y coordinate,"
"annot_z, FLOAT, Annotation hit Z coordinate,"
"annot_d, FLOAT, Annotation hit gridplane distance,"
"annot_visible, INT, Annotation hit visibility,"
"x_in, FLOAT, ,"
"y_in, FLOAT, ,"
"z_in, FLOAT, ,"
@@ -3078,7 +2895,6 @@ nirt_init(struct nirt_state *ns)
n->base2local = 0.0;
n->local2base = 0.0;
n->use_air = 0;
n->use_annotations = 0;
n->backout = 1;
n->b_state = false;
@@ -3093,8 +2909,6 @@ nirt_init(struct nirt_state *ns)
BU_GET(n->res_air, struct resource);
n->rtip = RTI_NULL;
n->rtip_air = RTI_NULL;
n->annotation_scene = NULL;
n->first_solid_distance = INFINITY;
n->need_reprep = 1;
BU_GET(n->val_types, struct bu_attribute_value_set);
@@ -3108,7 +2922,6 @@ nirt_init(struct nirt_state *ns)
n->vals->h = 0.0;
n->vals->v = 0.0;
n->vals->d_orig = 0.0;
n->vals->annotation = NULL;
n->vals->seg = NULL;
_nirt_diff_create(ns);
@@ -3212,8 +3025,6 @@ nirt_clear_dbip(struct nirt_state *ns)
{
if (!ns) return -1;
_nirt_annotation_scene_clear(ns);
_nirt_annotation_result_clear(ns);
db_close(ns->i->dbip);
ns->i->dbip = NULL;
@@ -3234,9 +3045,6 @@ void
nirt_destroy(struct nirt_state *ns)
{
if (!ns) return;
_nirt_annotation_scene_clear(ns);
_nirt_annotation_result_clear(ns);
bu_vls_free(&ns->nirt_cmd);
bu_vls_free(ns->i->err);
bu_vls_free(ns->i->msg);
@@ -3249,7 +3057,6 @@ nirt_destroy(struct nirt_state *ns)
db_close(ns->i->dbip);
delete ns->i->vals->seg;
BU_PUT(ns->i->vals, struct nirt_output_record);
BU_PUT(ns->i->res, struct resource);
+1 -17
View File
@@ -83,7 +83,7 @@ extern "C" b_off_t ftello(FILE *);
#define NIRT_OVLP_RETAIN 3
#define NIRT_PRINTF_SPECIFIERS "difeEgGs"
#define NIRT_OUTPUT_TYPE_SPECIFIERS "arhpfmog"
#define NIRT_OUTPUT_TYPE_SPECIFIERS "rhpfmog"
#define HELPFLAG "--print-help"
#define PURPOSEFLAG "--print-purpose"
@@ -274,16 +274,6 @@ class nirt_seg {
}
};
class nirt_annotation_record {
public:
std::string path;
int segment;
std::string role;
point_t point;
fastf_t d;
int visible;
};
/* This record structure doesn't have to correspond exactly to the above list
* of available values, but it needs to retain sufficient information to
* support the ability to generate all of them upon request. */
@@ -296,7 +286,6 @@ struct nirt_output_record {
fastf_t a;
fastf_t e;
struct nirt_overlap ovlp_list;
nirt_annotation_record *annotation;
nirt_seg *seg;
};
@@ -305,7 +294,6 @@ struct nirt_diff_state;
class nirt_fmt_state {
public:
std::vector<std::pair<std::string,std::string> > annotation;
std::vector<std::pair<std::string,std::string> > ray;
std::vector<std::pair<std::string,std::string> > head;
std::vector<std::pair<std::string,std::string> > part;
@@ -315,7 +303,6 @@ class nirt_fmt_state {
std::vector<std::pair<std::string,std::string> > gap;
void clear() {
annotation.clear();
ray.clear();
head.clear();
part.clear();
@@ -360,7 +347,6 @@ struct nirt_state_impl {
int backout;
int overlap_claims;
int use_air;
int use_annotations;
std::set<std::string> attrs; // active attributes
std::vector<std::string> active_paths; // active paths for raytracer
struct nirt_output_record *vals;
@@ -382,8 +368,6 @@ struct nirt_state_impl {
struct resource *res;
struct rt_i *rtip_air;
struct resource *res_air;
struct rt_annot_scene *annotation_scene;
fastf_t first_solid_distance;
int need_reprep;
/* internal format specifier arrays */
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -1,9 +1,9 @@
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)
brlcad_addexec(analyze_nirt_annotations nirt_annotations.cpp
"libanalyze;libwdb;librt;libbu" TEST_USESDATA)
brlcad_add_test(NAME analyze_nirt_annotations COMMAND analyze_nirt_annotations)
#####################################
# analyze_densities testing #
@@ -16,6 +16,7 @@ brlcad_add_test(NAME analyze_densities_std COMMAND analyze_densities std)
cmakefiles(
CMakeLists.txt
arbs.g
contour.cpp
raydiff.g
)
+425
View File
@@ -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, &params);
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
*/
-191
View File
@@ -1,191 +0,0 @@
/* N I R T _ A N N O T A T I O N S . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by the
* U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "common.h"
#include <cstdio>
#include <cstdlib>
#include "analyze.h"
#include "bu/app.h"
#include "bu/file.h"
#include "bu/malloc.h"
#include "raytrace.h"
#include "wdb.h"
static constexpr fastf_t FRONT_ANNOTATION_X = 18.0;
static constexpr fastf_t REAR_ANNOTATION_X = 10.0;
static constexpr fastf_t SCREEN_ANNOTATION_X = 19.0;
static constexpr fastf_t OCCLUDER_MINIMUM_X = 15.0;
static constexpr fastf_t OCCLUDER_MAXIMUM_X = 16.0;
static constexpr fastf_t RAY_ORIGIN_X = 20.0;
static constexpr fastf_t TEST_HALF_EXTENT = 1.0;
static int
capture_output(struct nirt_state *state, void *data)
{
struct bu_vls current = BU_VLS_INIT_ZERO;
struct bu_vls *captured = (struct bu_vls *)data;
nirt_log(&current, state, NIRT_OUT);
bu_vls_vlscat(captured, &current);
bu_vls_free(&current);
return 0;
}
static void
write_annotation(struct rt_wdb *wdbp, const char *name, fastf_t x,
bool screen_space)
{
struct rt_annot_internal annotation = {};
struct line_seg line = {};
struct rt_annot_seg_style style = {};
int reverse = 0;
void *segment = &line;
point2d_t vertices[2] = {{0.0, 0.0}, {TEST_HALF_EXTENT, 0.0}};
annotation.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(annotation.V, x, 0.0, 0.0);
annotation.flags = screen_space ? RT_ANNOT_SCREEN_SPACE :
RT_ANNOT_MODEL_SPACE;
VSET(annotation.u_vec, 0.0, 1.0, 0.0);
VSET(annotation.v_vec, 0.0, 0.0, 1.0);
annotation.vert_count = 2;
annotation.verts = vertices;
annotation.ant.count = 1;
annotation.ant.reverse = &reverse;
annotation.ant.segments = &segment;
annotation.styles = &style;
line.magic = CURVE_LSEG_MAGIC;
line.start = 0;
line.end = 1;
style.role = RT_ANNOT_ROLE_LEADER;
style.line_pattern = RT_ANNOT_LINE_CONTINUOUS;
if (rt_annot_validate(&annotation, NULL) ||
mk_annot(wdbp, name, &annotation) < 0)
bu_exit(EXIT_FAILURE, "unable to write %s\n", name);
}
static void
clear_standard_formats(struct nirt_state *state)
{
const char *commands[] = {
"fmt r \"\"", "fmt h \"\"", "fmt p \"\"", "fmt f \"\"",
"fmt m \"\"", "fmt o \"\"", "fmt g \"\""
};
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i)
if (nirt_exec(state, commands[i]))
bu_exit(EXIT_FAILURE, "NIRT rejected command: %s\n", commands[i]);
}
static void
get_output(struct bu_vls *output, struct bu_vls *captured)
{
bu_vls_sprintf(output, "%s", bu_vls_cstr(captured));
bu_vls_trunc(captured, 0);
}
int
main(int argc, char **argv)
{
char path[MAXPATHLEN];
struct db_i *dbip;
struct rt_wdb *wdbp;
struct nirt_state *state;
struct bu_vls captured = BU_VLS_INIT_ZERO;
struct bu_vls output = BU_VLS_INIT_ZERO;
struct wmember members;
point_t minimum, maximum;
bu_setprogname(argv[0]);
if (argc != 1)
return EXIT_FAILURE;
{
FILE *fp = bu_temp_file(path, sizeof(path));
if (!fp)
bu_exit(EXIT_FAILURE, "unable to create temporary database path\n");
fclose(fp);
}
dbip = db_create(path, 5);
if (!dbip)
bu_exit(EXIT_FAILURE, "unable to create temporary database\n");
wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DISK);
if (!wdbp)
bu_exit(EXIT_FAILURE, "unable to open temporary database for writing\n");
write_annotation(wdbp, "front.annot", FRONT_ANNOTATION_X, false);
write_annotation(wdbp, "rear.annot", REAR_ANNOTATION_X, false);
write_annotation(wdbp, "screen.annot", SCREEN_ANNOTATION_X, true);
VSET(minimum, OCCLUDER_MINIMUM_X, -TEST_HALF_EXTENT,
-TEST_HALF_EXTENT);
VSET(maximum, OCCLUDER_MAXIMUM_X, TEST_HALF_EXTENT,
TEST_HALF_EXTENT);
if (mk_rpp(wdbp, "occluder.s", minimum, maximum) < 0)
bu_exit(EXIT_FAILURE, "unable to write occluder\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("occluder.s", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "occluder.r", &members, 1, NULL, NULL, NULL, 0))
bu_exit(EXIT_FAILURE, "unable to write occluding region\n");
BU_GET(state, struct nirt_state);
if (nirt_init(state))
bu_exit(EXIT_FAILURE, "unable to initialize NIRT\n");
nirt_udata(state, &captured);
nirt_hook(state, capture_output, NIRT_OUT);
if (nirt_exec(state,
"draw front.annot rear.annot screen.annot occluder.r") ||
nirt_init_dbip(state, dbip))
bu_exit(EXIT_FAILURE, "unable to prepare NIRT test database\n");
clear_standard_formats(state);
if (nirt_exec(state,
"fmt a \"%s|%d|%s|%.1f|%.1f|%d\\n\" annot_path annot_segment annot_role annot_x annot_d annot_visible"))
bu_exit(EXIT_FAILURE, "unable to set annotation report format\n");
struct bu_vls shot = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&shot, "backout 0;xyz %g 0 0;dir -1 0 0;s",
RAY_ORIGIN_X);
if (nirt_exec(state, bu_vls_cstr(&shot)))
bu_exit(EXIT_FAILURE, "unable to shoot default NIRT ray\n");
bu_vls_free(&shot);
get_output(&output, &captured);
if (bu_vls_strlen(&output))
bu_exit(EXIT_FAILURE,
"annotations were reported without useannot: %s\n",
bu_vls_cstr(&output));
if (nirt_exec(state, "useannot 1;s"))
bu_exit(EXIT_FAILURE, "unable to shoot annotation NIRT ray\n");
get_output(&output, &captured);
struct bu_vls expected = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&expected,
"/front.annot|0|leader|%.1f|%.1f|1\n"
"/rear.annot|0|leader|%.1f|%.1f|0\n",
FRONT_ANNOTATION_X, FRONT_ANNOTATION_X,
REAR_ANNOTATION_X, REAR_ANNOTATION_X);
if (!BU_STR_EQUAL(bu_vls_cstr(&output), bu_vls_cstr(&expected)))
bu_exit(EXIT_FAILURE, "unexpected annotation report:\n%s",
bu_vls_cstr(&output));
bu_vls_free(&expected);
nirt_destroy(state);
BU_PUT(state, struct nirt_state);
bu_vls_free(&captured);
bu_vls_free(&output);
db_close(dbip);
bu_file_delete(path);
return EXIT_SUCCESS;
}
+6 -12
View File
@@ -419,7 +419,7 @@ protected:
ListNode* AllocListNode();
void FreeListNode(ListNode* a_listNode);
bool Overlap(Rect* a_rectA, Rect* a_rectB) const;
bool Intersect(const Rect* a_rect, const Ray* a_ray) const;
bool Intersect(Rect* a_rect, Ray* a_ray) const;
void ReInsert(Node* a_node, ListNode** a_listNode);
bool Search(Node* a_node, Rect* a_rect, int& a_foundCount, std::function<bool(const DataType&, void *)> callback, void *a_context) const;
void RemoveAllRec(Node* a_node);
@@ -433,15 +433,14 @@ protected:
size_t CheckNodes(Node *a_nodeA, Node *a_nodeB, std::set<DataType> *a_result, std::set<DataType> *b_result);
size_t CheckNodes(Node *a_nodeA, Node *a_nodeB, std::set<std::pair<DataType, DataType>> *result);
size_t IsectNode(const Node *a_node, const Ray *a_ray,
std::set<DataType> *result) const;
size_t IsectNode(Node *a_node, Ray *a_ray, std::set<DataType> *result);
Node* m_root = nullptr; ///< Root of tree
ElementTypeReal m_unitSphereVolume = kElementTypeRealZero; ///< Unit sphere constant for required number of dimensions
public:
// Find the leaves intersected by a ray, returning the data from the leaves in a set.
size_t Intersects(const Ray *a_ray, std::set<DataType> *result) const;
size_t Intersects(Ray *a_ray, std::set<DataType> *result);
void plot(const char *fname);
void plot2d(const char *fname);
@@ -1552,7 +1551,7 @@ bool RTREE_QUAL::Overlap(Rect* a_rectA, Rect* a_rectB) const
// Decide whether a ray intersect a Rect.
RTREE_TEMPLATE
bool RTREE_QUAL::Intersect(const Rect* a_rect, const Ray* a_ray) const
bool RTREE_QUAL::Intersect(Rect* a_rect, Ray* a_ray) const
{
RTreeAssert(a_rect);
if (kNumDimensions != 3) return false;
@@ -1686,8 +1685,7 @@ size_t RTREE_QUAL::Overlaps(RTree &other, std::set<std::pair<DataType, DataType>
RTREE_TEMPLATE
size_t RTREE_QUAL::IsectNode(const Node *a_node, const Ray *a_ray,
std::set<DataType> *result) const
size_t RTREE_QUAL::IsectNode(Node *a_node, Ray *a_ray, std::set<DataType> *result)
{
size_t ocnt = 0;
for (int index = 0; index < a_node->m_count; ++index) {
@@ -1705,8 +1703,7 @@ size_t RTREE_QUAL::IsectNode(const Node *a_node, const Ray *a_ray,
// Loosely based on the idea from the openNURBS code to search two R-trees for all pairs elements whose bounding boxes overlap.
RTREE_TEMPLATE
size_t RTREE_QUAL::Intersects(const Ray *a_ray,
std::set<DataType> *result) const
size_t RTREE_QUAL::Intersects(Ray *a_ray, std::set<DataType> *result)
{
if (!m_root) return 0;
size_t ovlp_cnt = IsectNode(m_root, a_ray, result);
@@ -1803,9 +1800,6 @@ void RTREE_QUAL::plot2d(const char *fname)
}
#undef BB_PLOT2D
#undef BB_PLOT
#undef TREE_LEAF_FACE_3D
#undef RTREE_TEMPLATE
#undef RTREE_QUAL
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 &parameters)
{
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
*/
+26 -1
View File
@@ -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)
+70
View File
@@ -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(&center, &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(&center, &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;
}
+69
View File
@@ -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;
}
+123
View File
@@ -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();
}
+200
View File
@@ -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
*/
+106
View File
@@ -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();
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 );
+74
View File
@@ -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");
+149
View File
@@ -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, &degree_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(&quotient, &remainder, &dividend, &constant_divisor);
if (!poly_close(&quotient, 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(&quotient, &remainder, &linear, &higher_divisor);
if (!poly_close(&quotient, 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}
+14
View File
@@ -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)) {
+18 -12
View File
@@ -250,22 +250,28 @@ brep_translate_scv(
return -1;
}
double *cv = NULL;
if (nurbsSurface) {
cv = nurbsSurface->CV(i, j);
} else {
if (!nurbsSurface)
return -2;
}
if (cv) {
ON_3dPoint newPt;
newPt.x = cv[X] + dx;
newPt.y = cv[Y] + dy;
newPt.z = cv[Z] + dz;
nurbsSurface->SetCV(i, j, newPt);
} else {
ON_3dPoint point;
if (!nurbsSurface->GetCV(i, j, point))
return -3;
point.x += dx;
point.y += dy;
point.z += dz;
bool updated;
if (nurbsSurface->IsRational()) {
const double weight = nurbsSurface->Weight(i, j);
updated = nurbsSurface->SetCV(i, j,
ON_4dPoint(point.x * weight, point.y * weight,
point.z * weight, weight));
} else {
updated = nurbsSurface->SetCV(i, j, point);
}
if (!updated)
return -3;
return 0;
}
+31 -17
View File
@@ -388,31 +388,45 @@ mem_sysctl(int type, size_t *memsz)
return -2;
#if defined(__FreeBSD__) && defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTL)
static const char *const page_size_name = "hw.pagesize";
static const char *const total_memory_name = "hw.physmem";
static const char *const free_pages_name = "vm.stats.vm.v_free_count";
static const char *const available_page_names[] = {
"vm.stats.vm.v_free_count",
"vm.stats.vm.v_inactive_count",
"vm.stats.vm.v_cache_count"
};
uint64_t page_size = 0;
size_t value_size = sizeof(page_size);
if (sysctlbyname(page_size_name, &page_size, &value_size, NULL, 0) != 0 ||
page_size == 0)
return -1;
if (type == BU_MEM_PAGE_SIZE)
return mem_size_from_uint64(page_size, memsz);
return mem_page_size(memsz);
uint64_t memory = 0;
value_size = sizeof(memory);
const char *memory_name = total_memory_name;
if (type == BU_MEM_AVAIL) {
memory_name = free_pages_name;
if (sysctlbyname(memory_name, &memory, &value_size, NULL, 0) != 0 ||
memory > UINT64_MAX / page_size)
return -1;
memory *= page_size;
} else if (sysctlbyname(memory_name, &memory, &value_size, NULL, 0) != 0) {
return -1;
/* FreeBSD's free queue omits clean inactive and cache pages that the VM
* can reclaim without paging another process. Counting only v_free_count
* makes an otherwise idle system appear memory-starved and needlessly
* disables memory-aware parallel work. */
for (size_t i = 0; i < sizeof(available_page_names) / sizeof(available_page_names[0]); i++) {
uint32_t pages = 0;
size_t value_size = sizeof(pages);
if (sysctlbyname(available_page_names[i], &pages, &value_size,
NULL, 0) != 0 || value_size != sizeof(pages)) {
/* v_free_count is required. Cache accounting is absent on some
* supported FreeBSD versions, so supplementary queues are optional. */
if (i == 0)
return -1;
continue;
}
if ((uint64_t)pages > UINT64_MAX - memory)
return -1;
memory += (uint64_t)pages;
}
return mem_pages_to_bytes(memory, memsz);
}
size_t value_size = sizeof(memory);
if (sysctlbyname(total_memory_name, &memory, &value_size, NULL, 0) != 0 ||
(value_size != sizeof(uint32_t) && value_size != sizeof(uint64_t)))
return -1;
return mem_size_from_uint64(memory, memsz);
#endif
return 1;
+47 -1
View File
@@ -24,6 +24,9 @@
#include <ctype.h>
#include <math.h>
#include <string.h>
#if !defined(_WIN32) || defined(__CYGWIN__)
# include <stdatomic.h>
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
@@ -130,6 +133,36 @@ void parallel_cpp11thread(void (*func)(int, void *), size_t ncpu, void *arg);
int BU_SEM_THREAD = BU_SEM_ID_THREAD;
#if defined(_WIN32) && !defined(__CYGWIN__)
static LONG bu_cpu_limit = 0;
static size_t
cpu_limit_get(void)
{
return (size_t)InterlockedCompareExchange(&bu_cpu_limit, 0, 0);
}
static void
cpu_limit_set(size_t ncpu)
{
(void)InterlockedExchange(&bu_cpu_limit, (LONG)ncpu);
}
#else
static atomic_size_t bu_cpu_limit = ATOMIC_VAR_INIT(0);
static size_t
cpu_limit_get(void)
{
return atomic_load_explicit(&bu_cpu_limit, memory_order_acquire);
}
static void
cpu_limit_set(size_t ncpu)
{
atomic_store_explicit(&bu_cpu_limit, ncpu, memory_order_release);
}
#endif
typedef enum {
PARALLEL_GET = 0,
@@ -355,7 +388,9 @@ bu_avail_cpus(void)
}
if (LIKELY(ncpu > 0)) {
return ncpu;
size_t cpu_limit = cpu_limit_get();
return (cpu_limit && cpu_limit < (size_t)ncpu) ? cpu_limit :
(size_t)ncpu;
}
/* non-PARALLEL */
@@ -363,6 +398,13 @@ bu_avail_cpus(void)
}
void
bu_avail_cpus_set(size_t ncpu)
{
cpu_limit_set((ncpu > MAX_PSW) ? MAX_PSW : ncpu);
}
/**********************************************************************/
@@ -538,6 +580,10 @@ parallel_interface_arg_stub(struct thread_data *user_thread_data)
void
bu_parallel(void (*func)(int, void *), size_t ncpu, void *arg)
{
size_t cpu_limit = cpu_limit_get();
if (ncpu && cpu_limit && ncpu > cpu_limit)
ncpu = cpu_limit;
#ifndef PARALLEL
if (!func)
+8 -2
View File
@@ -1077,11 +1077,17 @@ bu_process_poll(struct bu_process *pinfo, int *exit_status)
}
#if defined(_WIN32)
/* An exit code can be available before process teardown releases
* inherited files and pipes. Only report completion once signaled. */
DWORD wait_result = WaitForSingleObject(pinfo->hProcess, 0);
if (wait_result == WAIT_TIMEOUT)
return 0;
if (wait_result != WAIT_OBJECT_0)
return -1;
DWORD status = 0;
if (!GetExitCodeProcess(pinfo->hProcess, &status))
return -1;
if (status == STILL_ACTIVE)
return 0;
pinfo->exit_status = (status == BU_MSVC_ABORT_EXIT) ?
ERROR_PROCESS_ABORTED : (int)status;

Some files were not shown because too many files have changed in this diff Show More