Compare commits

...
115 Commits
Author SHA1 Message Date
Clifford Yapp 4bc4413b57 Cull BREP tracing work and report telemetry 2026-09-29 10:54:16 -04:00
Clifford Yapp a69ed574d9 Optimize BREP certificate evaluation and face hierarchies 2026-09-29 10:54:16 -04:00
Clifford Yapp c233a4fb61 Enclose BREP physical event intervals 2026-09-29 10:54:16 -04:00
Clifford Yapp 0b77165c0b Certify BREP periodic, singular, and fold streams 2026-09-29 10:54:16 -04:00
Clifford Yapp 726f89cd7a Certify BREP pole and span event streams 2026-09-29 10:54:16 -04:00
Clifford Yapp 22a670b44d Optimize BREP interval restrictions and coefficient workspaces 2026-09-29 10:54:16 -04:00
Clifford Yapp eea3eccbbc Certify BREP root and seam events 2026-09-29 10:54:16 -04:00
Clifford Yapp 901347705f Checkpoint 2026-09-29 10:54:16 -04:00
Clifford Yapp fc37a8caf8 Share exact BREP endpoint restrictions
Reuse one de Casteljau triangle for one-axis restrictions touching a
parameter endpoint, and add exact high-order enclosure and performance
gates.
2026-09-29 10:54:16 -04:00
Clifford Yapp dad479edd3 Resolve ambiguous BREP seam continuations
Defer exact fold-strip construction until the corridor has a unique
root proof, and screen candidate-specific root populations before
counting seam continuations.
2026-09-29 10:54:16 -04:00
Clifford Yapp 2a32a6a71b Elide identity-axis BREP expansion blossoms
Use normalized source intervals directly for complete parameter axes.

Keep outward bounds and ordinary intersections unchanged.

Add exact-dyadic coverage through surface order 16.
2026-09-29 10:54:16 -04:00
Clifford Yapp 0d2eb82c4c Screen BREP fold graphs before exact restriction
Use outward intervals to certify strict determinant signs early.

Retain exact fallback for ambiguous slabs and add proof telemetry.

Add a fixed-shot benchmark for exact seam contacts.
2026-09-29 10:54:16 -04:00
Clifford Yapp 401928d735 Defer exact BREP coefficient expansion
Build outward interval coefficients first and initialize exact
ray-plane expansions only for proof stages that consume them. Remove
dead expansion builds and add per-shot accounting.
2026-09-29 10:54:15 -04:00
Clifford Yapp f4dbb89258 Stage interval fold contractions before expansion
Try outward interval Newton contractions before exact Bernstein
expansion work. Preserve the exact path for inconclusive cases and
ratchet exact seam-contact throughput and proof accounting.
2026-09-29 10:54:15 -04:00
Clifford Yapp de1bb455af Localize BREP seam continuation proofs
Certify the existing and continued roots in disjoint Krawczyk
boxes, then exclude their bounded complement.  Add a prepared-once
grazing fixture to ratchet close-root correctness and work.
2026-09-29 10:54:15 -04:00
Clifford Yapp 1d31418815 Reclaim BREP surface trees by preparation
Build each face hierarchy in owned arena blocks and transfer the
arena with its root. This replaces process-lifetime pooled nodes while
preserving knot and seam splits, cache layout, and traversal.

Add a repeated-prep audit to exercise complete unload behavior and
record allocator high-water observations separately from live ownership.

Document the independent exact-expansion hotspot exposed by grazing
seam rays.
2026-09-29 10:54:15 -04:00
Clifford Yapp b89127c016 Compact BREP surface child storage
Replace each pooled BBNode vector holder with directly owned compact
child metadata while preserving topology and cache order.

Record large-object memory results and distinguish prepared all-span
diagnostics from legacy hierarchy and production throughput.
2026-09-29 10:54:15 -04:00
Clifford Yapp 732d329177 Compact retained BREP curve trees
Store only production trim leaves in CurveTree-owned vectors.

Compact UV leaf and periodic image records.  Serialize the flat
representation with a new prep-cache version.

Document why bu_heap cannot safely reclaim cross-thread allocations
without a broader ownership design.
2026-09-29 10:54:15 -04:00
Clifford Yapp ff7cfadd5a Resolve coplanar BREP hit ownership
Prefer transverse witnesses over coincident coplanar shadows.

Ratchet feature rays across a second imported-model slice.
2026-09-29 10:54:15 -04:00
Clifford Yapp be3314a685 Add feature-targeted BREP ray audits
Sample deterministic face, edge, and grazing chord pairs.

Make near-side and coplanar cleanup independent of tree density.
2026-09-29 10:54:15 -04:00
Clifford Yapp 9110dc6b44 Make BREP hit cleanup reversal invariant
Require local coincidence before resolving near-miss events.

Retain maximal balanced solid runs under ray reversal.
2026-09-29 10:54:15 -04:00
Clifford Yapp 9a73feac84 Make BREP trim classification topology aware
Use half-open loop parity and bounded join healing.

Add trim audits and certify isolated near-trim events.
2026-09-29 10:54:15 -04:00
Clifford Yapp bd8fd932fd Align BREP trim endpoint classification
Use the CurveTree UV candidate tolerance consistently and clamp
admitted queries to trim leaf endpoints.

Add a reduced split-trim regression and richer imported-ray
diagnostics.  Record and validate the NIST depth-dependent witness.
2026-09-29 10:54:15 -04:00
Clifford Yapp 487954cbca Resolve solid BREP overlapping hit runs 2026-09-29 10:54:15 -04:00
Clifford Yapp c640884cf0 Audit imported BREP ray depth and status-2 poles 2026-09-29 10:54:14 -04:00
Clifford Yapp 65e5ad6a62 Profile BREP SurfaceTrees across databases
Add compact all-object audit records with geometry, NURBS,
failure, depth, density, timing, and memory totals.

Record a 14-database depth comparison and qualify the new mode.
2026-09-29 10:54:14 -04:00
Clifford Yapp c26b4b2b4e Certify BREP streams after structural splits
Make seam and knot decomposition mandatory before adaptive
SurfaceTree refinement, and invalidate pre-contract caches.

Certify complete clean regular event streams across faces and spans.
Add reduced-depth knot, hard Crofton, and production-path tests.
2026-09-29 10:54:14 -04:00
Clifford Yapp 29744a4da7 Certify collapsed BREP pole events 2026-09-29 10:54:14 -04:00
Clifford Yapp 3450dee989 Certify close BREP regular root pairs 2026-09-29 10:54:14 -04:00
Clifford Yapp b5ed0043ee Measure controlled BREP SurfaceTree depth 2026-09-29 10:54:14 -04:00
Clifford Yapp d4cf5d1b8c Certify tolerance-healed BREP edge transitions 2026-09-29 10:54:14 -04:00
Clifford Yapp 97a1b9c30f Certify local BREP seam root components 2026-09-29 10:54:14 -04:00
Clifford Yapp 9d3cb83a28 Add isolated Cobb trim and edge defects 2026-09-29 10:54:14 -04:00
Clifford Yapp 4a3c66ceff Test nested transformed BREP-leaf CSG 2026-09-29 10:54:14 -04:00
Clifford Yapp fd2b077c6b Qualify status-2 grazing under similarities
Exercise rotated, translated, and uniformly scaled torus grazing rays.
Use the conservative stored fold interval for mixed event separation
even when optional localization levels cannot improve it.
2026-09-29 10:54:14 -04:00
Clifford Yapp 8ba2eb90b0 Resolve status-2 BREP grazing roots locally
Accept reparameterized NURBS forms with checked point mappings.
Use fixed-workspace exact refinement to separate close grazing roots
and certify mixed fold/regular event pairs. Add torus ratchets and
surface-form audit telemetry.
2026-09-29 10:54:14 -04:00
Clifford Yapp 61ebf18379 Profile BREP prep failures and dense trees 2026-09-29 10:54:14 -04:00
Clifford Yapp 3378e9d15a Fail closed on invalid BREP surface trees
Propagate missing midpoint normals and subtree failures through complete
BREP prep instead of aborting or publishing a partial BVH.  Add an
extreme-weight regression and make invalid SurfaceTree queries safe.
2026-09-29 10:54:14 -04:00
Clifford Yapp 8f053346fd Ratchet grazing BREP root certificates
Replay analytic near-graze sphere roots through the local uniqueness and
rational-image proof.  Gate the signed capability trend in a focused
CTest shard and document the remaining coverage limits.
2026-09-29 10:54:14 -04:00
Clifford Yapp 44c65fc483 Expand local BREP root corpus
Add high-order and nearly singular arithmetic systems plus prepared
rational surfaces with wide weight, reversal, and similarity controls.
Record the qualification results and the existing extreme-weight prep
boundary.
2026-09-29 10:54:14 -04:00
Clifford Yapp ec03234357 Certify local BREP contact roots
Prove unique declared-tolerance contact roots with an
allocation-free fixed-expansion contraction.  Bound the complete
positive-weight rational surface image inside both edge and trim
tubes before publishing the seam transaction.

Add synthetic boundary, multiple-root, and singular controls and
replay the actual transformed Cobb coefficient systems.  Document
the theorem, fail-closed scope, and verification results.
2026-09-29 10:54:14 -04:00
Clifford Yapp 49c387662a Qualify BREP contact-face trim misses
Keep ordinary seam closure bounded by model tolerance.  Allow a private
prepared-event candidate to use explicit edge tolerance only for an
already-solved one-hit/one-miss contact lobe, and retain all ownership
and material-sector proofs before publication.

Add transformed Cobb and threshold ratchets, including sanitizer and
full seam coverage.
2026-09-29 10:54:13 -04:00
Clifford Yapp dfa3468619 Qualify rational BREP trim intervals
Share an exact interval-union proof between prep and shot-time trim bounds.

Reject overlap-masked provenance gaps without ray-time allocation.

Add a focused production-core corpus and parallel CTest shard.
2026-09-29 10:54:13 -04:00
Clifford Yapp 6e31fde0f2 Certify curved BREP trim seam frames
Retain rational trim spans after strict seam prep and use complete
interval UV and derivative bounds for non-isoparametric material
frames.

Add a curved Cobb fixture with analytic, reversal, source-union, and
similarity ratchets in a focused parallel test shard.
2026-09-29 10:54:13 -04:00
Clifford Yapp ee4c1798f2 Ratchet BREP source-root union proof
Share terminal-box component construction between production and a
solver-level corpus.  Add connected cubic, disconnected, multiple-root,
singular, and invalid-provenance fail-closed cases.
2026-09-29 10:54:13 -04:00
Clifford Yapp 84b434bbd5 Certify connected BREP source-root boxes
Prove one connected source-box component contains only the existing
regular root using strict Krawczyk inclusion over its complete UV
hull.  Ratchet oblique trend, similarity, and fail-closed evidence.
2026-09-29 10:54:13 -04:00
Clifford Yapp 8b4e7c1207 Certify rootless BREP contact corridors
Allow rootless oblique solver boxes only when the complete same-face
contact corridor passes the interval material-sector proof.  Keep
source-boundary boxes fail closed and extend Cobb trend and similarity
ratchets.
2026-09-29 10:54:13 -04:00
Clifford Yapp ad4b4aaeae Certify oblique BREP seam contacts
Retain prep-time trim correspondence cells and use fixed-work interval
edge and surface frames to certify oblique two-root contact lobes.

Add Cobb analytic, reversal, similarity, and fail-closed trend gates.
2026-09-29 10:54:13 -04:00
Clifford Yapp 0945249041 Certify BREP seam endpoint correspondence
Seed seam discrepancy bounds from topology-certified trim endpoints
instead of requiring nearest-point mapping to recover domain ends.

Dispatch seam completion for imbalanced clean transitions regardless
of whether Krawczyk or subdivision isolated the surviving root.
2026-09-29 10:54:13 -04:00
Clifford Yapp 9f7d58028e Certify tolerance-near BREP contact lobes
Resolve the narrow two-root incident-face contact case only after
complete edge, trim, box, sector, and outer-event ownership proofs.

Add similarity-invariant Cobb contact checks and an endpoint-moving
fallback ratchet with coherent edge and vertex tolerance metadata.
2026-09-29 10:54:13 -04:00
Clifford Yapp 584d43cd28 Publish certified BREP edge events
Classify exact interior manifold-edge crossings from oriented sectors.
Require independent line witnesses and complete root and box ownership.
Cover reentrant, contact, mixed, periodic, and transformed cases.
2026-09-29 10:54:13 -04:00
Clifford Yapp 2e40d4f988 Publish certified BREP vertex events
Build oriented planar manifold vertex links during prep and classify
exact crossings by spherical winding.  Preserve reflex arcs and own
every observed root and box before atomic event publication.

Gate convex, concave, tangent, reversal, and similarity behavior against
implicit geometry while retaining fallback for unsupported fans.

Keep SurfaceTree density and cache format unchanged.
2026-09-29 10:54:13 -04:00
Clifford Yapp 6690aa38c0 Avoid misaligned EXTRUDE segment access
Read sketch segment magic before casting to carc_seg.

Weaker line-segment alignment made the old access undefined.

UBSan exposed it through the new implicit EXTRUDE oracle.
2026-09-29 10:54:13 -04:00
Clifford Yapp 9d72450607 Publish certified BREP seam events
Represent authorized crack continuations as immutable paired boundary
events.  Require complete edge, root, box, and state ownership before
atomic segment publication, otherwise retain whole-ray fallback.

Gate the controlled Cobb seam matrix and record the production contract.
2026-09-29 10:54:13 -04:00
Clifford Yapp 8d16f39bf7 Certify BREP seam gap authorization
Replace sampled crack permission with decision-directed conservative
gap bounds. Preserve equality, unsupported arithmetic, and fixed-work
exhaustion as explicit whole-ray fallback.
2026-09-29 10:54:13 -04:00
Clifford Yapp 7ae01d3df6 Certify global BREP edge correspondence
Replace sampled edge/trim eligibility with exact rational control
signs and conservative lifted-derivative subdivision. Preserve
unsupported and fixed-work fallback for ambiguous seams.
2026-09-29 10:54:13 -04:00
Clifford Yapp 7652f60da2 Publish certified BREP event streams
Preserve terminal-box and root provenance in fixed physical events.
Require certified orientation, complete solid state, and outward
minimum-size classification before atomic prepared publication.
2026-09-29 10:54:12 -04:00
Clifford Yapp 8164162ada Define BREP shot production contract
Specify whole-ray completion accounting, immutable root evidence,
topology-first physical events, solid-state assembly, and atomic
fallback.  Put this correctness phase before prep-density work.
2026-09-29 10:54:12 -04:00
Clifford Yapp f9dfa02c19 Shard BREP ray correctness tests
Run directed, Crofton, seam, interval, fold, and affine groups as
separate CTest processes.  Retain the complete no-argument corpus for
manual qualification.
2026-09-29 10:54:12 -04:00
Clifford Yapp 44f6855c08 Promote certified BREP fold pairs
Localize complete fold proofs to rigorous ray intervals and classify
mutually unique adjacent-span events against the model minimum size.
Publish only clean, resolved ENTER/LEAVE pairs and keep every ambiguous,
trim-adjacent, unsupported, or capacity-limited case on legacy fallback.

Extend affine and interval ratchets for the six former grazing gaps,
contact behavior, reversal, and model/direction scaling.
2026-09-29 10:54:12 -04:00
Clifford Yapp 081061a288 Prove BREP fold root existence
Contract failed expansion Krawczyk boxes with fixed work and prove
existence from opposed weak-boundary signs on the unique regular graph.
Extend exact and affine ratchets across positive, root-free, and contact
cases while leaving production hit publication unchanged.
2026-09-29 10:54:12 -04:00
Clifford Yapp 9751f520d6 Record BREP frame experiment
Document the rejected exact-perpendicular frame and the proof-floor
regressions that caused the affine ratchet to reject it.
2026-09-29 10:54:12 -04:00
Clifford Yapp 7ee6fd981a Prove BREP fold boundary strips
Certify determinant signs on contracted implicit graphs without
reparameterization scale loss. Exclude adjacent fold strips with fixed
workspace and add exact and affine ratchets.
2026-09-29 10:54:12 -04:00
Clifford Yapp ad47b8ccd1 Audit portable BREP fold expansions
Add fixed-size error-free expansions for ray-plane coefficients,
Bezier restriction, interval evaluation, and Krawczyk arithmetic.

Exact-audit every supported restriction order and recover six of the
twelve known hard real certificates without changing hit publication.
2026-09-29 10:54:12 -04:00
Clifford Yapp 8d186e7fc0 Trace per-coefficient BREP fold certificates
Preserve ray-plane coefficient intervals through local restriction and
exact-audit coefficient-varying subdomains at every supported order.

Recover every known hard fold candidate in bounded shadow work while
recording the remaining binary64 certification gap without publishing
hits.
2026-09-29 10:54:12 -04:00
Clifford Yapp b11ae6dee2 Audit BREP fold uniqueness certificates
Form per-coefficient Bernstein Jacobian determinants with outward
interval weights and gate them against exact integer oracles.

Qualify strict Krawczyk inclusion and monotonic one-sided corridors far
below the legacy determinant cutoff without publishing production hits.
2026-09-29 10:54:12 -04:00
Clifford Yapp 64f37c61af Add a bounded BREP fold candidate corpus
Exercise analytic two-root, tangent, and miss systems through fixed
regular/weak bracketing under conditioned equation and parameter bases.

Keep candidate recovery separate from certification and production hit
publication while ratcheting work bounds and near-fold behavior.
2026-09-29 10:54:12 -04:00
Clifford Yapp 001740a5b8 Record BREP fold certification limits
Document bounded scalar recovery, coefficient precision results, and
partial monotonic uniqueness proofs for the remaining affine grazes.

Keep incomplete coverage and platform-specific precision experiments out
of production while preserving the next proof requirements.
2026-09-29 10:54:12 -04:00
Clifford Yapp bca851ea40 Prune BREP roots in a conditioned equation basis
Form outward interval hulls after a local linear equation rotation
and exclude boxes only when a transformed component cannot be zero.

Exact-audit 2,187,000 coefficient combinations and reduce ordinary
affine subdivision while preserving conservative grazing fallback.
2026-09-29 10:54:12 -04:00
Clifford Yapp 7b88bb8eb2 Record grazing fold prototype evidence
Document the span-boundary localization, rotated interval-hull
reduction, and one-sided recovery of the remaining affine roots.

Keep the unqualified prototype reverted while certification and seam
deduplication remain open.
2026-09-29 10:54:12 -04:00
Clifford Yapp bf3d8a77c5 Classify bounded BREP corrector failures
Record structured outcomes and residual bounds for prepared surface
and local root correction without changing hit decisions.

Document affine grazing evidence and a certified fold-aware isolation
strategy for the six remaining high-condition gaps.
2026-09-29 10:54:12 -04:00
Clifford Yapp 282e2d2361 Preserve resolved affine grazing intervals
Retain ordered grazing enter/leave pairs when their separation reaches
the model distance tolerance instead of deleting them solely by normal
angle.

Add a mapped ellipsoid trend that gates reversal, monotonicity, analytic
endpoints, fallback accounting, and known high-condition solver limits.
2026-09-29 10:54:12 -04:00
Clifford Yapp 7ceb68956f Gate BREP roots under affine ellipsoids
Map analytic sphere chords through rotated nonuniform scales and compare
implicit, promoted, forced-legacy, and prepared partitions.

Ratchet prepared-box and SurfaceTree-leaf fallbacks through condition
number 1,000 and add a focused affine test mode.
2026-09-29 10:54:12 -04:00
Clifford Yapp f7b21edca6 Promote certified BREP root partitions
Publish only fully certified prepared ordinary partitions and retain the
legacy SurfaceTree path for every unqualified result.

Add explicit fallback accounting, a forced-legacy test oracle, and
similarity gates for production classification and hit locations.
2026-09-29 10:54:12 -04:00
Clifford Yapp 080e062328 Clip BREP roots with conservative bounds 2026-09-29 10:54:12 -04:00
Clifford Yapp 24d92cd200 Enclose BREP continuation transforms
Apply outside-span Bernstein tensor operators with outward interval
arithmetic.  Audit extrapolation across every supported order and wide
binary scales with an exact-dyadic oracle.

Preserve certified crack repairs and similarity classifications.
2026-09-29 10:54:12 -04:00
Clifford Yapp a036e2b3ee Bound BREP coefficients through subdivision
Construct ray-plane coefficients and restricted nets with outward
interval arithmetic.  Audit every supported surface order with an
exact-dyadic oracle and bound positive-weight ray parameters.

Classify rigorously excluded miss-side legacy contacts explicitly.
2026-09-29 10:54:12 -04:00
Clifford Yapp 63b42010ce Audit BREP interval root bounds
Evaluate function and derivative Bernstein data with outward intervals.
Gate every supported tensor order across wide exact-dyadic scales.
Retain coefficient construction and restriction as shadow-only work.
2026-09-29 10:54:11 -04:00
Clifford Yapp e660a7e80f Gate adaptive BREP roots under similarities
Transform exact spheres and oblique rays through rotation, translation,
and 0.01x/10,000x uniform scales with scaled tolerances.
Require normalized oracle partitions and two early-certified roots.
2026-09-29 10:54:11 -04:00
Clifford Yapp 6516c2794b Qualify adaptive BREP root isolation
Use a strict interval Krawczyk test to stop regular shadow roots.
Keep grazing and boundary cases on the conservative depth-24 path.
Gate oblique sphere partitions and reduced subdivision work.
2026-09-29 10:54:11 -04:00
Clifford Yapp e62a547795 Compare prepared BREP events through final partitions
Materialize candidate and physical event streams in fixed workspaces.

Observe every cleanup stage and rerun certified crack repair.

Classify changed Cobb partitions against the implicit sphere oracle.
2026-09-29 10:54:11 -04:00
Clifford Yapp 34282ddbed Classify prepared BREP roots against face trims
Query each prepared CurveTree without ray-time allocation.

Cluster nearby candidates before comparing trim and event semantics.

Gate sphere, Cobb, and similarity cases against legacy events.
2026-09-29 10:54:11 -04:00
Clifford Yapp 597292bb67 Compare prepared-span and SurfaceTree BREP roots
Match unique same-face candidates one-to-one in scaled t clusters.

Require prepared spans to cover every depth-eight solver root.

Gate direct-only contacts and arbitrary-axis similarities.
2026-09-29 10:54:11 -04:00
Clifford Yapp 1ad4ba4181 Reject ambiguous BREP seam correspondence
Screen lifted trims for monotone edge traversal before seam repair.

Withhold edge evidence for closed, reversed, or unsupported mappings.

Gate a bowed triple-traversal trim and convex vertex-fan fallbacks.
2026-09-29 10:54:11 -04:00
Clifford Yapp f1810edb9e Bound BREP seam discrepancy with adaptive control hulls
Add a prep-time shadow certificate for supported regular seams.

Use knot-aware rational Bezier hulls with bounded per-solid work.

Report exhaustion without changing production seam authorization.

Gate similarity, reparameterization, gap bounds, and capacity.
2026-09-29 10:54:11 -04:00
Clifford Yapp 35afe5d2d8 Resolve BREP edge and trim parameters geometrically
Project prepared edge points onto each incident surface-lifted trim
with fixed-work sampling and bounded correction.  Use the
correspondence for seam discrepancy, sector frames, and continuation
seeds.

Gate opposite nonlinear trim reparameterizations on the bowed Cobb
seam while preserving classifier states, certificates, and repaired
production intervals.
2026-09-29 10:54:11 -04:00
Clifford Yapp cbb00d0466 Repair certified BREP seam crack events 2026-09-29 10:54:11 -04:00
Clifford Yapp 04d770dfbd Process BREP hits in fixed ray workspaces 2026-09-29 10:54:11 -04:00
Clifford Yapp 3c2b469a33 Scan prepared BREP trims without allocations 2026-09-29 10:54:11 -04:00
Clifford Yapp cb8ee49a52 Synchronize parallel solid prep publication 2026-09-29 10:54:11 -04:00
Clifford Yapp 9ddf8c6993 Make libbu lazy buffers thread-safe 2026-09-29 10:54:11 -04:00
Clifford Yapp a6f824e143 Use fixed BREP leaf traversal with fallback 2026-09-29 10:54:11 -04:00
Clifford Yapp 8ff6e56fb7 Shadow BREP leaf traversal without allocations 2026-09-29 10:54:11 -04:00
Clifford Yapp 2b39054b44 Find and cluster close BREP roots deterministically 2026-09-29 10:54:11 -04:00
Clifford Yapp ebfcc5f997 Correct roots within bounded BREP surface boxes 2026-09-29 10:54:11 -04:00
Clifford Yapp 0b663ad2fd Record BREP seam tolerance provenance
Measure initial edge-to-trim lift discrepancy and distinguish model,
declared, inferred, and effective seam tolerances.

Gate correct, unset, zero, under-declared, and over-declared Cobb
metadata while rebuilding derived records after cached prep.
2026-09-29 10:54:11 -04:00
Clifford Yapp e1a2d40e26 Certify bounded BREP seam continuations
Reparameterize outside-span Bernstein nets with operator-norm error
bounds and require a positive rational weight hull.

Isolate the continued root from the existing event and assemble an
oracle-matching shadow segment without changing production hits.
2026-09-29 10:54:11 -04:00
Clifford Yapp 4cb4de46fe Isolate BREP roots with bounded Bezier subdivision
Restrict homogeneous ray-plane Bernstein nets with fixed scratch
storage and retain conservative UV and ray-parameter boxes.

Gate close-root, tangent, miss-side, transform, and workspace behavior
without replacing the production surface tree path.
2026-09-29 10:54:11 -04:00
Clifford Yapp 8ad1135f07 Prepare BREP spans and shadow seam continuations
Decompose exact-parameter positive-weight surfaces into prepared
Bezier spans and conservatively exclude spans with ray-plane bounds.

Recover authorized bowed-seam endpoints with a bounded shadow solve
without changing production hit assembly.
2026-09-29 10:54:10 -04:00
Clifford Yapp beab75c232 Classify repairable BREP crack events
Identify uniquely compatible unmatched solid hits using authorized
inside-sector edge evidence and ordered missing directions.

Gate the classification under rigid transforms and uniform scaling
without emitting synthetic hits or changing production results.
2026-09-29 10:54:10 -04:00
Clifford Yapp 708638afe6 Test concave and periodic BREP edge sectors
Factor the edge-sector oracle and add a re-entrant L-extrusion edge
and a converted sphere same-face seam.

Require inside, contact, and outside classifications to remain stable in
both ray directions alongside the existing convex-edge gate.
2026-09-29 10:54:10 -04:00
Clifford Yapp ce7f4bb237 Classify BREP manifold edge material sectors
Build observation-only edge cross-sections from incident trim lifts,
loop-interior conormals, and oriented face normals.

Gate sharp convex and bowed smooth seams for inside, contact, and
outside proximity under ray reversal without changing shot results.
2026-09-29 10:54:10 -04:00
Clifford Yapp 961d389bc8 Prepare BREP shared-edge Bezier spans
Build exact-parameter positive-weight edge spans and conservative
control-hull boxes during normal and cached prep.

Use them to gate trace-only edge candidates while preserving bounded
distance diagnostics and the legacy shot result.
2026-09-29 10:54:10 -04:00
Clifford Yapp 5309a76eb4 Measure BREP crack proximity on shared edges
Add fixed-work, trace-only line-to-edge observations with explicit
mated topology and declared edge-tolerance qualification.

Gate the bowed Cobb trend against analytic clearance and show that every
current below-envelope leak has eligible target-edge evidence.
2026-09-29 10:54:10 -04:00
Clifford Yapp 4ff4c81b0b Trace BREP seam root and cleanup outcomes
Expose fixed-capacity in-librt shot diagnostics for candidate roots,
solver outcomes, and each legacy cleanup stage.

Use the bowed Cobb matrix to distinguish missing physical roots from
trim rejection and cleanup loss without changing production behavior.
2026-09-29 10:54:10 -04:00
Clifford Yapp 2c77c33c2f Measure bowed Cobb seam crack behavior
Calibrate endpoint-preserving support-surface perturbations by their
actual trim-lift discrepancy and sweep signed g/T and grazing h/T.

Record machine-readable partition and chord trends, preserve current
below-tolerance leaks as repairable one-way ratchets, and reject any
spread beyond the measured mismatch.
2026-09-29 10:54:10 -04:00
Clifford Yapp cbdfb2440b Add a sewn Cobb sphere ray oracle
Move the exact Cobb patch definition into libbrep and construct both
the legacy unsewn form and a closed shared-edge solid.

Gate topology, scale, seam and vertex rays, and 8,000 shared Crofton
lines against an implicit sphere.
2026-09-29 10:54:10 -04:00
Clifford Yapp 64e8e4fd2d Compare implicit and BREP-leaf CSG hierarchies
Preserve each Boolean tree while replacing exactly convertible leaves
with BREP equivalents in a separate in-memory database.

Add union, intersection, cavity, and drilled-box fixtures with
directed and 8,000-line paired partition checks.

Document later database-scale leaf substitution and reverse
BREP-to-CSG recognition as separate qualification tiers.
2026-09-29 10:54:10 -04:00
Clifford Yapp 995af03df6 Broaden the BREP ray correctness corpus
Generalize shared-line partition and Crofton checks to arbitrary
implicit primitives with explicit common bounds.

Add ellipsoid, ARB8, cylinder, cone, and torus fixtures with
analytic metrics and directed seam, pole, vertex, and multi-interval
rays.
2026-09-29 10:54:10 -04:00
Clifford Yapp e021397912 Compare implicit and BREP Crofton partitions
Prepare separate in-memory rt_i instances and shoot one fixed-seed
line set through normal Boolean partition processing.

Retain bounded ordered intervals, endpoint and normal checks, chord
differences, and nested area/volume confidence estimates.

Require 32,000 sphere rays to match line-for-line outside the
explicit minimum-size tangent band.
2026-09-29 10:54:10 -04:00
Clifford Yapp f2e8cff36a Condition BREP root convergence near tangency
Evaluate line residuals relative to the ray origin to avoid large
coordinate cancellation.

Scale root acceptance by |N dot D| so grazing candidates refine
further while singular normal-incidence poles remain valid.

Promote the sphere tangent and outside grazing trend to permanent
implicit-to-BREP correctness gates.
2026-09-29 10:54:10 -04:00
Clifford Yapp f48c488744 Expand the BREP ray correctness corpus
Add permanent seam, boundary, inside-start, scale, and translation
gates using paired implicit and converted-BREP sphere shots.

Keep current tangent and outward surface-start discrepancies as
non-regression cases that permit corrected behavior.  Add aggregate
Cauchy-Crofton area and volume checks against analytic ground truth.
2026-09-29 10:54:10 -04:00
Clifford Yapp 46f2d40f1f Harden BREP prep ownership and ray solver groundwork
Transfer surface-tree ownership explicitly and retain curve-tree
owners through normal and cached prep.

Make Utah Newton outcomes deterministic, validate evaluations, and
accept exact singular roots with robust normals.

Add an in-memory implicit-to-BREP sphere corpus with pole,
reverse-ray, repeatability, and grazing trend checks.  Remove the
unreachable 1991 native BSPLINE ray wrapper after recording the
reusable ideas and rejection reasons.
2026-09-29 10:54:10 -04:00
Clifford Yapp 805e29a543 Stash plan document 2026-09-29 10:54:10 -04:00
Clifford Yapp b5dac09aae Extract logic specific to CDT work from old branch 2026-09-29 10:54:10 -04:00
50 changed files with 84851 additions and 1861 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -44,6 +44,7 @@ __BEGIN_DECLS
#include "brep/quality.h"
#include "brep/intersect.h"
#include "brep/boolean.h"
#include "brep/cobb.h"
#include "brep/cdt.h"
#include "brep/csg.h"
+1
View File
@@ -4,6 +4,7 @@ set(
bbnode.h
assembly.h
boolean.h
cobb.h
brnode.h
cdt.h
csg.h
+79 -36
View File
@@ -29,6 +29,13 @@
#include "common.h"
#ifdef __cplusplus
extern "C++" {
# include <cstdint>
# include <list>
}
#endif
#include "brep/defines.h"
#include "brep/curvetree.h"
#include "brep/ray.h"
@@ -41,6 +48,7 @@
extern "C++" {
namespace brlcad {
class BBNode;
class BBNodeArena;
}
}
#endif
@@ -65,7 +73,7 @@ namespace brlcad {
/**
* Bounding Box Hierarchy Node
*/
class BREP_EXPORT BBNode : public PooledObject<BBNode> {
class BREP_EXPORT BBNode {
public:
explicit BBNode(const ON_BoundingBox &node, const CurveTree *ct = NULL);
BBNode(const CurveTree *ct,
@@ -77,6 +85,8 @@ namespace brlcad {
~BBNode();
BBNode(Deserializer &deserieralizer, const CurveTree &ctree);
static BBNode *deserializeTree(Deserializer &deserializer,
const CurveTree &ctree);
void serialize(Serializer &serializer) const;
const ON_BrepFace &get_face() const;
@@ -115,18 +125,36 @@ namespace brlcad {
/** Test whether a ray intersects the 3D bounding volume of the
* node - if so, and node is not a leaf node, query children. If
* leaf node, and intersects, add to list.
*
* The fixed-array overload appends while count is below capacity,
* increments count for every intersected leaf, and sets overflow if
* any leaf cannot be stored. The caller initializes count and
* overflow and may use the total count to select a fallback.
*/
bool intersectsHierarchy(const ON_Ray &ray, std::list<const BBNode *> &results) const;
bool intersectsHierarchy(const ON_Ray &ray, const BBNode **results,
std::size_t capacity, std::size_t &count,
bool &overflow) const;
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt) const;
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const;
int getLeavesBoundingPoint(const ON_3dPoint &pt, std::list<const BBNode *> &out) const;
bool isTrimmed(const ON_2dPoint &uv, const BRNode **closest, double &closesttrim, double within_distance_tol) const;
/** Classify against the prepared trim leaves without allocating.
* isTrimmedAllocating retains the former filtered-list path for
* transition-time equivalence diagnostics only. */
bool isTrimmed(const ON_2dPoint &uv, const BRNode **closest,
double &closesttrim, double within_distance_tol,
std::size_t *candidate_count = NULL) const;
bool isTrimmedAllocating(const ON_2dPoint &uv, const BRNode **closest,
double &closesttrim, double within_distance_tol,
std::size_t *candidate_count = NULL) const;
void BuildBBox();
bool prepTrims();
bool prepTrims(double within_distance_tol =
BREP_EDGE_MISS_TOLERANCE);
const std::vector<BBNode *> &get_children() const;
std::size_t childCount() const;
BBNode *child(std::size_t index) const;
/** Bounding Box */
ON_BoundingBox m_node;
@@ -138,6 +166,8 @@ namespace brlcad {
/** Trimming Flags */
bool m_checkTrim;
bool m_trimmed;
/* Arena construction nodes are destroyed shallowly by their arena. */
bool m_arena_node;
/** Point used for closeness testing - usually based on evaluation
* of the curve/surface at the center of the parametric domain
@@ -152,10 +182,15 @@ namespace brlcad {
private:
friend class BBNodeArena;
BBNode(const BBNode &source);
BBNode &operator=(const BBNode &source);
BBNode(Deserializer &deserializer, const CurveTree &ctree,
BBNodeArena *arena);
void adopt(BBNode &source);
void setArenaOwner(BBNodeArena *owner);
void removeChild(BBNode *child);
bool intersectedBy(const ON_Ray &ray, double *tnear = NULL, double *tfar = NULL) const;
/** Report if a given uv point is within the uv boundaries defined
@@ -163,39 +198,41 @@ namespace brlcad {
*/
bool containsUV(const ON_2dPoint &uv) const;
void getTrimsAbove(const ON_2dPoint &uv, std::list<const BRNode *> &out_leaves) const;
const BBNode *closer(const ON_3dPoint &pt, const BBNode *left, const BBNode *right) const;
struct Stl : public PooledObject<Stl> {
Stl() : m_children(), m_trims_above() {}
std::vector<BBNode *> m_children;
std::list<const BRNode *> m_trims_above;
} * const m_stl;
BBNode **m_children;
uint32_t m_child_count;
uint32_t m_child_capacity;
};
inline void
BBNode::addChild(BBNode *child)
{
if (LIKELY(child != NULL)) {
m_stl->m_children.push_back(child);
}
}
/**
* Per-SurfaceTree construction storage. The first node uses ordinary
* transferable storage and subsequent nodes are bulk allocated. The
* final logical root is adopted into that first node, whose lifetime owns
* the arena.
*/
class BBNodeArena {
public:
BBNodeArena();
~BBNodeArena();
inline void
BBNode::removeChild(BBNode *child)
{
std::vector<BBNode *>::iterator i;
for (i = m_stl->m_children.begin(); i != m_stl->m_children.end();) {
if (*i == child) {
delete *i;
i = m_stl->m_children.erase(i);
} else {
++i;
}
}
}
BBNode *make(const ON_BoundingBox &node,
const CurveTree *ct = NULL);
BBNode *make(const CurveTree *ct, const ON_BoundingBox &node,
const ON_Interval &u, const ON_Interval &v,
bool checkTrim, bool trimmed);
BBNode *finish(BBNode *root);
private:
friend class BBNode;
struct Block;
void *allocate();
BBNode *make(Deserializer &deserializer, const CurveTree &ctree);
Block *m_blocks;
BBNode *m_anchor;
bool m_started;
};
inline const ON_BrepFace &
BBNode::get_face() const
@@ -203,16 +240,22 @@ namespace brlcad {
return *m_ctree->m_face;
}
inline const std::vector<BBNode *> &
BBNode::get_children() const
inline std::size_t
BBNode::childCount() const
{
return m_stl->m_children;
return m_child_count;
}
inline BBNode *
BBNode::child(std::size_t index) const
{
return index < m_child_count ? m_children[index] : NULL;
}
inline bool
BBNode::isLeaf() const
{
return m_stl->m_children.empty();
return m_child_count == 0;
}
inline void
+44 -99
View File
@@ -33,8 +33,7 @@
#ifdef __cplusplus
extern "C++" {
/* @cond */
# include <vector>
# include <list>
# include <algorithm>
/* @endcond */
}
#endif
@@ -49,34 +48,19 @@ extern "C++" {
namespace brlcad {
/**
* Bounding Rectangle Hierarchy
* Compact prepared trim-curve leaf.
*/
class BREP_EXPORT BRNode : public PooledObject<BRNode> {
class BREP_EXPORT BRNode {
public:
explicit BRNode(const ON_BoundingBox &node);
BRNode(const ON_Curve *curve,
int trim_index,
int adj_face_index,
const ON_BoundingBox &node,
const ON_BrepFace *face,
const ON_Interval &t,
bool innerTrim,
bool checkTrim,
bool trimmed);
~BRNode();
bool innerTrim);
BRNode(Deserializer &deserializer, const ON_Brep &brep);
void serialize(Serializer &serializer) const;
/** Node management functions */
void addChild(BRNode *child);
/** Return a list of all nodes below this node that are leaf nodes */
void getLeaves(std::list<const BRNode *> &out_leaves) const;
/** Report the depth of this node in the hierarchy */
int depth() const;
/**
* Get 2 points defining bounding box:
*
@@ -91,19 +75,42 @@ namespace brlcad {
*/
void GetBBox(fastf_t *min, fastf_t *max) const;
bool isTrimmed(const ON_2dPoint &uv, double &trimdist) const;
/** Test a vertical +V parameter-space ray against this monotone
* trim leaf. Endpoint fuzz bridges small imported loop gaps while
* the exact test uses a half-open interval to avoid double counting
* shared vertices. */
bool crossesAbove(const ON_2dPoint &uv, double &distance,
double endpoint_tolerance = 0.0) const;
/** Test a horizontal +U parameter-space ray using the same half-open
* endpoint ownership as crossesAbove(). */
bool crossesRight(const ON_2dPoint &uv, double &distance,
double endpoint_tolerance = 0.0) const;
/** Return a fixed-workspace estimate of the shortest UV distance from
* a point to this trim leaf. */
double curveDistance(const ON_2dPoint &uv) const;
/** Lower bound from the leaf's sampled UV bounding box. */
double curveBBoxDistance(const ON_2dPoint &uv) const;
int trimIndex() const { return m_trim_index; }
ON_Interval parameterInterval() const { return m_t; }
ON_Interval curveDomain() const {
return m_trim ? m_trim->Domain() : ON_Interval::EmptyInterval;
}
ON_3dPoint startPoint() const {
return ON_3dPoint(m_start[0], m_start[1], 0.0);
}
ON_3dPoint endPoint() const {
return ON_3dPoint(m_end[0], m_end[1], 0.0);
}
ON_2dPoint bboxMinimum() const {
return ON_2dPoint(m_bbox_min[0], m_bbox_min[1]);
}
ON_2dPoint bboxMaximum() const {
return ON_2dPoint(m_bbox_max[0], m_bbox_max[1]);
}
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt) const;
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const;
fastf_t getCurveEstimateOfV(fastf_t u, fastf_t tol) const;
fastf_t getCurveEstimateOfU(fastf_t v, fastf_t tol) const;
/** Bounding Box */
ON_BoundingBox m_node;
/** Surface Information */
ON_Interval m_v;
/** Trim Curve Information */
int m_adj_face_index;
@@ -114,85 +121,23 @@ namespace brlcad {
bool m_innerTrim;
private:
BRNode(const BRNode &source);
BRNode &operator=(const BRNode &source);
void removeChild(BRNode *child);
/** Test if this node is a leaf node (i.e. m_children is empty) */
bool isLeaf() const;
fastf_t getLinearEstimateOfV(fastf_t u) const;
const BRNode *closer(const ON_3dPoint &pt, const BRNode *left, const BRNode *right) const;
struct Stl : public PooledObject<Stl> {
Stl() : m_children() {}
std::vector<const BRNode *> m_children;
} * const m_stl;
const ON_BrepFace *m_face;
ON_Interval m_u;
const ON_Curve *m_trim;
int m_trim_index;
ON_Interval m_t;
bool m_checkTrim;
bool m_trimmed;
ON_3dPoint m_estimate;
fastf_t m_slope;
fastf_t m_bb_diag;
ON_3dPoint m_start;
ON_3dPoint m_end;
double m_bbox_min[2];
double m_bbox_max[2];
double m_start[2];
double m_end[2];
};
inline void
BRNode::addChild(BRNode *child)
{
if (LIKELY(child != NULL)) {
m_stl->m_children.push_back(child);
}
}
inline void
BRNode::removeChild(BRNode *child)
{
std::vector<const BRNode *>::iterator i;
for (i = m_stl->m_children.begin(); i != m_stl->m_children.end();) {
if (*i == child) {
delete *i;
i = m_stl->m_children.erase(i);
} else {
++i;
}
}
}
inline bool
BRNode::isLeaf() const
{
if (m_stl->m_children.empty()) {
return true;
}
return false;
}
inline void
BRNode::GetBBox(fastf_t *min, fastf_t *max) const
{
VSETALL(min, INFINITY);
VSETALL(max, -INFINITY);
if (m_start != ON_3dPoint::UnsetPoint) {
VMINMAX(min, max, m_start);
}
if (m_end != ON_3dPoint::UnsetPoint) {
VMINMAX(min, max, m_end);
}
VSET(min, std::min(m_start[X], m_end[X]),
std::min(m_start[Y], m_end[Y]), 0.0);
VSET(max, std::max(m_start[X], m_end[X]),
std::max(m_start[Y], m_end[Y]), 0.0);
}
extern bool sortX(const BRNode *first, const BRNode *second);
extern bool sortY(const BRNode *first, const BRNode *second);
} /* namespace brlcad */
} /* extern C++ */
+3
View File
@@ -159,6 +159,9 @@ extern BREP_EXPORT int cdt_bmesh_create(struct cdt_bmesh **m);
extern BREP_EXPORT void cdt_bmesh_destroy(struct cdt_bmesh *m);
extern BREP_EXPORT int cdt_bmesh_deserialize(const char *fname, struct cdt_bmesh *m);
extern BREP_EXPORT int cdt_bmesh_repair(struct cdt_bmesh *m);
extern BREP_EXPORT int cdt_test_boundary_start(void);
extern BREP_EXPORT int cdt_test_boundary_steiner_filter(void);
extern BREP_EXPORT int cdt_test_spurious_components(void);
__END_DECLS
+65
View File
@@ -0,0 +1,65 @@
/* C O B B . H
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
/** @file brep/cobb.h
*
* Reusable Cobb rational Bezier sphere fixtures.
*/
#ifndef BREP_COBB_H
#define BREP_COBB_H
#include "common.h"
#include "brep/defines.h"
#ifdef __cplusplus
extern "C++" {
class ON_3dPoint;
class ON_BezierSurface;
class ON_Brep;
/** Return one unit Cobb sphere patch after rotations in degrees. */
BREP_EXPORT extern ON_BezierSurface *
ON_Brep_CobbSphereFace(double rotation_x, double rotation_z);
/**
* Return the legacy six-patch representation with independent face topology.
* This is an open plate-mode fixture, not a solid.
*/
BREP_EXPORT extern ON_Brep *
ON_Brep_CobbSphereUnsewn(double radius, const ON_3dPoint &origin);
/**
* Return the same six support surfaces with eight shared vertices and twelve
* shared edges. The result is a closed, oriented solid.
*/
BREP_EXPORT extern ON_Brep *
ON_Brep_CobbSphereSewn(double radius, const ON_3dPoint &origin);
/** Measure maximum radial error on a uniform per-surface sample grid. */
BREP_EXPORT extern double
ON_Brep_CobbSphereMaxRadialError(const ON_Brep *brep, double radius,
const ON_3dPoint &origin, int samples_per_direction = 33);
}
#endif
#endif /* BREP_COBB_H */
/*
* Local Variables:
* mode: C++
* tab-width: 8
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+57 -29
View File
@@ -32,11 +32,11 @@
#include "common.h"
#ifdef __cplusplus
// @cond SKIP_C++_INCLUDE
extern "C++" {
# include <map>
# include <cstdint>
# include <list>
# include <vector>
}
// @endcond
#endif
#include "brep/defines.h"
@@ -54,24 +54,39 @@ namespace brlcad {
/**
* CurveTree declaration
*/
class BREP_EXPORT CurveTree : public PooledObject<CurveTree> {
class BREP_EXPORT CurveTree {
public:
explicit CurveTree(const ON_BrepFace *face);
~CurveTree();
CurveTree(Deserializer &deserializer, const ON_BrepFace &face);
void serialize(Serializer &serializer) const;
std::vector<std::size_t> serialize_get_leaves_keys(const std::list<const BRNode *> &leaves) const;
std::list<const BRNode *> serialize_get_leaves(const std::size_t *keys, std::size_t num_keys) const;
void serialize_cleanup() const;
/**
* Return just the leaves of the surface tree
*/
void getLeaves(std::list<const BRNode *> &out_leaves) const;
void getLeavesAbove(std::list<const BRNode *> &out_leaves, const ON_Interval &u, const ON_Interval &v) const;
void getLeavesAbove(std::list<const BRNode *> &out_leaves,
const ON_Interval &u, const ON_Interval &v,
fastf_t tol = BREP_UV_DIST_FUZZ) const;
void getLeavesAbove(std::list<const BRNode *> &out_leaves, const ON_2dPoint &pt, fastf_t tol) const;
void getLeavesRight(std::list<const BRNode *> &out_leaves, const ON_2dPoint &pt, fastf_t tol) const;
std::size_t preparedLeafCount() const;
std::size_t preparedLeafImageCount() const;
int preparedMaximumDepth() const;
/** Classify a face parameter against all prepared trim leaves without
* allocating a filtered list. */
bool isTrimmed(const ON_2dPoint &uv, const BRNode **closest,
double &closesttrim, double within_distance_tol,
std::size_t *candidate_count = NULL,
bool heal_join_gaps = true) const;
/** Certify a UV cell whose material state cannot change without
* crossing a trim or a structural trim endpoint. Returns false when
* the cell must retain pointwise trim checks. */
bool classifyCell(const ON_Interval &u, const ON_Interval &v,
double within_distance_tol, bool &trimmed) const;
private:
friend class BBNode;
@@ -79,37 +94,50 @@ namespace brlcad {
CurveTree(const CurveTree &source);
CurveTree &operator=(const CurveTree &source);
const BRNode *getRootNode() const;
/**
* Calculate, using the surface bounding volume hierarchy, a uv
* estimate for the closest point on the surface to the point in
* 3-space.
*/
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt) const;
ON_2dPoint getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const;
void getLeavesRight(std::list<const BRNode *> &out_leaves, const ON_Interval &u, const ON_Interval &v) const;
int depth() const;
bool getHVTangents(const ON_Curve *curve, const ON_Interval &t, std::list<fastf_t> &list) const;
bool isLinear(const ON_Curve *curve, double min, double max) const;
BRNode *subdivideCurve(const ON_Curve *curve, int trim_index, int adj_face_index, double min, double max, bool innerTrim, int depth) const;
BRNode *curveBBox(const ON_Curve *curve, int trim_index, int adj_face_index, const ON_Interval &t, bool isLeaf, bool innerTrim, const ON_BoundingBox &bb) const;
static ON_BoundingBox initialLoopBBox(const ON_BrepFace &face);
void prepareLoopLeaves();
void subdivideCurve(const ON_Curve *curve, int trim_index,
int adj_face_index, double min, double max, bool innerTrim,
int depth);
const ON_BrepFace * const m_face;
BRNode *m_root;
struct Stl {
struct Loop {
struct Leaf {
Leaf() : node_index(0), image{0, 0},
extend_minimum{false, false} {}
Leaf(uint32_t leaf_index, int u_image, int v_image,
bool extend_u_minimum, bool extend_v_minimum) :
node_index(leaf_index), image{u_image, v_image},
extend_minimum{extend_u_minimum,
extend_v_minimum} {}
struct Stl : public PooledObject<Stl> {
Stl() : m_sortedX() {}
uint32_t node_index;
int image[2];
bool extend_minimum[2];
};
std::vector<const BRNode *> m_sortedX;
Loop() : index(-1), type(ON_BrepLoop::unknown), minimum(0.0, 0.0),
maximum(0.0, 0.0), have_bbox(false), sorted() {}
int index;
ON_BrepLoop::TYPE type;
ON_2dPoint minimum;
ON_2dPoint maximum;
bool have_bbox;
std::vector<Leaf> sorted;
};
Stl() : m_leaves(), m_loops(), m_maximum_depth(0) {}
std::vector<BRNode> m_leaves;
std::vector<Loop> m_loops;
int m_maximum_depth;
} * const m_stl;
mutable std::map<const BRNode *, std::size_t> *m_sortedX_indices;
};
} /* namespace brlcad */
+36 -1
View File
@@ -57,6 +57,14 @@ namespace brlcad {
*/
class BREP_EXPORT SurfaceTree {
public:
enum FailureReason {
FAILURE_NONE = 0,
FAILURE_NULL_SURFACE,
FAILURE_BOUNDING_BOX,
FAILURE_NORMAL_EVALUATION,
FAILURE_SUBDIVISION
};
explicit SurfaceTree(const ON_BrepFace *face, bool removeTrimmed = true, int depthLimit = BREP_MAX_FT_DEPTH, double within_distance_tol = BREP_EDGE_MISS_TOLERANCE);
~SurfaceTree();
@@ -66,9 +74,29 @@ namespace brlcad {
return false;
return true;
};
/* The first deterministic construction failure and its local cell. */
FailureReason Failure() const { return m_failure; };
ON_Interval FailureDomain(int direction) const {
return direction == 0 ? m_failure_u : m_failure_v;
};
int FailureDepth() const { return m_failure_depth; };
BBNode *getRootNode() const;
/**
* Transfer ownership of the prepared surface hierarchy to a caller.
* The returned node must be deleted by the caller. After this call the
* SurfaceTree destructor will not delete the hierarchy.
*/
BBNode *releaseRootNode();
/**
* Transfer ownership of the curve hierarchy referenced by the prepared
* BBNodes to a caller. The curve tree must outlive the BBNode hierarchy.
* After this call the SurfaceTree destructor will not delete it.
*/
const CurveTree *releaseCurveTree();
/**
* Calculate, using the surface bounding volume hierarchy, a uv
* estimate for the closest point on the surface to the point in
@@ -100,14 +128,21 @@ namespace brlcad {
bool isFlatU(const ON_Plane frames[9]) const;
bool isFlatV(const ON_Plane frames[9]) const;
bool hasSplit(const ON_Surface *surf, int dir, const ON_Interval &interval, double &split) const;
BBNode *initialBBox(const CurveTree *ctree, const ON_Surface *surf, const ON_Interval &u, const ON_Interval &v, int depth) const;
BBNode *subdivideSurface(const ON_Surface *localsurf, const ON_Interval &u, const ON_Interval &v, ON_Plane frames[9], int depth, int depthLimit, int prev_knot, double within_distance_tol) const;
BBNode *surfaceBBox(const ON_Surface *localsurf, bool leaf, const ON_Plane frames[9], const ON_Interval &u, const ON_Interval &v, double within_distance_tol) const;
BBNode *surfaceBBox(const ON_Surface *localsurf, bool leaf, const ON_Plane frames[9], const ON_Interval &u, const ON_Interval &v, int depth, double within_distance_tol) const;
void recordFailure(FailureReason reason, const ON_Interval &u, const ON_Interval &v, int depth) const;
const bool m_removeTrimmed;
const ON_BrepFace * const m_face;
BBNode *m_root;
BBNodeArena *m_node_arena;
std::queue<ON_Plane *> * const m_f_queue;
SurfaceSplitCache * const m_split_cache;
mutable FailureReason m_failure;
mutable ON_Interval m_failure_u;
mutable ON_Interval m_failure_v;
mutable int m_failure_depth;
};
} /* namespace brlcad */
+2 -2
View File
@@ -219,8 +219,8 @@ RT_EXPORT extern int rt_gettrees(struct rt_i *rtip,
* observing nodes or soltabs that the finishing pass will subsequently free.
*
* Semaphores used for critical sections in parallel mode:
* RT_SEM_TREE ====> protects rtip->i->rti_solidheads[] lists, d_uses(solids)
* RT_SEM_RESULTS => protects HeadRegion, mdl_min/max, d_uses(reg), nregions
* RT_SEM_TREE ====> protects rti_solidheads[], directory d_uses, prep publish
* RT_SEM_RESULTS => protects HeadRegion, mdl_min/max, nregions
* RT_SEM_WORKER ==> (db_walk_dispatcher, from db_walk_tree)
* RT_SEM_STATS ===> nsolids
*
+2 -1
View File
@@ -60,6 +60,7 @@ struct soltab {
struct rt_i * st_rtip; /**< @brief "up" pointer to rt_i */
long st_uses; /**< @brief Usage count, for instanced solids */
int st_id; /**< @brief Solid ident */
int st_prep_complete; /**< @brief prep publication flag */
point_t st_center; /**< @brief Centroid of solid */
fastf_t st_aradius; /**< @brief Radius of APPROXIMATING sphere */
fastf_t st_bradius; /**< @brief Radius of BOUNDING sphere */
@@ -77,7 +78,7 @@ struct soltab {
struct bound_rpp * st_piece_rpps; /**< @brief bounding RPP of each piece of this solid */
};
#define RT_SOLTAB_INIT_ZERO {BU_LIST_INIT_ZERO, BU_LIST_INIT_ZERO, NULL, NULL, 0, 0, VINIT_ZERO, 0.0, 0.0, NULL, NULL, VINIT_ZERO, VINIT_ZERO, 0, BU_PTBL_INIT_ZERO, NULL, DB_FULL_PATH_INIT_ZERO, 0, 0, NULL}
#define RT_SOLTAB_INIT_ZERO {BU_LIST_INIT_ZERO, BU_LIST_INIT_ZERO, NULL, NULL, 0, 0, 0, VINIT_ZERO, 0.0, 0.0, NULL, NULL, VINIT_ZERO, VINIT_ZERO, 0, BU_PTBL_INIT_ZERO, NULL, DB_FULL_PATH_INIT_ZERO, 0, 0, NULL}
#define st_name st_dp->d_namep
#define RT_SOLTAB_NULL ((struct soltab *)0)
+2
View File
@@ -1,5 +1,6 @@
brlcad_adddata(gtransfer.c sample_applications)
brlcad_addexec(brep-audit brep-audit.cpp "libbrep;libbv;librt;libbu")
brlcad_addexec(ganalyze ganalyze.cpp "libanalyze;librt;libbu;libpkg;${LINENOISE_LIBRARIES}" NO_INSTALL)
target_include_directories(ganalyze BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../libtermio)
@@ -20,6 +21,7 @@ add_subdirectory(gsh)
add_subdirectory(tests)
cmakefiles(
brep-audit.cpp
CMakeLists.txt
)
File diff suppressed because it is too large Load Diff
+29
View File
@@ -1,5 +1,34 @@
brlcad_addexec(bigdb bigdb.c "libged;libwdb;librt;libbu" TEST_USESDATA)
brlcad_add_test(
NAME brep_audit_surface_trees_all
COMMAND
brep-audit --surface-trees-all --surface-tree-depth=0
${CMAKE_SOURCE_DIR}/regress/nurbs/sph.g
)
brlcad_add_test(
NAME brep_audit_prep_repeat
COMMAND
brep-audit --prep-only --prep-repeat=3
${CMAKE_SOURCE_DIR}/regress/nurbs/sph.g sph.brep
)
brlcad_add_test(
NAME brep_audit_ray_depth_compare
COMMAND
brep-audit --ray-depth-compare --ray-count=4 --feature-ray-count=3
${CMAKE_SOURCE_DIR}/regress/nurbs/sph.g sph.brep
)
brlcad_add_test(
NAME brep_audit_ray_depth_compare_multiface
COMMAND
brep-audit --ray-depth-compare --ray-count=4 --feature-ray-count=3
${CMAKE_SOURCE_DIR}/src/libbrep/tests/nurbs_csg.g
recursive_child_linkage_complex_subtractions.s
)
brlcad_add_test(NAME slow-bigdb_1gb COMMAND bigdb 1)
if(TARGET slow-bigdb_1gb)
set_property(TEST slow-bigdb_1gb PROPERTY SKIP_RETURN_CODE 123)
+652 -251
View File
File diff suppressed because it is too large Load Diff
+180 -262
View File
@@ -21,6 +21,7 @@
#include "common.h"
#include <algorithm>
#include <cfloat>
#include "bu/log.h"
#include "brep/brnode.h"
@@ -34,32 +35,27 @@ BRNode::BRNode(
int trim_index,
int adj_face_index,
const ON_BoundingBox &node,
const ON_BrepFace *face,
const ON_Interval &t,
bool innerTrim,
bool checkTrim,
bool trimmed) :
m_node(node),
m_v(),
bool innerTrim) :
m_adj_face_index(adj_face_index),
m_XIncreasing(false),
m_Horizontal(false),
m_Vertical(false),
m_innerTrim(innerTrim),
m_stl(new Stl),
m_face(face),
m_u(),
m_trim(curve),
m_trim_index(trim_index),
m_t(t),
m_checkTrim(checkTrim),
m_trimmed(trimmed),
m_estimate(),
m_slope(0.0),
m_bb_diag(0.0),
m_start(curve->PointAt(m_t[0])),
m_end(curve->PointAt(m_t[1]))
m_bbox_min{node.m_min.x, node.m_min.y},
m_bbox_max{node.m_max.x, node.m_max.y},
m_start{0.0, 0.0},
m_end{0.0, 0.0}
{
const ON_3dPoint start = curve->PointAt(m_t[0]);
const ON_3dPoint end = curve->PointAt(m_t[1]);
m_start[0] = start.x;
m_start[1] = start.y;
m_end[0] = end.x;
m_end[1] = end.y;
/* check for vertical segments they can be removed from trims
* above (can't tell direction and don't need
*/
@@ -69,21 +65,6 @@ BRNode::BRNode(
/*
* should be okay since we split on Horz/Vert tangents
*/
if (m_end[X] < m_start[X]) {
m_u[0] = m_end[X];
m_u[1] = m_start[X];
} else {
m_u[0] = m_start[X];
m_u[1] = m_end[X];
}
if (m_end[Y] < m_start[Y]) {
m_v[0] = m_end[Y];
m_v[1] = m_start[Y];
} else {
m_v[0] = m_start[Y];
m_v[1] = m_end[Y];
}
if (NEAR_EQUAL(m_end[X], m_start[X], BREP_UV_DIST_FUZZ)) {
m_Vertical = true;
if (m_innerTrim) {
@@ -98,299 +79,236 @@ BRNode::BRNode(
} else {
m_XIncreasing = false;
}
m_slope = 0.0;
} else {
if ((m_end[X] - m_start[X]) > 0.0) {
m_XIncreasing = true;
} else {
m_XIncreasing = false;
}
m_slope = (m_end[Y] - m_start[Y]) / (m_end[X] - m_start[X]);
}
m_bb_diag = DIST_PNT_PNT(m_start, m_end);
}
BRNode::BRNode(const ON_BoundingBox &node) :
m_node(node),
m_v(),
m_adj_face_index(-1),
m_XIncreasing(false),
m_Horizontal(false),
m_Vertical(false),
m_innerTrim(false),
m_stl(new Stl),
m_face(NULL),
m_u(),
m_trim(NULL),
m_trim_index(-1),
m_t(),
m_checkTrim(true),
m_trimmed(false),
m_estimate(),
m_slope(0.0),
m_bb_diag(0.0),
m_start(ON_3dPoint::UnsetPoint),
m_end(ON_3dPoint::UnsetPoint)
{
for (int i = 0; i < 3; i++) {
double d = m_node.m_max[i] - m_node.m_min[i];
if (NEAR_ZERO(d, ON_ZERO_TOLERANCE)) {
m_node.m_min[i] -= 0.001;
m_node.m_max[i] += 0.001;
}
}
m_start = m_node.m_min;
m_end = m_node.m_max;
}
BRNode::~BRNode()
{
/* delete the children */
for (size_t i = 0; i < m_stl->m_children.size(); i++) {
delete m_stl->m_children[i];
}
delete m_stl;
}
BRNode::BRNode(Deserializer &deserializer, const ON_Brep &brep) :
m_node(),
m_v(),
m_adj_face_index(-1),
m_XIncreasing(false),
m_Horizontal(false),
m_Vertical(false),
m_innerTrim(false),
m_stl(new Stl),
m_face(NULL),
m_u(),
m_trim(NULL),
m_trim_index(-1),
m_t(),
m_checkTrim(false),
m_trimmed(false),
m_estimate(),
m_slope(0.0),
m_bb_diag(0.0),
m_start(ON_3dPoint::UnsetPoint),
m_end(ON_3dPoint::UnsetPoint)
m_bbox_min{0.0, 0.0},
m_bbox_max{0.0, 0.0},
m_start{0.0, 0.0},
m_end{0.0, 0.0}
{
deserializer.read(m_node);
deserializer.read(m_u);
deserializer.read(m_v);
deserializer.read(m_t);
deserializer.read(m_start);
deserializer.read(m_end);
deserializer.read(m_estimate);
m_slope = deserializer.read_double();
m_bb_diag = deserializer.read_double();
for (int direction = 0; direction < 2; ++direction) {
m_bbox_min[direction] = deserializer.read_double();
m_bbox_max[direction] = deserializer.read_double();
m_start[direction] = deserializer.read_double();
m_end[direction] = deserializer.read_double();
}
const uint8_t bool_flags = deserializer.read_uint8();
const int face_index = deserializer.read_int32();
m_trim_index = deserializer.read_int32();
m_adj_face_index = deserializer.read_uint32();
const std::size_t num_children = deserializer.read_uint32();
m_adj_face_index = deserializer.read_int32();
m_XIncreasing = bool_flags & (1 << 0);
m_Horizontal = bool_flags & (1 << 1);
m_Vertical = bool_flags & (1 << 2);
m_innerTrim = bool_flags & (1 << 3);
m_checkTrim = bool_flags & (1 << 4);
m_trimmed = bool_flags & (1 << 5);
if (face_index != -1) {
if (const ON_BrepFace * const face = brep.m_F.At(face_index))
m_face = face;
else
bu_bomb("invalid face index");
}
if (m_trim_index != -1) {
if (const ON_BrepTrim * const trim = brep.m_T.At(m_trim_index))
m_trim = trim->TrimCurveOf();
else
bu_bomb("invalid face index");
bu_bomb("invalid trim index");
}
m_stl->m_children.resize(num_children);
for (std::vector<const BRNode *>::iterator it = m_stl->m_children.begin(); it != m_stl->m_children.end(); ++it)
*it = new BRNode(deserializer, brep);
}
void
BRNode::serialize(Serializer &serializer) const
{
const uint8_t bool_flags = (m_XIncreasing << 0) | (m_Horizontal << 1) | (m_Vertical << 2) | (m_innerTrim << 3) | (m_checkTrim << 4) | (m_trimmed << 5);
const uint8_t bool_flags = (m_XIncreasing << 0) |
(m_Horizontal << 1) | (m_Vertical << 2) |
(m_innerTrim << 3);
serializer.write(m_node);
serializer.write(m_u);
serializer.write(m_v);
serializer.write(m_t);
serializer.write(m_start);
serializer.write(m_end);
serializer.write(m_estimate);
serializer.write_double(m_slope);
serializer.write_double(m_bb_diag);
for (int direction = 0; direction < 2; ++direction) {
serializer.write_double(m_bbox_min[direction]);
serializer.write_double(m_bbox_max[direction]);
serializer.write_double(m_start[direction]);
serializer.write_double(m_end[direction]);
}
serializer.write_uint8(bool_flags);
serializer.write_int32(m_face ? m_face->m_face_index : -1);
serializer.write_int32(m_trim_index);
serializer.write_uint32(m_adj_face_index);
serializer.write_uint32(m_stl->m_children.size());
for (std::vector<const BRNode *>::const_iterator it = m_stl->m_children.begin(); it != m_stl->m_children.end(); ++it)
(*it)->serialize(serializer);
serializer.write_int32(m_adj_face_index);
}
int
BRNode::depth() const
double
BRNode::curveBBoxDistance(const ON_2dPoint &uv) const
{
int d = 0;
for (size_t i = 0; i < m_stl->m_children.size(); i++) {
d = 1 + std::max(d, m_stl->m_children[i]->depth());
}
return d;
double du = 0.0;
double dv = 0.0;
if (uv.x < m_bbox_min[X])
du = m_bbox_min[X] - uv.x;
else if (uv.x > m_bbox_max[X])
du = uv.x - m_bbox_max[X];
if (uv.y < m_bbox_min[Y])
dv = m_bbox_min[Y] - uv.y;
else if (uv.y > m_bbox_max[Y])
dv = uv.y - m_bbox_max[Y];
return hypot(du, dv);
}
void
BRNode::getLeaves(std::list<const BRNode *> &out_leaves) const
double
BRNode::curveDistance(const ON_2dPoint &uv) const
{
if (!m_stl->m_children.empty()) {
for (size_t i = 0; i < m_stl->m_children.size(); i++) {
m_stl->m_children[i]->getLeaves(out_leaves);
if (!m_trim)
return INFINITY;
const size_t sample_count = 8;
size_t best_sample = 0;
double best_squared = INFINITY;
for (size_t sample = 0; sample <= sample_count; ++sample) {
const double fraction = (double)sample / (double)sample_count;
const ON_3dPoint point = m_trim->PointAt(m_t.ParameterAt(fraction));
const double dx = point.x - uv.x;
const double dy = point.y - uv.y;
const double squared = dx * dx + dy * dy;
if (squared < best_squared) {
best_squared = squared;
best_sample = sample;
}
} else {
out_leaves.push_back(this);
}
}
const BRNode *
BRNode::closer(const ON_3dPoint &pt, const BRNode *left, const BRNode *right) const
{
double ldist = pt.DistanceTo(left->m_estimate);
double rdist = pt.DistanceTo(right->m_estimate);
TRACE("\t" << ldist << " < " << rdist);
if (ldist < rdist) {
return left;
} else {
return right;
double low = best_sample ?
(double)(best_sample - 1) / (double)sample_count : 0.0;
double high = best_sample < sample_count ?
(double)(best_sample + 1) / (double)sample_count : 1.0;
const double golden = 0.6180339887498948482;
double first = high - golden * (high - low);
double second = low + golden * (high - low);
auto squared_distance = [this, &uv](double fraction) {
const ON_3dPoint point = m_trim->PointAt(m_t.ParameterAt(fraction));
const double dx = point.x - uv.x;
const double dy = point.y - uv.y;
return dx * dx + dy * dy;
};
double first_squared = squared_distance(first);
double second_squared = squared_distance(second);
for (int iteration = 0; iteration < 24; ++iteration) {
best_squared = std::min(best_squared,
std::min(first_squared, second_squared));
if (first_squared < second_squared) {
high = second;
second = first;
second_squared = first_squared;
first = high - golden * (high - low);
first_squared = squared_distance(first);
} else {
low = first;
first = second;
first_squared = second_squared;
second = low + golden * (high - low);
second_squared = squared_distance(second);
}
}
best_squared = std::min(best_squared,
std::min(first_squared, second_squared));
return sqrt(std::max(0.0, best_squared));
}
bool
BRNode::isTrimmed(const ON_2dPoint &uv, double &trimdist) const
BRNode::crossesAbove(const ON_2dPoint &uv, double &distance,
double endpoint_tolerance) const
{
point_t bmin, bmax;
BRNode::GetBBox(bmin, bmax);
if ((bmin[X] <= uv[X]) && (uv[X] <= bmax[X])) /* if check trim and in BBox */
{
fastf_t v = getCurveEstimateOfV(uv[X], 0.0000001);
trimdist = v - uv[Y];
if (uv[Y] <= v) {
if (m_XIncreasing) {
return true;
} else {
return false;
}
} else if (uv[Y] > v) {
if (!m_XIncreasing) {
return true;
} else {
return false;
}
} else {
return true;
}
} else {
trimdist = -1.0;
if (m_trimmed) {
return true;
} else {
distance = -1.0;
if (!m_trim || m_Vertical)
return false;
const double first_u = m_start[X];
const double second_u = m_end[X];
const bool exact =
((first_u <= uv.x && uv.x < second_u) ||
(second_u <= uv.x && uv.x < first_u));
double query_u = uv.x;
if (!exact) {
if (!(endpoint_tolerance > 0.0))
return false;
/* A closed trim owns both coincident curve-domain endpoints. Its exact
* half-open test already gives that vertex to one incident branch; the
* imported-loop gap allowance must not admit the other branch too. */
if (m_trim->IsClosed())
return false;
const double minimum_u = std::min(first_u, second_u);
const double minimum_t = first_u < second_u ? m_t[0] : m_t[1];
const ON_Interval curve_domain = m_trim->Domain();
const double parameter_scale = std::max(1.0,
std::max(fabs(curve_domain.Min()), fabs(curve_domain.Max())));
const double parameter_fuzz = 64.0 * DBL_EPSILON * parameter_scale;
const bool minimum_is_trim_endpoint =
fabs(minimum_t - curve_domain.Min()) <= parameter_fuzz ||
fabs(minimum_t - curve_domain.Max()) <= parameter_fuzz;
const double endpoint_fuzz = std::max(BREP_UV_DIST_FUZZ,
std::max(0.0, endpoint_tolerance));
if (uv.x < minimum_u &&
minimum_u - uv.x <= endpoint_fuzz &&
minimum_is_trim_endpoint)
query_u = minimum_u;
else
return false;
}
}
const double curve_v = getCurveEstimateOfV(query_u, 1.0e-7);
distance = curve_v - uv.y;
return distance >= 0.0;
}
ON_2dPoint
BRNode::getClosestPointEstimate(const ON_3dPoint &pt) const
bool
BRNode::crossesRight(const ON_2dPoint &uv, double &distance,
double endpoint_tolerance) const
{
ON_Interval u, v;
return getClosestPointEstimate(pt, u, v);
}
ON_2dPoint
BRNode::getClosestPointEstimate(const ON_3dPoint &pt, ON_Interval &u, ON_Interval &v) const
{
if (isLeaf()) {
double uvs[5][2] = {{m_u.Min(), m_v.Min()}, /* include the corners for an easy refinement */
{m_u.Max(), m_v.Min()},
{m_u.Max(), m_v.Max()},
{m_u.Min(), m_v.Max()},
{m_u.Mid(), m_v.Mid()}
}; /* include the estimate */
ON_3dPoint corners[5];
const ON_Surface *surf = m_face->SurfaceOf();
u = m_u;
v = m_v;
/* ??? should we pass these in from SurfaceTree::curveBBox()
* to avoid this recalculation?
*/
if (!surf->EvPoint(uvs[0][0], uvs[0][1], corners[0]) ||
!surf->EvPoint(uvs[1][0], uvs[1][1], corners[1]) ||
!surf->EvPoint(uvs[2][0], uvs[2][1], corners[2]) ||
!surf->EvPoint(uvs[3][0], uvs[3][1], corners[3]))
{
throw std::exception(); /* FIXME */
}
corners[4] = BRNode::m_estimate;
/* find the point on the curve closest to pt */
size_t mini = 0;
double mindist = pt.DistanceTo(corners[mini]);
double tmpdist;
for (size_t i = 1; i < 5; i++) {
tmpdist = pt.DistanceTo(corners[i]);
TRACE("\t" << mindist << " < " << tmpdist);
if (tmpdist < mindist) {
mini = i;
mindist = tmpdist;
}
}
TRACE("Closest: " << mindist << "; " << PT2(uvs[mini]));
return ON_2dPoint(uvs[mini][0], uvs[mini][1]);
} else {
if (!m_stl->m_children.empty()) {
const BRNode *closestNode = m_stl->m_children[0];
for (size_t i = 1; i < m_stl->m_children.size(); i++) {
closestNode = closer(pt, closestNode, m_stl->m_children[i]);
}
return closestNode->getClosestPointEstimate(pt, u, v);
} else {
throw std::exception();
}
distance = -1.0;
if (!m_trim || m_Horizontal)
return false;
const double first_v = m_start[Y];
const double second_v = m_end[Y];
const bool exact =
((first_v <= uv.y && uv.y < second_v) ||
(second_v <= uv.y && uv.y < first_v));
double query_v = uv.y;
if (!exact) {
if (!(endpoint_tolerance > 0.0))
return false;
if (m_trim->IsClosed())
return false;
const double minimum_v = std::min(first_v, second_v);
const double minimum_t = first_v < second_v ? m_t[0] : m_t[1];
const ON_Interval curve_domain = m_trim->Domain();
const double parameter_scale = std::max(1.0,
std::max(fabs(curve_domain.Min()), fabs(curve_domain.Max())));
const double parameter_fuzz = 64.0 * DBL_EPSILON * parameter_scale;
const bool minimum_is_trim_endpoint =
fabs(minimum_t - curve_domain.Min()) <= parameter_fuzz ||
fabs(minimum_t - curve_domain.Max()) <= parameter_fuzz;
const double endpoint_fuzz = std::max(BREP_UV_DIST_FUZZ,
std::max(0.0, endpoint_tolerance));
if (uv.y < minimum_v &&
minimum_v - uv.y <= endpoint_fuzz &&
minimum_is_trim_endpoint)
query_v = minimum_v;
else
return false;
}
}
fastf_t
BRNode::getLinearEstimateOfV(fastf_t u) const
{
fastf_t v = m_start[Y] + m_slope * (u - m_start[X]);
return v;
const double curve_u = getCurveEstimateOfU(query_v, 1.0e-7);
distance = curve_u - uv.x;
return distance >= 0.0;
}
@@ -401,13 +319,13 @@ BRNode::getCurveEstimateOfV(fastf_t u, fastf_t tol) const
double Ta, Tb;
if (m_start[X] < m_end[X]) {
VMOVE(A, m_start);
VMOVE(B, m_end);
VSET(A, m_start[X], m_start[Y], 0.0);
VSET(B, m_end[X], m_end[Y], 0.0);
Ta = m_t[0];
Tb = m_t[1];
} else {
VMOVE(A, m_end);
VMOVE(B, m_start);
VSET(A, m_end[X], m_end[Y], 0.0);
VSET(B, m_start[X], m_start[Y], 0.0);
Ta = m_t[1];
Tb = m_t[0];
}
@@ -493,13 +411,13 @@ BRNode::getCurveEstimateOfU(fastf_t v, fastf_t tol) const
double Ta, Tb;
if (m_start[Y] < m_end[Y]) {
VMOVE(A, m_start);
VMOVE(B, m_end);
VSET(A, m_start[X], m_start[Y], 0.0);
VSET(B, m_end[X], m_end[Y], 0.0);
Ta = m_t[0];
Tb = m_t[1];
} else {
VMOVE(A, m_end);
VMOVE(B, m_start);
VSET(A, m_end[X], m_end[Y], 0.0);
VSET(B, m_start[X], m_start[Y], 0.0);
Ta = m_t[1];
Tb = m_t[0];
}
+1
View File
@@ -9,6 +9,7 @@ set(
Subcurve.cpp
Subsurface.cpp
boolean.cpp
cobb.cpp
cdt/cdt.cpp
cdt/fast.cpp
cdt/surf.cpp
+565 -6
View File
@@ -26,17 +26,426 @@
*/
#include "common.h"
#include <algorithm>
#include <iostream>
#include <map>
#include <numeric>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include "bg/chull.h"
#include "bg/tri_pt.h"
#include "bg/tri_tri.h"
#include "./cdt.h"
#define BREP_PLANAR_TOL 0.05
#define MAX_TRIANGULATION_ATTEMPTS 5
/* cpolyedge_t containers historically used the default pointer comparator.
* That is adequate for membership, but it must not determine the order of
* geometry-changing operations: allocator layout can then change the order in
* which singular trims are subdivided and, consequently, the final surface
* sampling. Every singular segment has an authoritative STEP trim index,
* parameter interval, and pair of face-local topology vertices, which together
* provide a stable processing order. */
static bool
singular_edge_process_less(const cpolyedge_t *a, const cpolyedge_t *b)
{
if (a == b)
return false;
if (!a)
return true;
if (!b)
return false;
if (a->trim_ind != b->trim_ind)
return a->trim_ind < b->trim_ind;
if (a->trim_start < b->trim_start)
return true;
if (b->trim_start < a->trim_start)
return false;
if (a->trim_end < b->trim_end)
return true;
if (b->trim_end < a->trim_end)
return false;
if (a->v2d[0] != b->v2d[0])
return a->v2d[0] < b->v2d[0];
return a->v2d[1] < b->v2d[1];
}
/* Correct triangle winding only after the complete mesh proves closed and
* manifold and misorientation is its sole defect. bg_trimesh_sync establishes
* a consistent orientation for each connected component, but its seed may
* choose either of the two possible component orientations. Preserve the
* OpenNURBS face orientation by selecting the result that changes the fewest
* source triangles in each component. A tied component has no authoritative
* majority, so leave the mesh unchanged rather than guessing.
*
* This function is transactional: faces and face normals are not modified
* unless the candidate subsequently passes the complete solid test. */
static int
closed_mesh_orientation_sync(int *faces, int face_count,
fastf_t *vertices, int vertex_count, int *face_normals)
{
if (!faces || !vertices || face_count < 4 || vertex_count < 4)
return -1;
struct bg_trimesh_solid_errors errors = BG_TRIMESH_SOLID_ERRORS_INIT_NULL;
const int initial_invalid = bg_trimesh_solid2(vertex_count, face_count,
vertices, faces, &errors);
if (!initial_invalid) {
bg_free_trimesh_solid_errors(&errors);
return 0;
}
const bool only_misoriented = errors.misoriented.count > 0 &&
errors.degenerate.count == 0 && errors.unmatched.count == 0 &&
errors.excess.count == 0;
bg_free_trimesh_solid_errors(&errors);
if (!only_misoriented)
return -1;
std::vector<int> original(faces, faces + 3 * face_count);
std::vector<int> candidate(original);
if (bg_trimesh_sync(candidate.data(), candidate.data(), face_count) <= 0)
return -1;
/* Determine the connected closed-shell components from shared edges. */
std::vector<std::vector<int>> adjacent((size_t)face_count);
std::map<std::pair<int, int>, int> first_edge_face;
for (int face = 0; face < face_count; ++face) {
for (int edge = 0; edge < 3; ++edge) {
int vertex_a = original[(size_t)face * 3 + edge];
int vertex_b = original[(size_t)face * 3 + (edge + 1) % 3];
if (vertex_a > vertex_b)
std::swap(vertex_a, vertex_b);
const std::pair<int, int> key(vertex_a, vertex_b);
auto first = first_edge_face.find(key);
if (first == first_edge_face.end()) {
first_edge_face[key] = face;
continue;
}
adjacent[(size_t)face].push_back(first->second);
adjacent[(size_t)first->second].push_back(face);
}
}
std::vector<int> component_id((size_t)face_count, -1);
std::vector<std::vector<int>> components;
for (int seed = 0; seed < face_count; ++seed) {
if (component_id[(size_t)seed] >= 0)
continue;
const int current_id = (int)components.size();
components.push_back(std::vector<int>());
std::queue<int> work;
work.push(seed);
component_id[(size_t)seed] = current_id;
while (!work.empty()) {
const int face = work.front();
work.pop();
components[(size_t)current_id].push_back(face);
for (int neighbor : adjacent[(size_t)face]) {
if (component_id[(size_t)neighbor] >= 0)
continue;
component_id[(size_t)neighbor] = current_id;
work.push(neighbor);
}
}
}
for (const std::vector<int> &component : components) {
size_t changed = 0;
for (int face : component) {
const size_t offset = (size_t)face * 3;
if (candidate[offset] != original[offset] ||
candidate[offset + 1] != original[offset + 1] ||
candidate[offset + 2] != original[offset + 2])
changed++;
}
if (changed * 2 == component.size())
return -1;
if (changed * 2 > component.size()) {
for (int face : component)
std::swap(candidate[(size_t)face * 3],
candidate[(size_t)face * 3 + 1]);
}
}
if (bg_trimesh_solid2(vertex_count, face_count, vertices,
candidate.data(), NULL) != 0)
return -1;
int changed_count = 0;
for (int face = 0; face < face_count; ++face) {
const size_t offset = (size_t)face * 3;
if (candidate[offset] == original[offset] &&
candidate[offset + 1] == original[offset + 1] &&
candidate[offset + 2] == original[offset + 2])
continue;
changed_count++;
if (face_normals)
std::swap(face_normals[offset], face_normals[offset + 1]);
}
std::copy(candidate.begin(), candidate.end(), faces);
return changed_count;
}
/* A failed local face remesh can leave a few open triangle islands in addition
* to the useful tessellation of the source solid. Those islands are removed
* only under one of two proofs: every retained component is independently
* closed and solid, or (for a partial display mesh) every point of an island
* is already covered within tolerance by retained triangles from the same
* source BREP face. Valid disjoint shells are retained, and no change is made
* if neither proof succeeds.
*
* The operation is transactional with respect to the caller's face arrays.
* Vertex arrays may retain now-unused entries; that is legal for a BoT and
* keeps the face-normal and point ownership maps stable. */
static int
closed_mesh_component_filter(int *faces, int *face_count,
fastf_t *vertices, int vertex_count, int *face_normals,
const int *source_faces = NULL, double overlap_tolerance = 0.0,
bool source_is_solid = true, bool emit_diagnostics = true)
{
if (!faces || !face_count || !vertices || *face_count < 2 ||
vertex_count < 4)
return 0;
/* Validate all indices before making the first transactional edit. */
for (int face = 0; face < *face_count; ++face) {
for (int corner = 0; corner < 3; ++corner) {
const int vertex = faces[(size_t)face * 3 + corner];
if (vertex < 0 || vertex >= vertex_count)
return 0;
}
}
/* Distinct face-mesh vertices may collapse to the same shared output
* point. Cull the resulting zero-area index triangles before component
* discovery; otherwise their repeated edge can falsely bridge two
* components in this audit even though rt_bot_split correctly ignores
* that bridge. */
const int original_face_count = *face_count;
std::vector<int> compact_faces;
std::vector<int> compact_normals;
std::vector<int> compact_sources;
compact_faces.reserve((size_t)original_face_count * 3);
if (face_normals)
compact_normals.reserve((size_t)original_face_count * 3);
if (source_faces)
compact_sources.reserve((size_t)original_face_count);
for (int face = 0; face < original_face_count; ++face) {
const size_t offset = (size_t)face * 3;
if (faces[offset] == faces[offset + 1] ||
faces[offset + 1] == faces[offset + 2] ||
faces[offset + 2] == faces[offset])
continue;
for (int corner = 0; corner < 3; ++corner) {
compact_faces.push_back(faces[offset + corner]);
if (face_normals)
compact_normals.push_back(face_normals[offset + corner]);
}
if (source_faces)
compact_sources.push_back(source_faces[face]);
}
int removed_count = original_face_count -
(int)(compact_faces.size() / 3);
if (removed_count) {
std::copy(compact_faces.begin(), compact_faces.end(), faces);
if (face_normals)
std::copy(compact_normals.begin(), compact_normals.end(),
face_normals);
*face_count = (int)(compact_faces.size() / 3);
if (source_faces)
source_faces = compact_sources.data();
}
if (*face_count < 2)
return removed_count;
std::map<std::pair<int, int>, std::vector<int>> edge_faces;
for (int face = 0; face < *face_count; ++face) {
for (int corner = 0; corner < 3; ++corner) {
int first = faces[(size_t)face * 3 + corner];
int second = faces[(size_t)face * 3 + (corner + 1) % 3];
if (first > second)
std::swap(first, second);
edge_faces[std::make_pair(first, second)].push_back(face);
}
}
std::vector<int> component_id((size_t)*face_count, -1);
std::vector<std::vector<int>> components;
for (int seed = 0; seed < *face_count; ++seed) {
if (component_id[(size_t)seed] >= 0)
continue;
const int current_id = (int)components.size();
components.push_back(std::vector<int>());
std::queue<int> work;
work.push(seed);
component_id[(size_t)seed] = current_id;
while (!work.empty()) {
const int face = work.front();
work.pop();
components[(size_t)current_id].push_back(face);
for (int corner = 0; corner < 3; ++corner) {
int first = faces[(size_t)face * 3 + corner];
int second =
faces[(size_t)face * 3 + (corner + 1) % 3];
if (first > second)
std::swap(first, second);
const std::vector<int> &edge_neighbors =
edge_faces[std::make_pair(first, second)];
/* An edge used by more than two triangles is non-manifold. It
* must not join an otherwise closed shell to a local remesh
* artifact during component discovery. Restricting adjacency
* to exactly two uses also matches the topological connectivity
* required by a valid solid mesh. */
if (edge_neighbors.size() != 2)
continue;
for (int neighbor : edge_neighbors) {
if (component_id[(size_t)neighbor] >= 0)
continue;
component_id[(size_t)neighbor] = current_id;
work.push(neighbor);
}
}
}
}
if (components.size() < 2)
return removed_count;
std::vector<bool> retain(components.size(), false);
size_t retained_face_count = 0;
for (size_t component = 0;
source_is_solid && component < components.size(); ++component) {
std::vector<int> component_faces;
component_faces.reserve(components[component].size() * 3);
for (int face : components[component]) {
const size_t offset = (size_t)face * 3;
component_faces.push_back(faces[offset]);
component_faces.push_back(faces[offset + 1]);
component_faces.push_back(faces[offset + 2]);
}
if (component_faces.size() >= 12 &&
bg_trimesh_solid2(vertex_count,
(int)components[component].size(), vertices,
component_faces.data(), NULL) == 0) {
retain[component] = true;
retained_face_count += components[component].size();
}
}
if (retained_face_count == (size_t)*face_count)
return removed_count;
/* A partial display tessellation may not itself be closed (for example,
* another BREP face may have independently failed to tessellate). We can
* still remove a disconnected local-remesh island when every one of its
* vertices and its centroid lies on retained triangles from the same
* source face. This is an exact redundancy proof within the tessellation
* tolerance, not a largest-component guess. */
if (!retained_face_count && source_faces &&
overlap_tolerance > 0.0) {
size_t largest = 0;
for (size_t component = 1; component < components.size(); ++component) {
if (components[component].size() > components[largest].size())
largest = component;
}
double minimum_failed_distance = DBL_MAX;
int failed_source_face = -1;
const auto point_covered =
[&](const point_t point, int source_face) {
double minimum_distance = DBL_MAX;
for (int candidate : components[largest]) {
if (source_faces[candidate] != source_face)
continue;
point_t triangle[3];
for (int corner = 0; corner < 3; ++corner) {
const int vertex =
faces[(size_t)candidate * 3 + corner];
VMOVE(triangle[corner],
&vertices[(size_t)vertex * 3]);
}
const double distance = bg_tri_closest_pt(NULL, point,
triangle[0], triangle[1], triangle[2]);
minimum_distance = std::min(minimum_distance, distance);
if (distance <= overlap_tolerance)
return true;
}
minimum_failed_distance = minimum_distance;
failed_source_face = source_face;
return false;
};
bool redundant = true;
for (size_t component = 0;
component < components.size() && redundant; ++component) {
if (component == largest)
continue;
for (int face : components[component]) {
point_t centroid = VINIT_ZERO;
for (int corner = 0; corner < 3; ++corner) {
const int vertex = faces[(size_t)face * 3 + corner];
point_t point;
VMOVE(point, &vertices[(size_t)vertex * 3]);
if (!point_covered(point, source_faces[face])) {
redundant = false;
break;
}
VADD2(centroid, centroid, point);
}
VSCALE(centroid, centroid, 1.0 / 3.0);
if (redundant &&
!point_covered(centroid, source_faces[face]))
redundant = false;
if (!redundant)
break;
}
}
if (!redundant) {
if (!emit_diagnostics)
return removed_count;
bu_log("CDT retained an open disconnected component because source "
"face %d was not redundantly covered (distance %.17g, "
"tolerance %.17g)\n", failed_source_face,
minimum_failed_distance, overlap_tolerance);
return removed_count;
}
retain[largest] = true;
retained_face_count = components[largest].size();
}
if (!retained_face_count)
return removed_count;
std::vector<int> candidate_faces;
std::vector<int> candidate_normals;
candidate_faces.reserve(retained_face_count * 3);
if (face_normals)
candidate_normals.reserve(retained_face_count * 3);
for (int face = 0; face < *face_count; ++face) {
if (!retain[(size_t)component_id[(size_t)face]])
continue;
const size_t offset = (size_t)face * 3;
for (int corner = 0; corner < 3; ++corner) {
candidate_faces.push_back(faces[offset + corner]);
if (face_normals)
candidate_normals.push_back(face_normals[offset + corner]);
}
}
if (!source_faces &&
bg_trimesh_solid2(vertex_count, (int)retained_face_count,
vertices, candidate_faces.data(), NULL) != 0)
return removed_count;
const int removed = removed_count +
*face_count - (int)retained_face_count;
std::copy(candidate_faces.begin(), candidate_faces.end(), faces);
if (face_normals)
std::copy(candidate_normals.begin(), candidate_normals.end(),
face_normals);
*face_count = (int)retained_face_count;
return removed;
}
// TODO - get rid of all BN_TOL_DIST-only tolerances - if the object is
// very small, that distance is too big (e.g. for linearity testing).
@@ -493,8 +902,12 @@ ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s_cdt, int face_cnt, int *faces
int cnt = 0;
wq = &w1;
nq = &w2;
nq->push(*(s_cdt->unsplit_singular_edges.begin()));
s_cdt->unsplit_singular_edges.erase(s_cdt->unsplit_singular_edges.begin());
std::set<cpolyedge_t *>::iterator first =
std::min_element(s_cdt->unsplit_singular_edges.begin(),
s_cdt->unsplit_singular_edges.end(),
singular_edge_process_less);
nq->push(*first);
s_cdt->unsplit_singular_edges.erase(first);
while (cnt < 6) {
cnt = 0;
tmpq = wq;
@@ -504,8 +917,12 @@ ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s_cdt, int face_cnt, int *faces
cpolyedge_t *ce = wq->front();
wq->pop();
std::set<cpolyedge_t *> nedges = split_singular_seg(s_cdt, ce, 0);
std::set<cpolyedge_t *>::iterator n_it;
for (n_it = nedges.begin(); n_it != nedges.end(); n_it++) {
std::vector<cpolyedge_t *> ordered_edges(nedges.begin(),
nedges.end());
std::sort(ordered_edges.begin(), ordered_edges.end(),
singular_edge_process_less);
for (std::vector<cpolyedge_t *>::const_iterator n_it =
ordered_edges.begin(); n_it != ordered_edges.end(); ++n_it) {
nq->push(*n_it);
cnt++;
}
@@ -558,6 +975,8 @@ ON_Brep_CDT_Tessellate(struct ON_Brep_CDT_State *s_cdt, int face_cnt, int *faces
trimesh_error_report(s_cdt, valid_fcnt, valid_vcnt, valid_faces, valid_vertices, &se);
}
bg_free_trimesh_solid_errors(&se);
bu_free(valid_faces, "faces");
bu_free(valid_vertices, "vertices");
@@ -610,7 +1029,7 @@ ON_Brep_CDT_Mesh(
std::set<ON_3dPoint *> vfnormals;
std::set<ON_3dPoint *> flip_normals;
for (size_t fi = 0; fi < active_faces.size(); fi++) {
cdt_mesh_t *fmesh = &s_cdt->fmeshes[(int)fi];
cdt_mesh_t *fmesh = &s_cdt->fmeshes[active_faces[fi]];
RTree<size_t, double, 3>::Iterator tree_it;
fmesh->tris_tree.GetFirst(tree_it);
size_t t_ind;
@@ -699,8 +1118,10 @@ ON_Brep_CDT_Mesh(
// Iterate over faces, adding points and faces to BoT container. Note: all
// 3D points should be geometrically unique in this final container.
int face_cnt = 0;
std::vector<int> output_face_ids;
output_face_ids.reserve(triangle_cnt);
for (size_t fi = 0; fi < active_faces.size(); fi++) {
cdt_mesh_t *fmesh = &s_cdt->fmeshes[(int)fi];
cdt_mesh_t *fmesh = &s_cdt->fmeshes[active_faces[fi]];
RTree<size_t, double, 3>::Iterator tree_it;
fmesh->tris_tree.GetFirst(tree_it);
size_t t_ind;
@@ -725,11 +1146,149 @@ ON_Brep_CDT_Mesh(
}
}
output_face_ids.push_back(fmesh->f_id);
face_cnt++;
++tree_it;
}
}
/* A complete, topologically solid BREP supplies authoritative shell
* orientation. If tessellation produced an otherwise closed/manifold
* mesh with inconsistent triangle winding, repair that winding only when
* the fully synchronized candidate validates as a solid. Partial/open
* meshes retain their original per-face ordering. */
if (!exp_face_cnt || !exp_faces) {
const bool source_is_solid = s_cdt->orig_brep &&
s_cdt->orig_brep->IsSolid();
const int removed = closed_mesh_component_filter(*faces, fcnt,
*vertices, *vcnt, face_normals ? *face_normals : NULL,
output_face_ids.data(), std::max(s_cdt->absmin,
ON_ZERO_TOLERANCE), source_is_solid);
if (removed > 0) {
bu_log("%s: removed %d redundant open triangle artifact%s after "
"transactional component validation\n",
s_cdt->name ? s_cdt->name : "BREP", removed,
removed == 1 ? "" : "s");
}
if (source_is_solid) {
const int synchronized = closed_mesh_orientation_sync(*faces, *fcnt,
*vertices, *vcnt, face_normals ? *face_normals : NULL);
if (synchronized > 0) {
bu_log("%s: synchronized %d triangle orientations after complete closed-mesh validation\n",
s_cdt->name ? s_cdt->name : "BREP", synchronized);
}
}
}
return 0;
}
int
cdt_test_spurious_components(void)
{
fastf_t vertices[] = {
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
2.0, 0.0, 0.0,
3.0, 0.0, 0.0,
2.0, 1.0, 0.0,
4.0, 0.0, 0.0
};
int tetra_and_open[] = {
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
4, 5, 6
};
int face_count = 5;
if (closed_mesh_component_filter(tetra_and_open, &face_count, vertices,
8, NULL) != 1 || face_count != 4)
return 1;
int two_open_components[] = {0, 1, 2, 4, 5, 6};
face_count = 2;
if (closed_mesh_component_filter(two_open_components, &face_count,
vertices, 8, NULL) != 0 || face_count != 2)
return 2;
fastf_t two_tetra_vertices[] = {
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
3.0, 0.0, 0.0, 4.0, 0.0, 0.0,
3.0, 1.0, 0.0, 3.0, 0.0, 1.0
};
int two_tetra_faces[] = {
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
4, 6, 5, 4, 5, 7, 4, 7, 6, 5, 6, 7
};
face_count = 8;
if (closed_mesh_component_filter(two_tetra_faces, &face_count,
two_tetra_vertices, 8, NULL) != 0 || face_count != 8)
return 3;
fastf_t open_duplicate_vertices[] = {
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
1.0, 1.0, 0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
1.0, 1.0, 0.0
};
int open_duplicate_faces[] = {
0, 1, 2, 0, 2, 3, 4, 5, 6
};
const int open_duplicate_sources[] = {7, 7, 7};
face_count = 3;
if (closed_mesh_component_filter(open_duplicate_faces, &face_count,
open_duplicate_vertices, 7, NULL, open_duplicate_sources,
1.0e-12) != 1 || face_count != 2)
return 4;
open_duplicate_vertices[20] = 0.1;
int distinct_faces[] = {0, 1, 2, 0, 2, 3, 4, 5, 6};
face_count = 3;
if (closed_mesh_component_filter(distinct_faces, &face_count,
open_duplicate_vertices, 7, NULL, open_duplicate_sources,
1.0e-6, true, false) != 0 || face_count != 3)
return 5;
/* A redundant triangle attached to a closed shell through an edge with
* three uses is not part of that manifold shell. This is the compact
* form of the NIST spherical-cap shading regression: treating the
* non-manifold edge as ordinary adjacency hid the extra component from
* the cleanup audit even though downstream BoT topology detected it. */
fastf_t nonmanifold_vertices[] = {
0.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0, 0.0, 1.0,
1.0, 0.0, 0.0
};
int nonmanifold_faces[] = {
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
0, 2, 4
};
face_count = 5;
if (closed_mesh_component_filter(nonmanifold_faces, &face_count,
nonmanifold_vertices, 5, NULL) != 1 || face_count != 4)
return 6;
/* Face-local vertices can collapse to one shared output vertex. The
* resulting index-degenerate triangle must not survive into a BoT or
* distort component discovery. */
int degenerate_faces[] = {
0, 2, 1, 0, 1, 3, 0, 3, 2, 1, 2, 3,
4, 4, 1
};
face_count = 5;
if (closed_mesh_component_filter(degenerate_faces, &face_count,
nonmanifold_vertices, 5, NULL) != 1 || face_count != 4)
return 7;
int invalid_faces[] = {0, 1, 2, 3, 3, 99};
const int invalid_original[] = {0, 1, 2, 3, 3, 99};
face_count = 2;
if (closed_mesh_component_filter(invalid_faces, &face_count,
nonmanifold_vertices, 5, NULL) != 0 || face_count != 2 ||
!std::equal(invalid_faces, invalid_faces + 6, invalid_original))
return 8;
return 0;
}
-1
View File
@@ -861,4 +861,3 @@ int ON_Brep_CDT_VList(
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
-1
View File
@@ -1675,4 +1675,3 @@ finalize_rtrees(struct ON_Brep_CDT_State *s_cdt)
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+125 -5
View File
@@ -29,6 +29,7 @@
#include "common.h"
#include <vector>
#include <atomic>
#include <list>
#include <map>
#include <stack>
@@ -57,6 +58,8 @@
#include "assert.h"
#include "vmath.h"
#include "bu/parallel.h"
#include "bu/env.h"
#include "bu/cv.h"
#include "bu/opt.h"
@@ -290,6 +293,55 @@ getEdgePoints(ON_BrepTrim &trim,
}
/* Boundary points shared by two faces must be taken from their common
* 3-D BREP edge, not independently from the two surface lifts. Valid
* trims may differ from that edge by their asserted edge tolerance; using
* the lifts directly opens a visible crack in the fast shaded-display
* mesh. A trim and its edge are not required to use identical parameter
* domains, so verify the normalized correspondence at every sample before
* changing any point. If it is not valid for the entire trim, leave the
* trim on the existing general path rather than producing a mixture of
* surface and edge points. */
const ON_BrepEdge *edge = trim.Edge();
const ON_Curve *edge_curve = edge ? edge->EdgeCurveOf() : NULL;
const ON_Interval edge_domain = edge_curve ? edge_curve->Domain() :
ON_Interval::EmptyInterval;
const double edge_tolerance = edge ? std::max(tol->dist,
edge->m_tolerance) : tol->dist;
bool edge_parameterization_matches = edge_curve && range.IsIncreasing() &&
edge_domain.IsIncreasing();
if (edge_parameterization_matches) {
for (std::map<double, BrepTrimPoint *>::iterator sample =
param_points->begin(); sample != param_points->end(); ++sample) {
BrepTrimPoint *point = sample->second;
if (!point || !point->p3d) {
edge_parameterization_matches = false;
break;
}
double fraction = range.NormalizedParameterAt(point->t);
if (trim.m_bRev3d)
fraction = 1.0 - fraction;
const ON_3dPoint edge_point = edge_curve->PointAt(
edge_domain.ParameterAt(fraction));
if (!edge_point.IsValid() ||
edge_point.DistanceTo(*point->p3d) > edge_tolerance) {
edge_parameterization_matches = false;
break;
}
}
}
if (edge_parameterization_matches) {
for (std::map<double, BrepTrimPoint *>::iterator sample =
param_points->begin(); sample != param_points->end(); ++sample) {
BrepTrimPoint *point = sample->second;
double fraction = range.NormalizedParameterAt(point->t);
if (trim.m_bRev3d)
fraction = 1.0 - fraction;
*point->p3d = edge_curve->PointAt(edge_domain.ParameterAt(fraction));
}
}
return param_points;
}
@@ -2298,6 +2350,55 @@ bg_CDT(std::vector<int> &faces, std::vector<fastf_t> &pnt_norms, std::vector<fas
}
struct fast_cdt_face_result {
std::vector<int> faces;
std::vector<fastf_t> norms;
std::vector<fastf_t> pnts;
};
struct fast_cdt_parallel_state {
const ON_Brep *brep;
const struct bg_tess_tol *ttol;
const struct bn_tol *tol;
std::vector<fast_cdt_face_result> *results;
std::atomic<int> next_face;
std::atomic<size_t> result_bytes;
std::atomic<bool> over_budget;
size_t result_budget;
};
static void
fast_cdt_face_worker(int UNUSED(cpu), void *data)
{
fast_cdt_parallel_state *state = (fast_cdt_parallel_state *)data;
const int face_cnt = state->brep->m_F.Count();
for (;;) {
if (state->over_budget.load())
return;
const int face_index = state->next_face.fetch_add(1);
if (face_index >= face_cnt)
return;
const ON_BrepFace& face = state->brep->m_F[face_index];
bg_CDT((*state->results)[face_index].faces,
(*state->results)[face_index].norms,
(*state->results)[face_index].pnts,
face, state->ttol, state->tol);
const fast_cdt_face_result &result = (*state->results)[face_index];
const size_t result_bytes = result.faces.size() * sizeof(int) +
(result.norms.size() + result.pnts.size()) * sizeof(fastf_t);
if (state->result_bytes.fetch_add(result_bytes) + result_bytes > state->result_budget) {
state->over_budget = true;
(*state->results)[face_index].faces.clear();
(*state->results)[face_index].norms.clear();
(*state->results)[face_index].pnts.clear();
return;
}
}
}
int
brep_cdt_fast(int **faces, int *face_cnt, vect_t **pnt_norms, point_t **pnts, int *pntcnt,
const ON_Brep *brep, int index, const struct bg_tess_tol *ttol, const struct bn_tol *tol)
@@ -2326,10 +2427,30 @@ brep_cdt_fast(int **faces, int *face_cnt, vect_t **pnt_norms, point_t **pnts, in
std::vector<fastf_t> all_pnts;
if (index == -1) {
for (index = 0; index < brep->m_F.Count(); index++) {
const ON_BrepFace& face = brep->m_F[index];
bg_CDT(all_faces, all_norms, all_pnts, face, ttol, tol);
}
/* Each face produces an independent set of indices and points. Keep
* those results separate while workers run, then concatenate them in
* face order. This avoids locking the hot CDT path and keeps output
* stable across runs. The BRep trim-user cleanup below remains serial. */
std::vector<fast_cdt_face_result> face_results(brep->m_F.Count());
ssize_t available = bu_mem(BU_MEM_AVAIL, NULL);
size_t result_budget = (available > 0) ? (size_t)available / 4 :
(size_t)512 * 1024 * 1024;
fast_cdt_parallel_state state = {
brep, ttol, tol, &face_results, 0, 0, false, result_budget
};
bu_parallel(fast_cdt_face_worker, 0, &state);
if (state.over_budget.load()) {
bu_log("brep_cdt_fast: tessellation result exceeded memory budget (%zu bytes)\n", result_budget);
} else {
for (size_t fi = 0; fi < face_results.size(); fi++) {
const size_t point_offset = all_pnts.size() / 3;
for (size_t i = 0; i < face_results[fi].faces.size(); i++) {
all_faces.push_back((int)point_offset + face_results[fi].faces[i]);
}
all_norms.insert(all_norms.end(), face_results[fi].norms.begin(), face_results[fi].norms.end());
all_pnts.insert(all_pnts.end(), face_results[fi].pnts.begin(), face_results[fi].pnts.end());
}
}
} else if (index < brep->m_F.Count()) {
const ON_BrepFaceArray& brep_faces = brep->m_F;
if (index < brep_faces.Count()) {
@@ -2387,4 +2508,3 @@ brep_cdt_fast(int **faces, int *face_cnt, vect_t **pnt_norms, point_t **pnts, in
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+297 -20
View File
@@ -490,6 +490,27 @@ cpolygon_t::add_ordered_edge(const struct edge2d_t &e)
return nedge;
}
/* Return a stable boundary-loop starting edge. poly is a pointer set, so
* dereferencing begin() makes traversal order depend on allocator layout.
* Geometry algorithms which walk next/prev from that arbitrary entry can then
* assign samples or exercise bounded refinements in a different order. */
cpolyedge_t *
cpolygon_t::first_edge() const
{
cpolyedge_t *first = NULL;
for (std::set<cpolyedge_t *>::const_iterator edge = poly.begin();
edge != poly.end(); ++edge) {
cpolyedge_t *candidate = *edge;
if (!candidate)
continue;
if (!first || candidate->v2d[0] < first->v2d[0] ||
(candidate->v2d[0] == first->v2d[0] &&
candidate->v2d[1] < first->v2d[1]))
first = candidate;
}
return first;
}
void
cpolygon_t::remove_ordered_edge(const struct edge2d_t &e)
{
@@ -1150,7 +1171,17 @@ cpolygon_t::cdt(triangulation_t ttype)
int *opoly = (int *)bu_calloc(poly.size()+1, sizeof(int), "polygon points");
size_t vcnt = 1;
cpolyedge_t *pe = (*poly.begin());
/* bg_nested_poly_triangulate may select a different valid diagonal when
* the same closed boundary is cyclically rotated. Downstream face-mesh
* stitching requires that choice to be independent of allocator layout,
* so use the same stable topological start as the LSCM projection. */
cpolyedge_t *pe = first_edge();
last_cdt_start_vertex = pe ? pe->v2d[0] : -1;
if (!pe) {
bu_free(opoly, "polygon points");
bu_free(bgp_2d, "free libbg 2d points array)");
return false;
}
cpolyedge_t *first = pe;
cpolyedge_t *next = pe->next;
@@ -3354,6 +3385,33 @@ loop_to_bgpoly(cpolygon_t *loop)
return opoly;
}
static bool
point_on_polygon_boundary(const point2d_t *points, int point_index,
const int *polygon, size_t polygon_point_count, double tolerance_sq)
{
if (!points || point_index < 0 || !polygon || polygon_point_count < 2)
return false;
const double px = points[point_index][X];
const double py = points[point_index][Y];
for (size_t edge = 0; edge + 1 < polygon_point_count; ++edge) {
const int first = polygon[edge];
const int second = polygon[edge + 1];
const double ax = points[first][X];
const double ay = points[first][Y];
const double dx = points[second][X] - ax;
const double dy = points[second][Y] - ay;
const double length_sq = dx * dx + dy * dy;
double parameter = length_sq > DBL_EPSILON ?
((px - ax) * dx + (py - ay) * dy) / length_sq : 0.0;
parameter = std::max(0.0, std::min(1.0, parameter));
const double ex = px - (ax + parameter * dx);
const double ey = py - (ay + parameter * dy);
if (ex * ex + ey * ey <= tolerance_sq)
return true;
}
return false;
}
bool
cdt_mesh_t::cdt()
{
@@ -3440,8 +3498,10 @@ cdt_mesh_t::cdt()
}
}
// Build Steiner array, filtering out any points that fall inside a hole.
// Sampled interior points inside trimmed-away hole regions confuse detria.
// Build the Steiner array, filtering points in trimmed-away holes and
// points numerically on a constrained boundary. Detria rejects a
// Steiner point on a constrained segment; the boundary already supplies
// the exact sample, so retaining that duplicate adds no geometry.
// Pre-build per-hole 2D polygon arrays once (reused for each Steiner point test).
std::vector<std::vector<double>> hole_polys_flat; // pairs of (x,y) stored flat
std::vector<size_t> hole_polys_npts;
@@ -3460,8 +3520,24 @@ cdt_mesh_t::cdt()
std::vector<int> steiner_vec;
steiner_vec.reserve(m_interior_pnts.size());
const double scaled_u_range = (umax - umin) * uscale;
const double scaled_v_range = (vmax - vmin) * vscale;
const double boundary_scale = std::max(1.0,
hypot(scaled_u_range, scaled_v_range));
const double boundary_tolerance =
sqrt(DBL_EPSILON) * boundary_scale;
const double boundary_tolerance_sq =
boundary_tolerance * boundary_tolerance;
for (auto p_it = m_interior_pnts.begin(); p_it != m_interior_pnts.end(); p_it++) {
int idx = (int)*p_it;
bool on_boundary = point_on_polygon_boundary(bgp_2d, idx, opoly,
outer_loop.poly.size() + 1, boundary_tolerance_sq);
for (int hi = 0; hi < holes_cnt && !on_boundary; ++hi) {
on_boundary = point_on_polygon_boundary(bgp_2d, idx,
holes_array[hi], holes_npts[hi], boundary_tolerance_sq);
}
if (on_boundary)
continue;
bool in_hole = false;
for (int hi = 0; hi < holes_cnt && !in_hole; hi++) {
point2d_t test_pnt;
@@ -3824,7 +3900,77 @@ cdt_mesh_t::repair()
// Now that the out-and-out problem triangles have been handled,
// remesh near singularities to try and produce more reasonable
// triangles.
// triangles. This is a quality refinement, not a topology repair. Keep
// it transactional: process_seed_tri may have replaced several patches
// before a later singular seed proves unmeshable. Returning with those
// partial mutations used to leak isolated triangles into an otherwise
// valid face mesh (notably the NIST MBE PMI 6 spherical cap).
boundary_edges_stale = true;
boundary_edges_update();
const bool pre_singularity_mesh_valid = problem_edges.empty();
std::vector<triangle_t> pre_singularity_triangle_store;
std::vector<size_t> pre_singularity_active_triangles;
decltype(v2edges) pre_singularity_v2edges;
decltype(v2tris) pre_singularity_v2tris;
decltype(edges2tris) pre_singularity_edges2tris;
decltype(uedges2tris) pre_singularity_uedges2tris;
decltype(boundary_edges) pre_singularity_boundary_edges;
decltype(problem_edges) pre_singularity_problem_edges;
if (pre_singularity_mesh_valid && has_singularities) {
/* Preserve the complete indexed mesh state rather than rebuilding it
* through tri_add. Coincident singularity triangles may intentionally
* have the same three 3-D vertex indices but different orientations;
* tri_add's duplicate filter would discard one and make rollback itself
* non-transactional. */
pre_singularity_triangle_store = tris_vect;
pre_singularity_v2edges = v2edges;
pre_singularity_v2tris = v2tris;
pre_singularity_edges2tris = edges2tris;
pre_singularity_uedges2tris = uedges2tris;
pre_singularity_boundary_edges = boundary_edges;
pre_singularity_problem_edges = problem_edges;
RTree<size_t, double, 3>::Iterator snapshot_it;
tris_tree.GetFirst(snapshot_it);
while (!snapshot_it.IsNull()) {
pre_singularity_active_triangles.push_back(*snapshot_it);
++snapshot_it;
}
}
const auto restore_pre_singularity_mesh = [&]() {
if (!pre_singularity_mesh_valid ||
pre_singularity_triangle_store.empty() ||
pre_singularity_active_triangles.empty())
return false;
tris_vect = pre_singularity_triangle_store;
tris_tree.RemoveAll();
for (size_t triangle_index : pre_singularity_active_triangles) {
if (triangle_index >= tris_vect.size()) return false;
triangle_t &triangle = tris_vect[triangle_index];
triangle.m = this;
ON_3dPoint *point = pnts[triangle.v[0]];
ON_BoundingBox bounds(*point, *point);
for (int vertex = 1; vertex < 3; ++vertex) {
point = pnts[triangle.v[vertex]];
bounds.Set(*point, true);
}
const double minimum[3] = {bounds.Min().x, bounds.Min().y,
bounds.Min().z};
const double maximum[3] = {bounds.Max().x, bounds.Max().y,
bounds.Max().z};
tris_tree.Insert(minimum, maximum, triangle_index);
}
v2edges = pre_singularity_v2edges;
v2tris = pre_singularity_v2tris;
edges2tris = pre_singularity_edges2tris;
uedges2tris = pre_singularity_uedges2tris;
boundary_edges = pre_singularity_boundary_edges;
problem_edges = pre_singularity_problem_edges;
seed_tris.clear();
new_tris.clear();
boundary_edges_stale = false;
bounding_box_stale = true;
return true;
};
if (has_singularities) {
std::vector<triangle_t> s_tris = this->singularity_triangles();
@@ -3839,6 +3985,11 @@ cdt_mesh_t::repair()
bool pseed = process_seed_tri(seed, false, deg, NULL);
if (!pseed || seed_tris.size() >= st_size) {
if (restore_pre_singularity_mesh()) {
bu_log("Face %d: retained the valid pre-refinement mesh after singularity quality remeshing made no progress\n",
f_id);
return true;
}
std::cerr << f_id << ": Error - failed to process refinement seed triangle!\n";
struct bu_vls fname = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&fname, "%d-failed_seed.plot3", f_id);
@@ -3849,7 +4000,6 @@ cdt_mesh_t::repair()
serialize(bu_vls_cstr(&fname));
bu_vls_free(&fname);
return false;
break;
}
st_size = seed_tris.size();
@@ -4597,13 +4747,13 @@ cdt_mesh_t::serialize(const char *fname)
sfile << m_it->first << "," << m_it->second << "\n";
}
sfile << "TRIANGLES_VECT" << tris_vect.size() << "\n";
sfile << "TRIANGLES_VECT " << tris_vect.size() << "\n";
std::vector<triangle_t>::iterator t_it;
for (t_it = tris_vect.begin(); t_it != tris_vect.end(); t_it++) {
sfile << (*t_it).v[0] << "," << (*t_it).v[1] << "," << (*t_it).v[2] << "," << (*t_it).ind << "\n";
}
sfile << "TRIANGLES_TREE" << tris_tree.Count() << "\n";
sfile << "TRIANGLES_TREE " << tris_tree.Count() << "\n";
RTree<size_t, double, 3>::Iterator tree_it;
size_t t_ind;
triangle_t tri;
@@ -4679,9 +4829,11 @@ cdt_mesh_t::deserialize(const char *fname)
if (std::getline(sfile,switch_line)) {
if (switch_line == std::string("V1")) {
version = 1;
} else if (switch_line == std::string("V2")) {
version = 2;
}
}
if (version < 1 || version > 1) {
if (version < 1 || version > 2) {
std::cerr << "Invalid deserialization file - format version " << switch_line << "\n";
return false;
}
@@ -4716,11 +4868,27 @@ cdt_mesh_t::deserialize(const char *fname)
problem_edges.clear();
while (std::getline(sfile,switch_line)) {
std::cout << switch_line << "\n";
size_t spos = switch_line.find_first_of(' ');
std::string dtype = switch_line.substr(0, spos);
switch_line.erase(0, spos+1);
long lcnt = std::stol(switch_line);
std::string dtype;
std::string count_text;
if (spos != std::string::npos) {
dtype = switch_line.substr(0, spos);
count_text = switch_line.substr(spos + 1);
} else if (switch_line.compare(0, 14, "TRIANGLES_VECT") == 0) {
/* V2 snapshots written before the delimiter fix concatenated these
* two record names and their counts. Accept those diagnostics so
* existing failure captures remain replayable. */
dtype = "TRIANGLES_VECT";
count_text = switch_line.substr(14);
} else if (switch_line.compare(0, 14, "TRIANGLES_TREE") == 0) {
dtype = "TRIANGLES_TREE";
count_text = switch_line.substr(14);
} else {
std::cerr << "Malformed serialization record: " << switch_line
<< "\nSerialization import failed.\n";
return false;
}
long lcnt = std::stol(count_text);
if (dtype == std::string("POINTS")) {
for (long i = 0; i < lcnt; i++) {
@@ -4781,7 +4949,8 @@ cdt_mesh_t::deserialize(const char *fname)
continue;
}
if (dtype == std::string("TRIANGLES_VECT")) {
if (dtype == std::string("TRIANGLES") ||
dtype == std::string("TRIANGLES_VECT")) {
for (long i = 0; i < lcnt; i++) {
std::string tline;
std::getline(sfile,tline);
@@ -4796,9 +4965,17 @@ cdt_mesh_t::deserialize(const char *fname)
long v2 = std::stol(v2str);
long v3 = std::stol(v3str);
triangle_t tri(v1, v2, v3);
// The tree is loaded separately - just do the basic population
tri.ind = tris_vect.size();
tris_vect.push_back(tri);
if (dtype == std::string("TRIANGLES")) {
/* V1 stored only active triangles. Rebuild the spatial index and
* adjacency maps through the normal insertion path. */
tri.m = this;
tri_add(tri);
} else {
/* V2 stores inactive vector entries as well; its following tree
* record identifies and indexes the active subset. */
tri.ind = tris_vect.size();
tris_vect.push_back(tri);
}
}
continue;
}
@@ -5285,10 +5462,17 @@ cdt_mesh_t::lscm_reproject(cpolygon_t *polygon)
// ── Step 1: Walk the boundary loop ───────────────────────────────────────
// Collect exactly poly.size() unique boundary vertices (one per edge,
// using each edge's start vertex) so LSCMParameterization receives N
// distinct vertices without a closing repeat.
// distinct vertices without a closing repeat. polygon->poly is a set of
// pointers, so its begin() depends on heap layout. The choice of starting
// edge rotates the convex LSCM boundary and can change the triangulation
// of difficult patches. Choose the lexicographically first topological
// edge instead, making the result independent of allocation order and
// unrelated inputs such as the requested output object name.
std::vector<int32_t> bnd_loop;
{
cpolyedge_t *pe = *polygon->poly.begin();
cpolyedge_t *pe = polygon->first_edge();
if (!pe)
return false;
cpolyedge_t *cur = pe;
do {
bnd_loop.push_back((int32_t)cur->v2d[0]);
@@ -6172,6 +6356,100 @@ void cdt_mesh_t::polygon_print_3d(cpolygon_t *polygon)
std::cout << "\n";
}
/* Exercise the allocator-independent polygon start used by the actual CDT
* call. A square is deliberately co-circular, so rotating its boundary input
* can select the opposite valid diagonal unless the triangulator receives a
* stable starting vertex. Keep this compact guard here because cpolygon_t is
* an internal type whose symbols are not exported from libbrep. */
int
cdt_test_boundary_start(void)
{
const auto initialize_regular_polygon = [](cpolygon_t &polygon,
int point_count, int first_allocated_edge) {
for (int point = 0; point < point_count; ++point) {
const double angle = 2.0 * ON_PI * point / point_count;
ON_2dPoint p(cos(angle), sin(angle));
polygon.add_point(p, point);
}
for (int offset = 0; offset < point_count; ++offset) {
const int start = (first_allocated_edge + offset) % point_count;
edge2d_t edge(start, (start + 1) % point_count);
polygon.add_ordered_edge(edge);
}
};
const auto canonical_triangles = [](const cpolygon_t &polygon) {
std::set<std::vector<long> > result;
for (std::set<triangle_t>::const_iterator triangle =
polygon.tris.begin(); triangle != polygon.tris.end(); ++triangle) {
std::vector<long> vertices;
vertices.push_back(triangle->v[0]);
vertices.push_back(triangle->v[1]);
vertices.push_back(triangle->v[2]);
std::sort(vertices.begin(), vertices.end());
result.insert(vertices);
}
return result;
};
const auto release_edges = [](cpolygon_t &polygon) {
for (std::set<cpolyedge_t *>::iterator edge = polygon.poly.begin();
edge != polygon.poly.end(); ++edge)
delete *edge;
polygon.poly.clear();
};
for (int point_count = 4; point_count <= 12; ++point_count) {
for (int first_allocated_edge = 1;
first_allocated_edge < point_count; ++first_allocated_edge) {
cpolygon_t edge_zero_first;
cpolygon_t rotated_first;
initialize_regular_polygon(edge_zero_first, point_count, 0);
initialize_regular_polygon(rotated_first, point_count,
first_allocated_edge);
for (cpolyedge_t *edge : edge_zero_first.poly) {
if (!edge || edge->trim_ind != -1 || edge->loop_type != 0 ||
edge->defines_spnt || edge->split_status != 0 ||
edge->eseg != NULL) {
release_edges(edge_zero_first);
release_edges(rotated_first);
return 1;
}
}
const bool valid = edge_zero_first.cdt() && rotated_first.cdt();
const std::set<std::vector<long> > first =
canonical_triangles(edge_zero_first);
const std::set<std::vector<long> > rotated =
canonical_triangles(rotated_first);
release_edges(edge_zero_first);
release_edges(rotated_first);
if (!valid || edge_zero_first.last_cdt_start_vertex != 0 ||
rotated_first.last_cdt_start_vertex != 0 ||
first.size() != static_cast<size_t>(point_count - 2) ||
first != rotated)
return 1;
}
}
return 0;
}
int
cdt_test_boundary_steiner_filter(void)
{
point2d_t points[6] = {
{0.0, 0.0}, {2.0, 0.0}, {2.0, 2.0}, {0.0, 2.0},
{1.0, 0.0}, {1.0, 1.0}
};
const int polygon[] = {0, 1, 2, 3, 0};
const double tolerance = sqrt(DBL_EPSILON) * sqrt(8.0);
if (!point_on_polygon_boundary(points, 4, polygon, 5,
tolerance * tolerance))
return 1;
if (point_on_polygon_boundary(points, 5, polygon, 5,
tolerance * tolerance))
return 1;
return 0;
}
// PImpl exposure of some mesh operations for use in tests
struct cdt_bmesh_impl {
cdt_mesh_t fmesh;
@@ -6199,8 +6477,7 @@ cdt_bmesh_deserialize(const char *fname, struct cdt_bmesh *m)
{
if (!fname || !m) return -1;
if (!bu_file_exists(fname, NULL)) return -1;
m->i->fmesh.deserialize(fname);
return 0;
return m->i->fmesh.deserialize(fname) ? 0 : -1;
}
int
+18
View File
@@ -441,7 +441,15 @@ class cpolyedge_t
polygon = NULL;
prev = NULL;
next = NULL;
trim_ind = -1;
loop_type = 0;
defines_spnt = false;
spnt = ON_2dPoint::UnsetPoint;
trim_start = 0.0;
trim_end = 0.0;
split_status = 0;
v1_dist = 0.0;
v2_dist = 0.0;
eseg = NULL;
};
@@ -453,7 +461,15 @@ class cpolyedge_t
polygon = NULL;
prev = NULL;
next = NULL;
trim_ind = -1;
loop_type = 0;
defines_spnt = false;
spnt = ON_2dPoint::UnsetPoint;
trim_start = 0.0;
trim_end = 0.0;
split_status = 0;
v1_dist = 0.0;
v2_dist = 0.0;
eseg = NULL;
};
@@ -506,6 +522,7 @@ class cpolygon_t
cpolyedge_t *add_edge(const struct uedge2d_t &e);
void remove_edge(const struct uedge2d_t &e);
std::set<cpolyedge_t *> replace_edges(std::set<uedge_t> &new_edges, std::set<uedge_t> &old_edges);
cpolyedge_t *first_edge() const;
/* Means to update the point array if we're incrementally building. orig_index should
* identify the same point in the parent's index, so cdt() knows what triangles to
@@ -561,6 +578,7 @@ class cpolygon_t
std::set<triangle_t> visited_triangles;
std::set<uedge2d_t> active_edges;
std::set<uedge2d_t> self_isect_edges;
long last_cdt_start_vertex = -1;
ON_Plane tplane;
ON_Plane fit_plane;
ON_3dVector pdir;
-1
View File
@@ -1050,4 +1050,3 @@ GetInteriorPoints(struct ON_Brep_CDT_State *s_cdt, int face_index)
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+319
View File
@@ -0,0 +1,319 @@
/* C O B B . C P P
* BRL-CAD
*
* Copyright (c) 2013-2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* Based off of code from Ayam:
*
* This software is copyrighted (c) 1998-2012 by
* Randolf Schultz (randolf.schultz@gmail.com).
* All rights reserved.
*
* The author hereby grants permission to use, copy, modify, distribute,
* and license this software and its documentation for any purpose, provided
* that existing copyright notices are retained in all copies and that this
* notice is included verbatim in any distributions. No written agreement,
* license, or royalty fee is required for any of the authorized uses.
* Modifications to this software may be copyrighted by their authors
* and need not follow the licensing terms described here, provided that
* the new terms are clearly indicated on the first page of each file where
* they apply.
*
* IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY
* FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
* DERIVATIVES THEREOF, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
* IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE
* NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
* MODIFICATIONS.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
/** @file cobb.cpp
*
* J. E. Cobb's six-patch rational Bezier sphere, with both the historical
* independent-face topology and a shared solid topology for seam tests.
*/
#include "common.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include "brep/cobb.h"
#include "opennurbs.h"
ON_BezierSurface *
ON_Brep_CobbSphereFace(double rotation_x, double rotation_z)
{
const double t = sqrt(3.0);
const double d = sqrt(2.0);
const double s = sqrt(6.0);
const double cvs[5][5][4] = {
{
{4.0*(1.0-t), 4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)},
{-d, d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
{0.0, 4.0*(1.0-2.0*t)/3.0, 4.0*(1.0-2.0*t)/3.0,
4.0*(5.0-t)/3.0},
{d, d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
{4.0*(t-1.0), 4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)}
},
{
{d*(t-4.0), -d, d*(t-4.0), d*(3.0*t-2.0)},
{(2.0-3.0*t)/2.0, (2.0-3.0*t)/2.0, -(t+6.0)/2.0,
(t+6.0)/2.0},
{0.0, d*(2.0*t-7.0)/3.0, -5.0*s/3.0, d*(t+6.0)/3.0},
{(3.0*t-2.0)/2.0, (2.0-3.0*t)/2.0, -(t+6.0)/2.0,
(t+6.0)/2.0},
{d*(4.0-t), -d, d*(t-4.0), d*(3.0*t-2.0)}
},
{
{4.0*(1.0-2.0*t)/3.0, 0.0, 4.0*(1.0-2.0*t)/3.0,
4.0*(5.0-t)/3.0},
{d*(2.0*t-7.0)/3.0, 0.0, -5.0*s/3.0, d*(t+6.0)/3.0},
{0.0, 0.0, 4.0*(t-5.0)/3.0, 4.0*(5.0*t-1.0)/9.0},
{-d*(2.0*t-7.0)/3.0, 0.0, -5.0*s/3.0, d*(t+6.0)/3.0},
{-4.0*(1.0-2.0*t)/3.0, 0.0, 4.0*(1.0-2.0*t)/3.0,
4.0*(5.0-t)/3.0}
},
{
{d*(t-4.0), d, d*(t-4.0), d*(3.0*t-2.0)},
{(2.0-3.0*t)/2.0, -(2.0-3.0*t)/2.0, -(t+6.0)/2.0,
(t+6.0)/2.0},
{0.0, -d*(2.0*t-7.0)/3.0, -5.0*s/3.0, d*(t+6.0)/3.0},
{(3.0*t-2.0)/2.0, -(2.0-3.0*t)/2.0, -(t+6.0)/2.0,
(t+6.0)/2.0},
{d*(4.0-t), d, d*(t-4.0), d*(3.0*t-2.0)}
},
{
{4.0*(1.0-t), -4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)},
{-d, -d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
{0.0, -4.0*(1.0-2.0*t)/3.0, 4.0*(1.0-2.0*t)/3.0,
4.0*(5.0-t)/3.0},
{d, -d*(t-4.0), d*(t-4.0), d*(3.0*t-2.0)},
{4.0*(t-1.0), -4.0*(1.0-t), 4.0*(1.0-t), 4.0*(3.0-t)}
}
};
ON_BezierSurface *surface = new ON_BezierSurface(3, true, 5, 5);
surface->ReserveCVCapacity(100);
for (int i = 0; i < 5; ++i) {
for (int j = 0; j < 5; ++j)
surface->SetCV(i, j, ON_4dPoint(cvs[i][j][0], cvs[i][j][1],
cvs[i][j][2], cvs[i][j][3]));
}
const ON_3dPoint center(0.0, 0.0, 0.0);
surface->Rotate(rotation_x * ON_PI / 180.0,
ON_3dVector(1.0, 0.0, 0.0), center);
surface->Rotate(rotation_z * ON_PI / 180.0,
ON_3dVector(0.0, 0.0, 1.0), center);
return surface;
}
static ON_NurbsSurface *
cobb_nurbs_face(double rotation_x, double rotation_z, double radius,
const ON_3dPoint &origin)
{
ON_BezierSurface *bezier = ON_Brep_CobbSphereFace(rotation_x,
rotation_z);
if (!bezier)
return NULL;
ON_NurbsSurface *surface = ON_NurbsSurface::New();
if (!bezier->GetNurbForm(*surface)) {
delete surface;
delete bezier;
return NULL;
}
delete bezier;
surface->Scale(radius);
surface->Translate(ON_3dVector(origin.x, origin.y, origin.z));
return surface;
}
static bool
cobb_face_outward(const ON_Surface *surface, const ON_3dPoint &origin)
{
const ON_Interval u = surface->Domain(0);
const ON_Interval v = surface->Domain(1);
ON_3dPoint point;
ON_3dVector du;
ON_3dVector dv;
if (!surface->Ev1Der(u.Mid(), v.Mid(), point, du, dv))
return true;
return ON_CrossProduct(du, dv) * (point - origin) > 0.0;
}
ON_Brep *
ON_Brep_CobbSphereUnsewn(double radius, const ON_3dPoint &origin)
{
if (!(radius > 0.0) || !std::isfinite(radius) || !origin.IsValid())
return NULL;
const double rotations[6][2] = {
{0.0, 0.0}, {90.0, 0.0}, {180.0, 0.0},
{270.0, 0.0}, {90.0, 90.0}, {90.0, 270.0}
};
ON_Brep *brep = ON_Brep::New();
for (int i = 0; i < 6; ++i) {
ON_NurbsSurface *surface = cobb_nurbs_face(rotations[i][0],
rotations[i][1], radius, origin);
if (!surface) {
delete brep;
return NULL;
}
ON_BrepFace *face = brep->NewFace(*surface);
if (!face) {
delete surface;
delete brep;
return NULL;
}
face->m_bRev = !cobb_face_outward(surface, origin);
delete surface;
}
brep->Standardize();
brep->Compact();
return brep;
}
ON_Brep *
ON_Brep_CobbSphereSewn(double radius, const ON_3dPoint &origin)
{
if (!(radius > 0.0) || !std::isfinite(radius) || !origin.IsValid())
return NULL;
const double rotations[6][2] = {
{0.0, 0.0}, {90.0, 0.0}, {180.0, 0.0},
{270.0, 0.0}, {90.0, 90.0}, {90.0, 270.0}
};
const double coordinate_scale = std::max(std::max(radius, 1.0),
std::max(std::max(fabs(origin.x), fabs(origin.y)), fabs(origin.z)));
const double corner_tolerance = std::max(ON_ZERO_TOLERANCE,
128.0 * std::numeric_limits<double>::epsilon() * coordinate_scale);
ON_Brep *brep = ON_Brep::New();
for (int face_index = 0; face_index < 6; ++face_index) {
ON_NurbsSurface *surface = cobb_nurbs_face(rotations[face_index][0],
rotations[face_index][1], radius, origin);
if (!surface) {
delete brep;
return NULL;
}
const ON_Interval u = surface->Domain(0);
const ON_Interval v = surface->Domain(1);
const ON_3dPoint corners[4] = {
surface->PointAt(u.Min(), v.Min()),
surface->PointAt(u.Max(), v.Min()),
surface->PointAt(u.Max(), v.Max()),
surface->PointAt(u.Min(), v.Max())
};
int vid[4] = {-1, -1, -1, -1};
for (int corner = 0; corner < 4; ++corner) {
for (int vertex = 0; vertex < brep->m_V.Count(); ++vertex) {
if (corners[corner].DistanceTo(brep->m_V[vertex].Point()) <=
corner_tolerance) {
vid[corner] = vertex;
break;
}
}
}
/* Oriented outer-loop corner pairs for south, east, north, west. */
const int side_corners[4][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 0}};
int eid[4] = {-1, -1, -1, -1};
bool reversed[4] = {false, false, false, false};
for (int side = 0; side < 4; ++side) {
const int from = vid[side_corners[side][0]];
const int to = vid[side_corners[side][1]];
if (from < 0 || to < 0)
continue;
for (int edge = 0; edge < brep->m_E.Count(); ++edge) {
const int edge_from = brep->m_E[edge].m_vi[0];
const int edge_to = brep->m_E[edge].m_vi[1];
if ((edge_from == from && edge_to == to) ||
(edge_from == to && edge_to == from)) {
eid[side] = edge;
reversed[side] = edge_from != from;
break;
}
}
}
const bool outward = cobb_face_outward(surface, origin);
ON_BrepFace *face = brep->NewFace(surface, vid, eid, reversed);
if (!face) {
delete surface;
delete brep;
return NULL;
}
face->m_bRev = !outward;
for (int corner = 0; corner < 4; ++corner)
brep->m_V[vid[corner]].m_tolerance = 0.0;
for (int side = 0; side < 4; ++side)
brep->m_E[eid[side]].m_tolerance = 0.0;
}
for (int trim = 0; trim < brep->m_T.Count(); ++trim) {
brep->m_T[trim].m_tolerance[0] = 0.0;
brep->m_T[trim].m_tolerance[1] = 0.0;
}
brep->Standardize();
brep->Compact();
return brep;
}
double
ON_Brep_CobbSphereMaxRadialError(const ON_Brep *brep, double radius,
const ON_3dPoint &origin, int samples_per_direction)
{
if (!brep || !(radius > 0.0) || !std::isfinite(radius) ||
!origin.IsValid() || samples_per_direction < 2)
return std::numeric_limits<double>::infinity();
double maximum_error = 0.0;
for (int si = 0; si < brep->m_S.Count(); ++si) {
const ON_Surface *surface = brep->m_S[si];
if (!surface)
return std::numeric_limits<double>::infinity();
const ON_Interval u = surface->Domain(0);
const ON_Interval v = surface->Domain(1);
for (int i = 0; i < samples_per_direction; ++i) {
const double up = u.ParameterAt((double)i /
(double)(samples_per_direction - 1));
for (int j = 0; j < samples_per_direction; ++j) {
const double vp = v.ParameterAt((double)j /
(double)(samples_per_direction - 1));
const double sample_radius = surface->PointAt(up, vp).
DistanceTo(origin);
maximum_error = std::max(maximum_error,
fabs(sample_radius - radius));
}
}
}
return maximum_error;
}
/*
* Local Variables:
* tab-width: 8
* mode: C++
* c-basic-offset: 4
* indent-tabs-mode: t
* End:
* ex: shiftwidth=4 tabstop=8
*/
File diff suppressed because it is too large Load Diff
+32 -3
View File
@@ -5,8 +5,29 @@ find_package(Threads REQUIRED)
brlcad_addexec(test_brep_ppx ppx.cpp "libbrep" TEST)
brlcad_add_test(NAME brep_ppx COMMAND test_brep_ppx)
brlcad_addexec(test_brep_assembly assembly.cpp "libbrep" TEST)
brlcad_add_test(NAME brep_assembly COMMAND test_brep_assembly)
# Replay serialized face-mesh failures without rerunning a complete BRep
# tessellation. mesh.cpp emits this format at the exact repair failure site,
# making it useful both for diagnosis and for compact regression fixtures.
brlcad_addexec(test_brep_cdt_mesh brep_cdt_mesh.cpp "libbrep" TEST)
brlcad_add_test(
NAME brep_cdt_mesh_single_bad_edge
COMMAND test_brep_cdt_mesh "${CMAKE_CURRENT_SOURCE_DIR}/single_bad_edge.cdtmesh"
)
# A cyclic rotation of the same co-circular boundary must not change the CDT
# diagonal merely because its cpolyedge allocations have different addresses.
brlcad_addexec(test_brep_cdt_boundary_start brep_cdt_boundary_start.cpp "libbrep" TEST)
brlcad_add_test(NAME brep_cdt_boundary_start COMMAND test_brep_cdt_boundary_start)
# An interior sample exactly on a constrained face boundary must not be passed
# to the triangulator a second time as a Steiner point.
brlcad_addexec(test_brep_cdt_boundary_steiner brep_cdt_boundary_steiner.cpp "libbrep" TEST)
brlcad_add_test(NAME brep_cdt_boundary_steiner COMMAND test_brep_cdt_boundary_steiner)
# A closed source solid may not retain redundant open local-remesh islands
# after either retained-solid or same-source-face coverage validation.
brlcad_addexec(test_brep_cdt_spurious_components brep_cdt_spurious_components.cpp "libbrep" TEST)
brlcad_add_test(NAME brep_cdt_spurious_components COMMAND test_brep_cdt_spurious_components)
# Standalone CDT hole-triangulation tests: verify that bg_nested_poly_triangulate
# produces no triangles whose centroid falls inside a hole polygon, for two
@@ -26,13 +47,20 @@ endif()
brlcad_addexec(test_pullback_context pullback_context.cpp "libbrep;Threads::Threads" TEST)
brlcad_add_test(NAME brep_pullback_context COMMAND test_pullback_context)
# The adaptive SurfaceTree depth budget starts only after closed seams and all
# internal NURBS knot boundaries have established well-behaved subdomains.
brlcad_addexec(test_surfacetree_knots surfacetree_knots.cpp "libbrep" TEST)
brlcad_add_test(NAME brep_surfacetree_knots COMMAND test_surfacetree_knots)
cmakefiles(
CMakeLists.txt
ayam_hyperbolid.3dm
assembly.cpp
brep_cdt_invalid.cpp
brep_cdt_hole_face17.c
brep_cdt_hole_face27.c
brep_cdt_boundary_start.cpp
brep_cdt_boundary_steiner.cpp
brep_cdt_spurious_components.cpp
brep_cdt_mesh.cpp
cyl_hole.g
cyls_holes.g
@@ -43,6 +71,7 @@ cmakefiles(
plate_mode_samples_rhino.stp
pullback_context.cpp
single_bad_edge.cdtmesh
surfacetree_knots.cpp
test_curve_intersect.cpp
test_point_intersect.cpp
vsp.stp
@@ -0,0 +1,25 @@
/* B R E P _ C D T _ B O U N D A R Y _ S T A R T . 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.
*/
#include "common.h"
#include "bu/app.h"
#include "brep/cdt.h"
int
main(int argc, const char **argv)
{
bu_setprogname(argv[0]);
if (argc != 1)
return 1;
return cdt_test_boundary_start();
}
@@ -0,0 +1,19 @@
/* B R E P _ C D T _ B O U N D A R Y _ S T E I N E 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.
*/
#include "common.h"
#include "brep/cdt.h"
int
main()
{
return cdt_test_boundary_steiner_filter();
}
+1 -1
View File
@@ -31,6 +31,7 @@ main(int argc, const char **argv)
if (argc != 2) {
std::cerr << "brep_cdt_mesh <serialization_file>\n";
return 1;
}
struct cdt_bmesh *fmesh;
if (cdt_bmesh_create(&fmesh)) return -1;
@@ -57,4 +58,3 @@ main(int argc, const char **argv)
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
@@ -0,0 +1,19 @@
/* B R E P _ C D T _ S P U R I O U S _ C O M P O N E N T S . 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.
*/
#include "common.h"
#include "brep/cdt.h"
int
main()
{
return cdt_test_spurious_components();
}
+121
View File
@@ -0,0 +1,121 @@
/* S U R F A C E T R E E _ K N O T S . 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.
*/
#include "common.h"
#include <cmath>
#include <cstdio>
#include <list>
#include <vector>
#include "brep.h"
#include "brep/surfacetree.h"
int
main()
{
ON_NurbsSurface surface(3, false, 3, 3, 8, 7);
if (!surface.MakeClampedUniformKnotVector(0) ||
!surface.MakeClampedUniformKnotVector(1)) {
std::printf("FAIL: knot vector construction\n");
return 1;
}
const ON_Interval domain[2] = {surface.Domain(0), surface.Domain(1)};
for (int u = 0; u < surface.CVCount(0); ++u) {
for (int v = 0; v < surface.CVCount(1); ++v) {
const double uf = (double)u / (surface.CVCount(0) - 1);
const double vf = (double)v / (surface.CVCount(1) - 1);
const double x = domain[0].ParameterAt(uf);
const double y = domain[1].ParameterAt(vf);
const double z = 0.05 * std::sin(0.7 * u) * std::cos(0.6 * v);
if (!surface.SetCV(u, v, ON_3dPoint(x, y, z))) {
std::printf("FAIL: control point construction\n");
return 1;
}
}
}
if (!surface.IsValid()) {
std::printf("FAIL: invalid test surface\n");
return 1;
}
ON_Brep brep;
ON_BrepFace *face = brep.NewFace(surface);
if (!face) {
std::printf("FAIL: face construction\n");
return 1;
}
brlcad::SurfaceTree tree(face, false, 0);
if (!tree.Valid()) {
std::printf("FAIL: depth-zero SurfaceTree construction reason=%d\n",
(int)tree.Failure());
return 1;
}
const ON_Surface *tree_surface = face->SurfaceOf();
std::vector<double> spans[2];
size_t expected_leaves = 1;
for (int direction = 0; direction < 2; ++direction) {
const int span_count = tree_surface->SpanCount(direction);
if (span_count < 2) {
std::printf("FAIL: direction %d has only %d spans\n", direction,
span_count);
return 1;
}
spans[direction].resize((size_t)span_count + 1);
if (!tree_surface->GetSpanVector(direction,
spans[direction].data())) {
std::printf("FAIL: direction %d span extraction\n", direction);
return 1;
}
expected_leaves *= (size_t)span_count;
}
std::list<const brlcad::BBNode *> leaves;
tree.getLeaves(leaves);
if (leaves.size() != expected_leaves) {
std::printf("FAIL: depth-zero leaves=%zu expected=%zu\n", leaves.size(),
expected_leaves);
return 1;
}
for (std::list<const brlcad::BBNode *>::const_iterator leaf = leaves.begin();
leaf != leaves.end(); ++leaf) {
const ON_Interval interval[2] = {(*leaf)->m_u, (*leaf)->m_v};
for (int direction = 0; direction < 2; ++direction) {
for (size_t split = 1;
split + 1 < spans[direction].size(); ++split) {
const double knot = spans[direction][split];
if (knot > interval[direction].Min() &&
knot < interval[direction].Max()) {
std::printf("FAIL: leaf crosses direction %d knot %.17g "
"in [%.17g %.17g]\n", direction, knot,
interval[direction].Min(), interval[direction].Max());
return 1;
}
}
}
}
std::printf("SurfaceTree structural knots: spans=%d/%d leaves=%zu "
"adaptive-depth=0\n", tree_surface->SpanCount(0),
tree_surface->SpanCount(1), leaves.size());
return 0;
}
/*
* Local Variables:
* tab-width: 8
* mode: C++
* c-basic-offset: 4
* indent-tabs-mode: t
* End:
* ex: shiftwidth=4 tabstop=8
*/
+1 -1
View File
@@ -578,7 +578,7 @@ static const char *
vdir(char *result, size_t len, va_list args)
{
struct bu_vls vls = BU_VLS_INIT_ZERO;
static char buf[MAXPATHLEN] = {0};
static THREADLOCAL char buf[MAXPATHLEN] = {0};
const char *cpath;
uintptr_t arg;
+18 -5
View File
@@ -214,13 +214,19 @@ bu_heap_get(size_t sz)
/* init */
if (heap->count == 0) {
if (registered++ == 0) {
/*
* Different per-CPU bins can be initialized concurrently. Serialize
* the process-global environment check and atexit registration rather
* than racing on the lazy initialization flag.
*/
bu_semaphore_acquire(BU_SEM_SYSCALL);
if (!registered) {
registered = 1;
ret = getenv("BU_HEAP_PRINT");
if ((++registered == 2) && (ret && atoi(ret) > 0)) {
if (ret && atoi(ret) > 0)
atexit(heap_print);
}
}
bu_semaphore_release(BU_SEM_SYSCALL);
heap->count++;
heap->pages = (char **)bu_malloc(1 * sizeof(char *), "heap malloc pages[]");
@@ -252,7 +258,14 @@ bu_heap_put(void *ptr, size_t sz)
return;
}
/* TODO: actually do something useful :) */
/* TODO: This bump allocator cannot safely reuse individual objects yet.
* Allocations are selected by bu_parallel_id(), but put has no owner or
* page metadata and objects may be destroyed by a different thread.
* Any implementation must define cross-thread return, page liveness and
* reclamation/compaction semantics before adding per-bin free lists.
* Bulk-lifetime clients should use an owning arena or compact container
* instead of relying on this process-lifetime storage.
*/
return;
}
+5 -6
View File
@@ -678,18 +678,17 @@ drawBBNode(const SurfaceTree* st, struct bu_list *vlfree, struct bv_vlblock *vbp
return;
}
} else {
if (!node->get_children().empty()) {
for (std::vector<BBNode*>::const_iterator childnode = node->get_children().begin(); childnode
!= node->get_children().end(); childnode++) {
drawBBNode(st, vlfree, vbp, *childnode);
}
}
for (std::size_t child_index = 0; child_index < node->childCount();
++child_index)
drawBBNode(st, vlfree, vbp, node->child(child_index));
}
}
static void
plotFaceFromSurfaceTree(const SurfaceTree* st, struct bu_list *vlfree, struct bv_vlblock *vbp, int UNUSED(isocurveres), int UNUSED(gridres))
{
if (!st || !st->Valid())
return;
const BBNode *root = st->getRootNode();
drawBBNode(st, vlfree, vbp, root);
}
File diff suppressed because it is too large Load Diff
+1
View File
@@ -75,6 +75,7 @@ rt_i_internal_create(void)
struct rt_i_internal *ip;
BU_GET(ip, struct rt_i_internal);
memset(ip, 0, sizeof(struct rt_i_internal));
ip->rti_brep_surface_tree_depth = RT_BREP_DEFAULT_SURFACE_TREE_DEPTH;
for (int i=0; i < RT_DBNHASH; i++) {
BU_LIST_INIT(&(ip->rti_solidheads[i]));
File diff suppressed because it is too large Load Diff
+192 -6
View File
@@ -25,18 +25,204 @@
#ifndef LIBRT_PRIMITIVES_BREP_BREP_LOCAL_H
#define LIBRT_PRIMITIVES_BREP_BREP_LOCAL_H
#include <vector>
struct brep_edge_span {
ON_BezierCurve curve;
ON_BoundingBox bbox;
ON_Interval edge_domain;
int edge_index = -1;
};
struct brep_trim_span {
ON_BezierCurve curve;
ON_Interval trim_domain;
};
struct brep_edge_trim_cell {
ON_Interval edge_domain;
ON_Interval trim_domain;
size_t edge_span = 0;
size_t trim_span_begin = 0;
size_t trim_span_count = 0;
int trim_index = -1;
};
struct brep_edge_record {
ON_BoundingBox bbox;
double bbox_coordinate_scale = 0.0;
size_t span_begin = 0;
size_t span_count = 0;
double tolerance = ON_UNSET_VALUE;
double model_tolerance = 0.0;
double declared_tolerance = ON_UNSET_VALUE;
double measured_discrepancy = ON_UNSET_VALUE;
double discrepancy_lower_bound = ON_UNSET_VALUE;
double discrepancy_upper_bound = ON_UNSET_VALUE;
double discrepancy_bound_tolerance = ON_UNSET_VALUE;
size_t correspondence_cells = 0;
size_t correspondence_depth = 0;
size_t discrepancy_bound_cells = 0;
size_t discrepancy_bound_depth = 0;
size_t trim_cell_begin[2] = {0, 0};
size_t trim_cell_count[2] = {0, 0};
int edge_index = -1;
int face_index[2] = {-1, -1};
bool supported = false;
bool bbox_coordinate_scale_valid = false;
bool discrepancy_measured = false;
bool correspondence_screened = false;
bool correspondence_supported = false;
bool correspondence_exhausted = false;
bool discrepancy_bounded = false;
bool discrepancy_bound_exhausted = false;
bool discrepancy_endpoints_certified = false;
bool frame_interval_supported = false;
bool discrepancy_sample_authorized = false;
int discrepancy_proof_class = 0;
bool discrepancy_authorized = false;
bool tolerance_inferred = false;
};
struct brep_surface_control_layout {
int stride[2] = {0, 0};
int order[2] = {0, 0};
bool direct = false;
bool rational = false;
};
struct brep_surface_span {
ON_BezierSurface surface;
brep_surface_control_layout control_layout;
ON_BoundingBox bbox;
double bbox_coordinate_scale = 0.0;
ON_Interval surface_domain[2];
int face_index = -1;
int span_index = -1;
unsigned int singular_side_mask = 0;
bool control_points_valid = false;
bool bbox_coordinate_scale_valid = false;
};
struct brep_face_record {
ON_BoundingBox bbox;
double bbox_coordinate_scale = 0.0;
size_t span_begin = 0;
size_t span_count = 0;
size_t span_tree_root = 0;
int face_index = -1;
int nurb_form_status = 0;
bool status2_revolution_singular_map = false;
bool supported = false;
bool bbox_coordinate_scale_valid = false;
bool span_tree_available = false;
};
/* A balanced hierarchy over one face's contiguous Bezier spans. Traversal
* retains span order so bounded root collection stays deterministic. */
struct brep_prepared_span_node {
ON_BoundingBox bbox;
double bbox_coordinate_scale = 0.0;
size_t span_count = 0;
bool cullable = false;
bool bbox_coordinate_scale_valid = false;
};
/* A balanced hierarchy over contiguous face records. Keeping the original
* order avoids perturbing the bounded prepared-root collection. */
struct brep_prepared_face_node {
ON_BoundingBox bbox;
double bbox_coordinate_scale = 0.0;
size_t face_begin = 0;
size_t span_count = 0;
size_t supported_face_count = 0;
size_t reparameterized_face_count = 0;
size_t unsupported_face_count = 0;
size_t left_child = 0;
size_t right_child = 0;
bool cullable = false;
bool leaf = false;
bool bbox_coordinate_scale_valid = false;
};
/* A compact immutable hierarchy over BREP face bounds. Leaves retain their
* source index, allowing callers to restore the original face order after
* spatial selection. */
struct brep_face_bvh_node {
ON_BoundingBox bbox;
double bbox_coordinate_scale = 0.0;
size_t left_child = 0;
size_t right_child = 0;
size_t index = 0;
bool leaf = false;
bool bbox_coordinate_scale_valid = false;
};
/* One oriented arc of the link obtained by intersecting a sufficiently small
* sphere about a manifold vertex with the incident faces. outgoing is the
* tangent of edge_index directed away from the vertex. face_index carries
* this arc from outgoing to the next arc's outgoing tangent, with solid
* directions on its left. */
struct brep_vertex_arc {
ON_3dVector outgoing;
ON_3dVector outward_normal;
double clockwise_sweep = 0.0;
int edge_index = -1;
int face_index = -1;
};
struct brep_vertex_record {
ON_3dPoint point;
std::vector<brep_vertex_arc> arcs;
int vertex_index = -1;
bool planar = false;
bool supported = false;
};
/**
* The b-rep specific data structure for caching the prepared
* acceleration data structure.
*/
struct brep_specific {
ON_Brep* brep;
BrepBoundingVolume* bvh;
int is_solid;
int plate_mode;
int plate_mode_nocos;
double plate_mode_thickness;
ON_Brep *brep = NULL;
BrepBoundingVolume *bvh = NULL;
std::vector<brep_face_bvh_node> legacy_face_nodes;
size_t legacy_face_root = (size_t)-1;
std::vector<const brlcad::CurveTree *> ctrees;
std::vector<brep_edge_record> edge_records;
std::vector<brep_edge_span> edge_spans;
std::vector<brep_edge_trim_cell> edge_trim_cells;
std::vector<brep_trim_span> edge_trim_spans;
std::vector<brep_face_record> face_records;
std::vector<brep_surface_span> surface_spans;
std::vector<brep_prepared_span_node> prepared_span_nodes;
std::vector<brep_prepared_face_node> prepared_face_nodes;
std::vector<brep_face_bvh_node> prepared_face_bvh_nodes;
size_t prepared_face_bvh_root = (size_t)-1;
std::vector<brep_vertex_record> vertex_records;
int surface_tree_depth_limit = 0;
int surface_tree_maximum_depth = 0;
size_t surface_tree_nodes = 0;
size_t surface_tree_leaves = 0;
size_t curve_tree_leaves = 0;
size_t surface_tree_build_microseconds = 0;
int is_solid = 0;
int plate_mode = 0;
int plate_mode_nocos = 0;
double plate_mode_thickness = 0.0;
};
#endif /* LIBRT_PRIMITIVES_BREP_BREP_LOCAL_H */
+18 -351
View File
@@ -26,9 +26,6 @@
*/
/** @} */
/* define to use new brep ray-tracing */
#define CONVERT_TO_BREP 1
/* define to display old nurbs wireframe plot */
//#define OLD_WIREFRAME 1
@@ -50,9 +47,7 @@
#include "rt/geom.h"
#include "raytrace.h"
#ifdef CONVERT_TO_BREP
# include "brep/defines.h"
#endif /* CONVERT_TO_BREP */
#include "brep/defines.h"
#include "../../librt_private.h"
@@ -61,39 +56,16 @@
extern "C" {
#endif
struct nurb_specific {
struct nurb_specific *next; /* next surface in the solid */
struct face_g_snurb *srf; /* Original surface description */
struct bu_list bez_hd; /* List of Bezier face_g_snurbs */
};
extern void rt_nurb_brep(ON_Brep **b, const struct rt_db_internal *ip, const struct bn_tol *tol);
struct nurb_hit {
struct nurb_hit *next;
struct nurb_hit *prev;
fastf_t hit_dist; /* Distance from the r_pt to surface */
point_t hit_point; /* intersection point */
vect_t hit_normal; /* Surface normal */
fastf_t hit_uv[2]; /* Surface parametric u, v */
int hit_surfno; /* index for the surface hit */
char *hit_private; /* Store current nurb root */
};
#define NULL_HIT (struct nurb_hit *)0
#ifdef CONVERT_TO_BREP
extern void rt_nurb_brep(ON_Brep **b, const struct rt_db_internal *ip, const struct bn_tol *tol);
extern int rt_brep_prep(struct soltab *stp, struct rt_db_internal *ip, struct rt_i *rtip);
extern void rt_brep_print(const struct soltab *stp);
extern int rt_brep_shot(struct soltab *stp, struct xray *rp, struct application *ap, struct seg *seghead);
extern void rt_brep_norm(struct hit *hitp, struct soltab *stp, struct xray *rp);
extern void rt_brep_curve(struct curvature *cvp, struct hit *hitp, struct soltab *stp);
extern void rt_brep_uv(struct application *ap, struct soltab *stp, struct hit *hitp, struct uvcoord *uvp);
extern void rt_brep_free(struct soltab *stp);
extern int rt_brep_plot(struct bu_list *vhead, struct rt_db_internal *ip, const struct bg_tess_tol *ttol, const struct bn_tol *tol, const struct bview *UNUSED(info));
#endif /* CONVERT_TO_BREP */
extern int rt_brep_prep(struct soltab *stp, struct rt_db_internal *ip, struct rt_i *rtip);
extern void rt_brep_print(const struct soltab *stp);
extern int rt_brep_shot(struct soltab *stp, struct xray *rp, struct application *ap, struct seg *seghead);
extern void rt_brep_norm(struct hit *hitp, struct soltab *stp, struct xray *rp);
extern void rt_brep_curve(struct curvature *cvp, struct hit *hitp, struct soltab *stp);
extern void rt_brep_uv(struct application *ap, struct soltab *stp, struct hit *hitp, struct uvcoord *uvp);
extern void rt_brep_free(struct soltab *stp);
extern int rt_brep_plot(struct bu_list *vhead, struct rt_db_internal *ip, const struct bg_tess_tol *ttol, const struct bn_tol *tol, const struct bview *UNUSED(info));
static int
@@ -121,8 +93,6 @@ rt_nurb_grans(struct face_g_snurb *srf)
int
rt_nurb_bbox(struct rt_db_internal *ip, point_t *min, point_t *max, const struct bn_tol *UNUSED(tol)) {
int i;
struct nurb_specific *nurbs = NULL;
struct nurb_specific *next;
struct rt_nurb_internal *sip;
sip = (struct rt_nurb_internal *) ip->idb_ptr;
@@ -131,49 +101,28 @@ rt_nurb_bbox(struct rt_db_internal *ip, point_t *min, point_t *max, const struct
for (i = 0; i < sip->nsrf; i++) {
struct face_g_snurb * s;
struct nurb_specific * n;
BU_GET(n, struct nurb_specific);
/* Store off the original face_g_snurb */
s = nmg_nurb_scopy(sip->srfs[i]);
NMG_CK_SNURB(s);
nmg_nurb_s_bound(s, s->min_pt, s->max_pt);
n->srf = s;
BU_LIST_INIT(&n->bez_hd);
struct bu_list bez_hd;
BU_LIST_INIT(&bez_hd);
/* Grind up the original surf into a list of Bezier face_g_snurbs */
(void)nmg_nurb_bezier(&n->bez_hd, sip->srfs[i]);
(void)nmg_nurb_bezier(&bez_hd, sip->srfs[i]);
/* Compute bounds of each Bezier face_g_snurb */
for (BU_LIST_FOR(s, face_g_snurb, &n->bez_hd)) {
for (BU_LIST_FOR(s, face_g_snurb, &bez_hd)) {
NMG_CK_SNURB(s);
nmg_nurb_s_bound(s, s->min_pt, s->max_pt);
VMINMAX((*min), (*max), s->min_pt);
VMINMAX((*min), (*max), s->max_pt);
}
n->next = nurbs;
nurbs = n;
}
/* zero thickness will get missed by the raytracer */
BBOX_NONDEGEN((*min), (*max), SMALL_FASTF);
for (; nurbs != (struct nurb_specific *)0; nurbs = next) {
struct face_g_snurb *s;
next = nurbs->next;
/* There is a linked list of surfaces to free for each nurb */
while (BU_LIST_WHILE (s, face_g_snurb, &nurbs->bez_hd)) {
while (BU_LIST_WHILE(s, face_g_snurb, &bez_hd)) {
NMG_CK_SNURB(s);
BU_LIST_DEQUEUE(&(s->l));
nmg_nurb_free_snurb(s);
}
nmg_nurb_free_snurb(nurbs->srf); /* original surf */
BU_PUT(nurbs, struct nurb_specific);
}
/* zero thickness will get missed by the raytracer */
BBOX_NONDEGEN((*min), (*max), SMALL_FASTF);
return 0;
}
@@ -187,20 +136,14 @@ rt_nurb_bbox(struct rt_db_internal *ip, point_t *min, point_t *max, const struct
int
rt_nurb_prep(struct soltab *stp, struct rt_db_internal *ip, struct rt_i *rtip)
{
#ifndef CONVERT_TO_BREP
struct nurb_specific *nurbs = NULL;
#endif
struct rt_nurb_internal *sip;
const struct bn_tol *tol = &rtip->rti_tol;
sip = (struct rt_nurb_internal *) ip->idb_ptr;
RT_NURB_CK_MAGIC(sip);
#ifdef CONVERT_TO_BREP
ON_Brep *brep = ON_Brep::New();
rt_nurb_brep(&brep, ip, tol);
sip->brep = brep;
struct rt_brep_internal bi;
bi.magic = RT_BREP_INTERNAL_MAGIC;
@@ -211,83 +154,13 @@ rt_nurb_prep(struct soltab *stp, struct rt_db_internal *ip, struct rt_i *rtip)
di.idb_ptr = (void *)&bi;
return rt_brep_prep(stp, &di, rtip);
#else
fastf_t los = tol->dist;
if (los < SMALL_FASTF)
los = SMALL_FASTF;
for (i = 0; i < sip->nsrf; i++) {
struct face_g_snurb * s;
struct nurb_specific * n;
BU_GET(n, struct nurb_specific);
/* Store off the original face_g_snurb */
s = nmg_nurb_scopy(sip->srfs[i]);
NMG_CK_SNURB(s);
nmg_nurb_s_bound(s, s->min_pt, s->max_pt);
n->srf = s;
BU_LIST_INIT(&n->bez_hd);
/* Grind up the original surf into a list of Bezier face_g_snurbs */
(void)nmg_nurb_bezier(&n->bez_hd, sip->srfs[i]);
/* Compute bounds of each Bezier face_g_snurb */
for (BU_LIST_FOR(s, face_g_snurb, &n->bez_hd)) {
NMG_CK_SNURB(s);
nmg_nurb_s_bound(s, s->min_pt, s->max_pt);
VMINMAX(stp->st_min, stp->st_max, s->min_pt);
VMINMAX(stp->st_min, stp->st_max, s->max_pt);
}
n->next = nurbs;
nurbs = n;
}
stp->st_specific = (void *)nurbs;
/* zero thickness will get missed by the raytracer */
BBOX_NONDEGEN(stp->st_min, stp->st_max, los);
VADD2SCALE(stp->st_center, stp->st_max, stp->st_min, 0.5);
{
fastf_t f, dx, dy, dz;
dx = (stp->st_max[0] - stp->st_min[0])/2;
f = dx;
dy = (stp->st_max[1] - stp->st_min[1])/2;
if (dy > f) f = dy;
dz = (stp->st_max[2] - stp->st_min[2])/2;
if (dz > f) f = dz;
stp->st_aradius = f;
stp->st_bradius = sqrt(dx*dx + dy*dy + dz*dz);
}
return 0;
#endif /* CONVERT_TO_BREP */
}
void
rt_nurb_print(const struct soltab *stp)
{
#ifdef CONVERT_TO_BREP
return rt_brep_print(stp);
#else
struct nurb_specific *nurb =
(struct nurb_specific *)stp->st_specific;
if (nurb == (struct nurb_specific *)0) {
bu_log("rt_nurb_print: no surfaces\n");
return;
}
for (; nurb != (struct nurb_specific *)0; nurb = nurb->next) {
/* XXX There is a linked list of Bezier surfaces to print here too */
nmg_nurb_s_print("NURB", nurb->srf);
}
#endif /* CONVERT_TO_BREP */
}
@@ -302,139 +175,7 @@ rt_nurb_print(const struct soltab *stp)
int
rt_nurb_shot(struct soltab *stp, struct xray *rp, struct application *ap, struct seg *seghead)
{
#ifdef CONVERT_TO_BREP
return rt_brep_shot(stp, rp, ap, seghead);
#else
struct nurb_specific *nurb =
(struct nurb_specific *)stp->st_specific;
struct seg *segp;
const struct bn_tol *tol = &ap->a_rt_i->rti_tol;
point_t p1, p2, p3, p4;
vect_t dir1, dir2;
plane_t plane1, plane2;
struct nurb_hit * hit;
struct nurb_hit hit_list;
vect_t invdir;
int hit_num;
int surface = 0;
VINVDIR(invdir, rp->r_dir);
/* Create two orthogonal Planes their intersection contains the
* ray so we can project the surface into a 2 dimensional problem
*/
bn_vec_ortho(dir1, rp->r_dir);
VCROSS(dir2, rp->r_dir, dir1);
VMOVE(p1, rp->r_pt);
VADD2(p2, rp->r_pt, rp->r_dir);
VADD2(p3, rp->r_pt, dir1);
VADD2(p4, rp->r_pt, dir2);
/* Note: the equation of the plane in BRL-CAD is
* Ax + By + Cz = D represented by [A B C D]
*/
bg_make_plane_3pnts(plane1, p1, p3, p2, tol);
bg_make_plane_3pnts(plane2, p1, p2, p4, tol);
/* make sure that the hit_list is zero */
hit_list.next = (struct nurb_hit *)0;
hit_list.prev = (struct nurb_hit *)0;
hit_list.hit_dist = 0;
VSET(hit_list.hit_point, 0.0, 0.0, 0.0);
VSET(hit_list.hit_normal, 0.0, 0.0, 0.0);
hit_list.hit_uv[0] = hit_list.hit_uv[1] = 0.0;
hit_list.hit_surfno = -1;
hit_list.hit_private = (char *)0;
while (nurb != (struct nurb_specific *) 0) {
struct face_g_snurb * s;
struct nmg_nurb_uv_hit *hp;
for (BU_LIST_FOR (s, face_g_snurb, &nurb->bez_hd)) {
if (!rt_in_rpp(rp, invdir, s->min_pt, s->max_pt))
continue;
#define UV_TOL 1.0e-6 /* Paper says 1.0e-4 is reasonable for 1k images, not close up */
hp = nmg_nurb_intersect(
s, plane1, plane2, UV_TOL, NULL);
while (hp != (struct nmg_nurb_uv_hit *)0) {
struct nmg_nurb_uv_hit * o;
if (RT_G_DEBUG & RT_DEBUG_SPLINE)
bu_log("hit at %d %d sub = %d u = %f v = %f\n",
ap->a_x, ap->a_y, hp->sub, hp->u, hp->v);
hit = (struct nurb_hit *)
rt_conv_uv(nurb, rp, hp);
hit->hit_surfno = surface;
o = hp;
hp = hp->next;
bu_free((char *)o,
"rt_nurb_shot:nmg_nurb_uv_hit structure");
rt_nurb_add_hit(&hit_list, hit, tol);
}
/* surfaces are incrementally numbered based on the
* face_g_snurb list for all nurbs in this nurb_specific.
*/
surface++;
}
nurb = nurb->next;
/* Insert Trimming routines here */
}
/* Convert hits to segments for rt */
hit_num = 0;
while (hit_list.next != NULL_HIT) {
struct nurb_hit * h1, * h2;
RT_GET_SEG(segp, ap->a_resource);
h1 = (struct nurb_hit *) rt_return_nurb_hit(&hit_list);
h2 = (struct nurb_hit *) rt_return_nurb_hit(&hit_list);
segp->seg_stp = stp;
segp->seg_in.hit_dist = h1->hit_dist;
VMOVE(segp->seg_in.hit_point, h1->hit_point);
segp->seg_in.hit_vpriv[0] = h1->hit_uv[0];
segp->seg_in.hit_vpriv[1] = h1->hit_uv[1];
segp->seg_in.hit_vpriv[2] = 0;
segp->seg_in.hit_private = h1->hit_private;
segp->seg_in.hit_surfno = h1->hit_surfno;
hit_num++;
if (h2 != NULL_HIT) {
segp->seg_out.hit_dist = h2->hit_dist;
VMOVE(segp->seg_out.hit_point, h2->hit_point);
segp->seg_out.hit_vpriv[0] = h2->hit_uv[0];
segp->seg_out.hit_vpriv[1] = h2->hit_uv[1];
segp->seg_out.hit_private = h2->hit_private;
bu_free((char *)h2, "rt_nurb_shot: nurb hit");
hit_num++;
} else {
segp->seg_out.hit_dist = h1->hit_dist + .01;
VJOIN1(segp->seg_out.hit_point,
rp->r_pt, segp->seg_out.hit_dist, rp->r_dir);
segp->seg_out.hit_vpriv[0] = h1->hit_uv[0];
segp->seg_out.hit_vpriv[1] = h1->hit_uv[1];
segp->seg_out.hit_vpriv[2] = 1;
segp->seg_out.hit_private = h1->hit_private;
}
bu_free((char *)h1, "rt_nurb_shot:nurb hit");
BU_LIST_INSERT(&(seghead->l), &(segp->l));
}
return hit_num; /* not hit */
#endif /* CONVERT_TO_BREP */
}
@@ -461,27 +202,7 @@ rt_nurb_vshot(struct soltab *stp[], struct xray *rp[], struct seg *segp, int n,
void
rt_nurb_norm(struct hit *hitp, struct soltab *stp, struct xray *rp)
{
#ifdef CONVERT_TO_BREP
return rt_brep_norm(hitp, stp, rp);
#else
/* struct nurb_specific *nurb =
(struct nurb_specific *)stp->st_specific; */
struct face_g_snurb *n = (struct face_g_snurb *) hitp->hit_private;
fastf_t u = hitp->hit_vpriv[0];
fastf_t v = hitp->hit_vpriv[1];
fastf_t norm[4];
nmg_nurb_s_norm(n, u, v, norm);
VMOVE(hitp->hit_normal, norm);
if (hitp->hit_vpriv[2] == 1) {
VREVERSE(hitp->hit_normal, norm);
}
return;
#endif /* CONVERT_TO_BREP */
}
@@ -491,27 +212,7 @@ rt_nurb_norm(struct hit *hitp, struct soltab *stp, struct xray *rp)
void
rt_nurb_curve(struct curvature *cvp, struct hit *hitp, struct soltab *stp)
{
#ifdef CONVERT_TO_BREP
return rt_brep_curve(cvp, hitp, stp);
#else
/* struct nurb_specific *nurb =
(struct nurb_specific *)stp->st_specific; */
struct face_g_snurb *srf = (struct face_g_snurb *) hitp->hit_private;
fastf_t u, v;
if (srf->order[0] <= 2 && srf->order[1] <= 2) {
cvp->crv_c1 = cvp->crv_c2 = 0;
/* any tangent direction */
bn_vec_ortho(cvp->crv_pdir, hitp->hit_normal);
return;
}
u = hitp->hit_vpriv[0];
v = hitp->hit_vpriv[1];
nmg_nurb_curvature((struct nmg_curvature *)cvp, srf, u, v);
#endif /* CONVERT_TO_BREP */
}
@@ -524,48 +225,14 @@ rt_nurb_curve(struct curvature *cvp, struct hit *hitp, struct soltab *stp)
void
rt_nurb_uv(struct application *ap, struct soltab *stp, struct hit *hitp, struct uvcoord *uvp)
{
#ifdef CONVERT_TO_BREP
return rt_brep_uv(ap, stp, hitp, uvp);
#else
/* struct nurb_specific *nurb =
(struct nurb_specific *)stp->st_specific; */
uvp->uv_u = hitp->hit_vpriv[0];
uvp->uv_v = hitp->hit_vpriv[1];
return;
#endif /* CONVERT_TO_BREP */
}
void
rt_nurb_free(struct soltab *stp)
{
#ifdef CONVERT_TO_BREP
return rt_brep_free(stp);
#else
struct nurb_specific *nurb =
(struct nurb_specific *)stp->st_specific;
struct nurb_specific *next;
if (nurb == (struct nurb_specific *)0)
bu_bomb("rt_nurb_free: no surfaces\n");
for (; nurb != (struct nurb_specific *)0; nurb = next) {
struct face_g_snurb *s;
next = nurb->next;
/* There is a linked list of surfaces to free for each nurb */
while (BU_LIST_WHILE (s, face_g_snurb, &nurb->bez_hd)) {
NMG_CK_SNURB(s);
BU_LIST_DEQUEUE(&(s->l));
nmg_nurb_free_snurb(s);
}
nmg_nurb_free_snurb(nurb->srf); /* original surf */
BU_PUT(nurb, struct nurb_specific);
}
return;
#endif /* CONVERT_TO_BREP */
}
@@ -695,7 +362,7 @@ rt_nurb_plot(struct bu_list *vhead, struct rt_db_internal *ip, const struct bg_t
return rt_brep_plot(vhead, &di, ttol, tol, NULL);
#else
return 0;
#endif /* CONVERT_TO_BREP */
#endif /* NEW_WIREFRAME */
}
+14 -10
View File
@@ -174,10 +174,11 @@ rt_extrude_bbox(struct rt_db_internal *ip, point_t *min, point_t *max, const str
vert_count = skt->vert_count;
/* count how many additional vertices we will need for arc centers */
for (i = 0; i < skt->curve.count; i++) {
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
uint32_t *magic = (uint32_t *)skt->curve.segment[i];
if (csg->magic != CURVE_CARC_MAGIC)
if (*magic != CURVE_CARC_MAGIC)
continue;
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
if (csg->radius <= 0.0)
continue;
@@ -249,12 +250,13 @@ rt_extrude_bbox(struct rt_db_internal *ip, point_t *min, point_t *max, const str
* beyond the listed vertices
*/
for (i = 0; i < skt->curve.count; i++) {
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
struct carc_seg *csg_extr=(struct carc_seg *)extr.crv.segment[i];
uint32_t *magic = (uint32_t *)skt->curve.segment[i];
point_t center;
if (csg->magic != CURVE_CARC_MAGIC)
if (*magic != CURVE_CARC_MAGIC)
continue;
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
struct carc_seg *csg_extr=(struct carc_seg *)extr.crv.segment[i];
if (csg->radius <= 0.0) {
/* full circle */
@@ -450,10 +452,11 @@ rt_extrude_prep(struct soltab *stp, struct rt_db_internal *ip, struct rt_i *rtip
vert_count = skt->vert_count;
/* count how many additional vertices we will need for arc centers */
for (i = 0; i < skt->curve.count; i++) {
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
uint32_t *magic = (uint32_t *)skt->curve.segment[i];
if (csg->magic != CURVE_CARC_MAGIC)
if (*magic != CURVE_CARC_MAGIC)
continue;
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
if (csg->radius <= 0.0)
continue;
@@ -525,12 +528,13 @@ rt_extrude_prep(struct soltab *stp, struct rt_db_internal *ip, struct rt_i *rtip
* beyond the listed vertices
*/
for (i = 0; i < skt->curve.count; i++) {
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
struct carc_seg *csg_extr=(struct carc_seg *)extr->crv.segment[i];
uint32_t *magic = (uint32_t *)skt->curve.segment[i];
point_t center;
if (csg->magic != CURVE_CARC_MAGIC)
if (*magic != CURVE_CARC_MAGIC)
continue;
struct carc_seg *csg=(struct carc_seg *)skt->curve.segment[i];
struct carc_seg *csg_extr=(struct carc_seg *)extr->crv.segment[i];
if (csg->radius <= 0.0) {
/* full circle */
+87
View File
@@ -188,6 +188,91 @@ brlcad_add_test(NAME rt_primitive_brep COMMAND rt_primitive_brep)
brlcad_addexec(rt_brep_io brep_io.cpp "librt;libbrep;libbu;Threads::Threads;${M_LIBRARY}" TEST)
brlcad_add_test(NAME rt_brep_io COMMAND rt_brep_io)
# Directed analytic primitive vs converted-BRep ray intersections
brlcad_addexec(rt_brep_ray brep_ray.cpp "librt;libbrep;libwdb;libbu;${M_LIBRARY}" TEST)
brlcad_add_test(NAME rt_brep_ray COMMAND rt_brep_ray --directed-only)
# Mixed collapsed-pole and regular BREP ray events
brlcad_addexec(rt_brep_mixed_pole brep_mixed_pole.cpp librt;libbrep;libbu;${M_LIBRARY} TEST)
brlcad_add_test(NAME rt_brep_mixed_pole COMMAND rt_brep_mixed_pole)
# Internal Bezier-span duplicate BREP ray events
brlcad_addexec(rt_brep_adjacent_span brep_adjacent_span.cpp "librt;libbrep;libbu;${M_LIBRARY}" TEST)
brlcad_add_test(NAME rt_brep_adjacent_span COMMAND rt_brep_adjacent_span)
# Internal Bezier-span junction BREP ray events
brlcad_addexec(rt_brep_span_junction brep_span_junction.cpp "librt;libbrep;libbu;${M_LIBRARY}" TEST)
brlcad_add_test(NAME rt_brep_span_junction COMMAND rt_brep_span_junction)
foreach(span_junction_face_index 0 1 2 3 4)
brlcad_add_test(
NAME rt_brep_span_junction_face_${span_junction_face_index}
COMMAND rt_brep_span_junction ${span_junction_face_index})
endforeach()
brlcad_add_test(
NAME rt_brep_ray_pole
COMMAND rt_brep_ray --pole-only --surface-tree-depth=2
)
brlcad_add_test(NAME rt_brep_ray_crofton COMMAND rt_brep_ray --crofton-only)
brlcad_add_test(
NAME rt_brep_ray_regular_stream
COMMAND rt_brep_ray --regular-stream-only --surface-tree-depth=0
)
brlcad_add_test(
NAME rt_brep_ray_crofton_hard
COMMAND rt_brep_ray --crofton-hard-only
)
brlcad_add_test(NAME rt_brep_ray_seam COMMAND rt_brep_ray --seam-only)
brlcad_add_test(
NAME rt_brep_ray_seam_authority
COMMAND rt_brep_ray --seam-authority-only
)
brlcad_add_test(
NAME rt_brep_ray_throughput
COMMAND rt_brep_ray --throughput-only
)
brlcad_add_test(
NAME rt_brep_ray_restriction
COMMAND rt_brep_ray --restriction-only
)
brlcad_add_test(NAME rt_brep_ray_endpoint COMMAND rt_brep_ray --endpoint-only)
brlcad_add_test(
NAME rt_brep_ray_nonisoparametric
COMMAND rt_brep_ray --nonisoparametric-only
)
brlcad_add_test(
NAME rt_brep_ray_contact_trim
COMMAND rt_brep_ray --contact-trim-only
)
brlcad_add_test(
NAME rt_brep_ray_defect
COMMAND rt_brep_ray --defect-only
)
brlcad_add_test(NAME rt_brep_ray_interval COMMAND rt_brep_ray --interval-only)
brlcad_add_test(
NAME rt_brep_ray_local_root
COMMAND rt_brep_ray --local-root-only
)
brlcad_add_test(NAME rt_brep_ray_cleanup COMMAND rt_brep_ray --cleanup-only)
brlcad_add_test(
NAME rt_brep_ray_grazing_root
COMMAND rt_brep_ray --grazing-root-only
)
brlcad_add_test(
NAME rt_brep_ray_trim_interval
COMMAND rt_brep_ray --trim-interval-only
)
brlcad_add_test(
NAME rt_brep_ray_trim_classifier
COMMAND rt_brep_ray --trim-classifier-only
)
brlcad_add_test(
NAME rt_brep_ray_source_union
COMMAND rt_brep_ray --source-union-only
)
brlcad_add_test(NAME rt_brep_ray_fold COMMAND rt_brep_ray --fold-only)
brlcad_add_test(NAME rt_brep_ray_affine COMMAND rt_brep_ray --affine-only)
# stand-alone Crofton estimator tests
brlcad_addexec(rt_crofton crofton.c "librt;libwdb;libbu;${M_LIBRARY}" TEST)
brlcad_add_test(NAME rt_crofton COMMAND rt_crofton)
@@ -216,6 +301,7 @@ set(
arb_intersect.g
binary_attribute.c
binunif.c
brep_mixed_pole.cpp
brep_io.cpp
bound_internal.c
crofton.c
@@ -224,6 +310,7 @@ set(
datum_annot_semantics.c
dsp_raytrace_check.c
dsp_sa_vol_check.c
brep_ray.cpp
primitive_brep.cpp
reprep_prune.c
brep_boolean_tests.g
+755
View File
@@ -0,0 +1,755 @@
/* B R E P _ A D J A C E N T _ S P A N . C P P
* BRL-CAD
*
* Internal Bezier-span BREP ray regression.
*/
#include "common.h"
#include <cmath>
#include <cstdio>
#include <vector>
#include "bu/app.h"
#include "bu/malloc.h"
#include "raytrace.h"
#include "rt/geom.h"
#include "brep.h"
#include "brep/cobb.h"
#include "../librt_private.h"
#include "../primitives/brep/brep_local.h"
static const int ADJACENT_SPAN_SURFACE_TREE_DEPTH = 0;
static const double ADJACENT_SPAN_RADIUS = 10.0;
static const double ADJACENT_SPAN_RADIAL_OFFSET = 8.0;
static const double ADJACENT_SPAN_ORIGIN_DISTANCE = 20.0;
static const int ADJACENT_SPAN_HIERARCHY_SPAN_COUNT = 16;
static const double ADJACENT_SPAN_ENTRY_DISTANCE =
ADJACENT_SPAN_ORIGIN_DISTANCE - std::sqrt(
ADJACENT_SPAN_RADIUS * ADJACENT_SPAN_RADIUS -
ADJACENT_SPAN_RADIAL_OFFSET * ADJACENT_SPAN_RADIAL_OFFSET);
static const double ADJACENT_SPAN_EXIT_DISTANCE =
ADJACENT_SPAN_ORIGIN_DISTANCE + std::sqrt(
ADJACENT_SPAN_RADIUS * ADJACENT_SPAN_RADIUS -
ADJACENT_SPAN_RADIAL_OFFSET * ADJACENT_SPAN_RADIAL_OFFSET);
static struct soltab *
prepare_solid(struct rt_i *rtip, struct rt_db_internal *intern, int type)
{
struct soltab *stp = (struct soltab *)bu_calloc(1, sizeof(struct soltab),
"adjacent span ray soltab");
stp->l.magic = RT_SOLTAB_MAGIC;
stp->l2.magic = RT_SOLTAB2_MAGIC;
stp->st_rtip = rtip;
struct directory *directory = (struct directory *)bu_calloc(1,
sizeof(struct directory), "adjacent span ray directory");
directory->d_magic = RT_DIR_MAGIC;
directory->d_namep = (char *)"adjacent_span_ray.s";
stp->st_dp = directory;
stp->st_id = type;
stp->st_meth = &OBJ[type];
if (OBJ[type].ft_prep(stp, intern, rtip)) {
if (stp->st_specific && stp->st_meth && stp->st_meth->ft_free)
stp->st_meth->ft_free(stp);
bu_free((void *)stp->st_dp, "adjacent span ray directory");
bu_free(stp, "adjacent span ray soltab");
return NULL;
}
return stp;
}
static void
free_solid(struct soltab *stp)
{
if (!stp)
return;
if (stp->st_meth && stp->st_meth->ft_free)
stp->st_meth->ft_free(stp);
bu_free((void *)stp->st_dp, "adjacent span ray directory");
bu_free(stp, "adjacent span ray soltab");
}
static void
free_rtip(struct rt_i *rtip, struct resource *resource)
{
if (!rtip || !resource)
return;
rt_clean_resource_basic(rtip, resource);
BU_PTBL_SET(&rtip->rti_resources, 0, NULL);
rt_i_destroy(rtip);
}
static ON_Brep *
make_adjacent_span_sphere()
{
const ON_3dPoint center(0.0, 0.0, 0.0);
ON_Brep *brep = ON_Brep_CobbSphereSewn(ADJACENT_SPAN_RADIUS, center);
if (!brep)
return NULL;
for (int face_index = 0; face_index < brep->m_F.Count(); ++face_index) {
const int surface_index = brep->m_F[face_index].m_si;
ON_NurbsSurface *surface = surface_index >= 0 &&
surface_index < brep->m_S.Count() ? ON_NurbsSurface::Cast(
brep->m_S[surface_index]) : NULL;
if (!surface) {
delete brep;
return NULL;
}
const ON_Interval domain = surface->Domain(1);
const double span_width = 0.25 * (domain.Max() - domain.Min());
if (!domain.IsIncreasing() || !(span_width > 0.0) ||
!surface->InsertKnot(1, domain.Min() + span_width) ||
!surface->InsertKnot(1, domain.Mid())) {
delete brep;
return NULL;
}
}
return brep;
}
static ON_Brep *
make_hierarchy_span_sphere()
{
const ON_3dPoint center(0.0, 0.0, 0.0);
ON_Brep *brep = ON_Brep_CobbSphereSewn(ADJACENT_SPAN_RADIUS, center);
if (!brep)
return NULL;
for (int face_index = 0; face_index < brep->m_F.Count(); ++face_index) {
const int surface_index = brep->m_F[face_index].m_si;
ON_NurbsSurface *surface = surface_index >= 0 &&
surface_index < brep->m_S.Count() ? ON_NurbsSurface::Cast(
brep->m_S[surface_index]) : NULL;
if (!surface) {
delete brep;
return NULL;
}
const ON_Interval domain = surface->Domain(1);
const double span_width = (domain.Max() - domain.Min()) /
ADJACENT_SPAN_HIERARCHY_SPAN_COUNT;
if (!domain.IsIncreasing() || !(span_width > 0.0)) {
delete brep;
return NULL;
}
for (int span = 1; span < ADJACENT_SPAN_HIERARCHY_SPAN_COUNT;
++span) {
if (!surface->InsertKnot(1, domain.Min() + span * span_width)) {
delete brep;
return NULL;
}
}
}
return brep;
}
static struct soltab *
prepare_brep_solid(struct rt_i *rtip, ON_Brep *brep)
{
if (!rtip || !brep)
return NULL;
struct rt_brep_internal brep_internal = {};
brep_internal.magic = RT_BREP_INTERNAL_MAGIC;
brep_internal.brep = brep;
struct rt_db_internal database_internal;
RT_DB_INTERNAL_INIT(&database_internal);
database_internal.idb_major_type = DB5_MAJORTYPE_BRLCAD;
database_internal.idb_type = ID_BREP;
database_internal.idb_meth = &OBJ[ID_BREP];
database_internal.idb_ptr = &brep_internal;
return prepare_solid(rtip, &database_internal, ID_BREP);
}
static bool
bbox_contains(const ON_BoundingBox &outer, const ON_BoundingBox &inner)
{
if (!outer.IsValid() || !inner.IsValid())
return false;
for (int axis = 0; axis < 3; ++axis) {
if (outer.m_min[axis] > inner.m_min[axis] ||
outer.m_max[axis] < inner.m_max[axis])
return false;
}
return true;
}
static bool
prepared_span_tree_node_valid(const brep_specific &specific,
size_t node_index, size_t span_begin, size_t span_count)
{
if (!span_count || node_index >= specific.prepared_span_nodes.size() ||
span_begin > specific.surface_spans.size() ||
span_count > specific.surface_spans.size() - span_begin)
return false;
const brep_prepared_span_node &node =
specific.prepared_span_nodes[node_index];
if (node.span_count != span_count || !node.cullable || !node.bbox.IsValid())
return false;
if (span_count == 1)
return bbox_contains(node.bbox, specific.surface_spans[span_begin].bbox);
const size_t left_count = span_count / 2;
const size_t left_child = node_index + 1;
const size_t right_child = left_child + 2 * left_count - 1;
if (right_child >= specific.prepared_span_nodes.size() ||
!prepared_span_tree_node_valid(specific, left_child, span_begin,
left_count) ||
!prepared_span_tree_node_valid(specific, right_child,
span_begin + left_count, span_count - left_count))
return false;
return bbox_contains(node.bbox,
specific.prepared_span_nodes[left_child].bbox) &&
bbox_contains(node.bbox,
specific.prepared_span_nodes[right_child].bbox);
}
static bool
prepared_span_tree_available(const struct soltab *solid)
{
const brep_specific *specific = solid ?
static_cast<const brep_specific *>(solid->st_specific) : NULL;
if (!specific)
return false;
size_t multi_span_faces = 0;
bool multi_level_tree = false;
for (std::vector<brep_face_record>::const_iterator record_it =
specific->face_records.begin();
record_it != specific->face_records.end(); ++record_it) {
const brep_face_record &record = *record_it;
if (!record.supported || record.span_count < 2)
continue;
multi_span_faces++;
if (record.span_count >= 3)
multi_level_tree = true;
if (!record.span_tree_available ||
record.span_tree_root >= specific->prepared_span_nodes.size())
return false;
if (!prepared_span_tree_node_valid(*specific, record.span_tree_root,
record.span_begin, record.span_count))
return false;
}
return multi_span_faces > 0 && multi_level_tree;
}
static bool
disable_prepared_span_tree(struct soltab *solid)
{
brep_specific *specific = solid ?
static_cast<brep_specific *>(solid->st_specific) : NULL;
if (!specific)
return false;
for (std::vector<brep_face_record>::iterator record_it =
specific->face_records.begin();
record_it != specific->face_records.end(); ++record_it) {
record_it->span_tree_root = 0;
record_it->span_tree_available = false;
}
specific->prepared_span_nodes.clear();
return true;
}
static bool
disable_prepared_face_bvh(struct soltab *solid)
{
brep_specific *specific = solid ?
static_cast<brep_specific *>(solid->st_specific) : NULL;
if (!specific)
return false;
specific->prepared_face_bvh_nodes.clear();
specific->prepared_face_bvh_root = (size_t)-1;
return true;
}
static bool
disable_prepared_face_tree(struct soltab *solid)
{
brep_specific *specific = solid ?
static_cast<brep_specific *>(solid->st_specific) : NULL;
if (!specific || !disable_prepared_face_bvh(solid))
return false;
specific->prepared_face_nodes.clear();
return true;
}
static bool
prepared_face_tree_node_valid(const brep_specific &specific,
size_t node_index, size_t face_begin, size_t face_count)
{
if (!face_count || node_index >= specific.prepared_face_nodes.size() ||
face_begin > specific.face_records.size() ||
face_count > specific.face_records.size() - face_begin)
return false;
const brep_prepared_face_node &node =
specific.prepared_face_nodes[node_index];
if (node.face_begin != face_begin)
return false;
if (face_count == 1) {
const brep_face_record &record = specific.face_records[face_begin];
const size_t supported = record.supported ? 1 : 0;
const size_t reparameterized = record.supported &&
record.nurb_form_status == 2 ? 1 : 0;
const size_t unsupported = record.supported ? 0 : 1;
const bool cullable = record.supported && record.span_count &&
record.bbox.IsValid();
return node.leaf && node.span_count ==
(record.supported ? record.span_count : 0) &&
node.supported_face_count == supported &&
node.reparameterized_face_count == reparameterized &&
node.unsupported_face_count == unsupported &&
node.cullable == cullable && (!record.supported ||
!record.bbox.IsValid() ||
bbox_contains(node.bbox, record.bbox));
}
const size_t left_count = face_count / 2;
const size_t right_count = face_count - left_count;
if (node.leaf || node.left_child >= specific.prepared_face_nodes.size() ||
node.right_child >= specific.prepared_face_nodes.size() ||
!prepared_face_tree_node_valid(specific, node.left_child, face_begin,
left_count) || !prepared_face_tree_node_valid(specific,
node.right_child, face_begin + left_count, right_count))
return false;
const brep_prepared_face_node &left =
specific.prepared_face_nodes[node.left_child];
const brep_prepared_face_node &right =
specific.prepared_face_nodes[node.right_child];
const bool left_complete = !left.supported_face_count || left.cullable;
const bool right_complete = !right.supported_face_count || right.cullable;
const bool cullable = node.span_count && left_complete && right_complete &&
node.bbox.IsValid();
return node.span_count == left.span_count + right.span_count &&
node.supported_face_count == left.supported_face_count +
right.supported_face_count && node.reparameterized_face_count ==
left.reparameterized_face_count + right.reparameterized_face_count &&
node.unsupported_face_count == left.unsupported_face_count +
right.unsupported_face_count && node.cullable == cullable &&
(!left.bbox.IsValid() || bbox_contains(node.bbox, left.bbox)) &&
(!right.bbox.IsValid() || bbox_contains(node.bbox, right.bbox));
}
static bool
prepared_face_tree_available(const struct soltab *solid)
{
const brep_specific *specific = solid ?
static_cast<const brep_specific *>(solid->st_specific) : NULL;
return specific && !specific->face_records.empty() &&
!specific->prepared_face_nodes.empty() &&
prepared_face_tree_node_valid(*specific, 0, 0,
specific->face_records.size());
}
static bool
prepared_face_bvh_node_valid(const brep_specific &specific,
size_t node_index, std::vector<bool> &seen_faces)
{
if (node_index >= specific.prepared_face_bvh_nodes.size())
return false;
const brep_face_bvh_node &node =
specific.prepared_face_bvh_nodes[node_index];
if (!node.bbox.IsValid())
return false;
if (node.leaf) {
if (node.index >= specific.face_records.size() ||
seen_faces[node.index])
return false;
const brep_face_record &record = specific.face_records[node.index];
if (!record.supported || !record.span_count || !record.bbox.IsValid() ||
!bbox_contains(node.bbox, record.bbox))
return false;
seen_faces[node.index] = true;
return true;
}
if (node.left_child <= node_index || node.right_child <= node_index ||
node.left_child >= specific.prepared_face_bvh_nodes.size() ||
node.right_child >= specific.prepared_face_bvh_nodes.size())
return false;
const brep_face_bvh_node &left =
specific.prepared_face_bvh_nodes[node.left_child];
const brep_face_bvh_node &right =
specific.prepared_face_bvh_nodes[node.right_child];
return bbox_contains(node.bbox, left.bbox) &&
bbox_contains(node.bbox, right.bbox) &&
prepared_face_bvh_node_valid(specific, node.left_child, seen_faces) &&
prepared_face_bvh_node_valid(specific, node.right_child, seen_faces);
}
static bool
prepared_face_bvh_available(const struct soltab *solid)
{
const brep_specific *specific = solid ?
static_cast<const brep_specific *>(solid->st_specific) : NULL;
if (!specific || specific->prepared_face_bvh_nodes.empty() ||
specific->prepared_face_bvh_root >=
specific->prepared_face_bvh_nodes.size())
return false;
std::vector<bool> seen_faces(specific->face_records.size(), false);
if (!prepared_face_bvh_node_valid(*specific,
specific->prepared_face_bvh_root, seen_faces))
return false;
for (size_t record_index = 0;
record_index < specific->face_records.size(); ++record_index) {
if (specific->face_records[record_index].supported !=
seen_faces[record_index])
return false;
}
return true;
}
static int
shoot_trace(struct soltab *stp, struct rt_i *rtip, struct resource *resource,
const point_t origin, const vect_t direction,
struct rt_brep_shot_trace &trace)
{
struct application application;
struct seg seghead;
struct xray ray;
RT_APPLICATION_INIT(&application);
application.a_rt_i = rtip;
application.a_resource = resource;
VMOVE(ray.r_pt, origin);
VMOVE(ray.r_dir, direction);
ray.magic = RT_RAY_MAGIC;
BU_LIST_INIT(&seghead.l);
const int hits = _rt_brep_shot_trace(stp, &ray, &application, &seghead,
&trace);
struct seg *segment;
while (BU_LIST_WHILE(segment, seg, &seghead.l)) {
BU_LIST_DEQUEUE(&segment->l);
RT_FREE_SEG(segment, resource);
}
return hits;
}
static void
report_trace(const struct rt_brep_shot_trace &trace)
{
std::printf(" selected/fallback/hits=%zu/%d/%zu boxes/roots/events="
"%zu/%zu/%zu stream=%zu/%zu/%zu/%zu/%zu stage=%d\n",
trace.prepared_production_selected, trace.prepared_production_fallback,
trace.prepared_production_hits, trace.stored_surface_boxes,
trace.stored_local_roots, trace.stored_physical_events,
trace.physical_event_regular_stream_attempts,
trace.physical_event_regular_stream_certified,
trace.physical_event_regular_stream_components,
trace.physical_event_regular_stream_boxes,
trace.physical_event_regular_stream_roots,
trace.physical_event_regular_stream_failure_stage);
for (size_t root_index = 0; root_index < trace.stored_local_roots;
++root_index) {
const struct rt_brep_trace_local_root &root =
trace.local_roots[root_index];
std::printf(" root[%zu] face/span=%d/%d uv=(%.17g,%.17g) t=%.17g "
"normal/direction=%.17g/%d\n", root_index, root.face_index,
root.span_index, root.uv[0], root.uv[1], root.dist,
root.normal_dot, root.direction);
}
for (size_t box_index = 0; box_index < trace.stored_surface_boxes;
++box_index) {
const struct rt_brep_trace_surface_box &box =
trace.surface_boxes[box_index];
std::printf(" box[%zu] face/span=%d/%d uv=[%.17g,%.17g]x"
"[%.17g,%.17g] t=[%.17g,%.17g] state/sign=%d/%d\n",
box_index, box.face_index, box.span_index, box.uv_min[0],
box.uv_max[0], box.uv_min[1], box.uv_max[1], box.t_min,
box.t_max, box.disposition, box.determinant_sign);
}
for (size_t event_index = 0;
event_index < trace.stored_physical_events; ++event_index) {
const struct rt_brep_trace_physical_event &event =
trace.physical_events[event_index];
std::printf(" event[%zu] face/span=%d/%d t=[%.17g,%.17g] "
"distance=%.17g root/boxes=%zu/%zu certificate/direction=%d/%d\n",
event_index, event.face_index, event.span_index, event.t_min,
event.t_max, event.dist, event.source_root, event.source_box_count,
event.certificate, event.direction);
}
}
static bool
trace_has_expected_events(const struct rt_brep_shot_trace &trace, int hits,
double distance_tolerance)
{
if (hits != 2 || trace.prepared_production_selected != 1 ||
trace.prepared_production_fallback != RT_BREP_PREPARED_FALLBACK_NONE ||
trace.prepared_production_hits != 2 || trace.physical_event_complete != 1 ||
trace.physical_event_regular_stream_attempts != 1 ||
trace.physical_event_regular_stream_certified != 1 ||
trace.physical_event_regular_stream_components != 2 ||
trace.physical_event_regular_stream_boxes !=
trace.stored_surface_boxes ||
trace.physical_event_regular_stream_roots !=
trace.stored_local_roots ||
trace.physical_event_regular_stream_failure_stage ||
trace.stored_local_roots != 4 || trace.stored_physical_events != 2 ||
trace.physical_event_regular != 2 || trace.physical_event_unresolved ||
trace.physical_event_direction_mismatches ||
trace.physical_event_state_failures ||
trace.physical_event_material_segments != 1 ||
trace.physical_event_subminimum_contacts ||
trace.physical_event_tolerance_ambiguous)
return false;
const double expected_distance[2] = {
ADJACENT_SPAN_ENTRY_DISTANCE, ADJACENT_SPAN_EXIT_DISTANCE
};
const int expected_direction[2] = {
RT_BREP_TRACE_ENTERING, RT_BREP_TRACE_LEAVING
};
size_t source_boxes = 0;
for (size_t event_index = 0; event_index < 2; ++event_index) {
const struct rt_brep_trace_physical_event &event =
trace.physical_events[event_index];
if (event.certificate != RT_BREP_TRACE_EVENT_REGULAR_INTERIOR ||
event.source_kind != RT_BREP_TRACE_EVENT_SOURCE_LOCAL_ROOT ||
event.source_root >= trace.stored_local_roots ||
event.source_box >= trace.stored_surface_boxes ||
event.source_box_count < 2 || !event.determinant_sign ||
event.direction != expected_direction[event_index] ||
fabs(event.dist - expected_distance[event_index]) >
distance_tolerance || event.dist < event.t_min ||
event.dist > event.t_max)
return false;
source_boxes += event.source_box_count;
}
if (source_boxes != trace.stored_surface_boxes)
return false;
for (size_t box_index = 0; box_index < trace.stored_surface_boxes;
++box_index) {
const struct rt_brep_trace_surface_box &box =
trace.surface_boxes[box_index];
if (box.disposition != RT_BREP_TRACE_BOX_RESOLVED_REGULAR_STREAM ||
!box.determinant_sign)
return false;
}
return true;
}
static bool
trace_has_hierarchy_pruning(const struct soltab *solid,
const struct rt_brep_shot_trace &trace, int hits,
double distance_tolerance)
{
const brep_specific *specific = solid ?
static_cast<const brep_specific *>(solid->st_specific) : NULL;
if (!specific || !trace_has_expected_events(trace, hits,
distance_tolerance))
return false;
size_t largest_face_span_count = 0;
for (std::vector<brep_face_record>::const_iterator record_it =
specific->face_records.begin();
record_it != specific->face_records.end(); ++record_it) {
const brep_face_record &record = *record_it;
if (record.supported && record.span_count > largest_face_span_count)
largest_face_span_count = record.span_count;
}
const size_t prepared_span_count = specific->surface_spans.size();
return largest_face_span_count >= ADJACENT_SPAN_HIERARCHY_SPAN_COUNT &&
trace.prepared_surface_spans == prepared_span_count &&
trace.candidate_surface_spans + trace.excluded_surface_spans ==
prepared_span_count && trace.excluded_surface_spans >
3 * prepared_span_count / 4 && trace.candidate_surface_spans <
prepared_span_count / 4 && trace.surface_coefficient_expansion_avoided >=
trace.excluded_surface_spans;
}
int
main(int argc, char **argv)
{
bu_setprogname(argv[0]);
struct rt_i *rtip = rt_dirbuild_inmem(NULL, 0, NULL, 0);
if (!rtip) {
std::printf("FAIL: in-memory raytrace setup\n");
return 1;
}
rtip->rti_tol.magic = BN_TOL_MAGIC;
rtip->rti_tol.dist = 0.0005;
rtip->rti_tol.dist_sq = rtip->rti_tol.dist * rtip->rti_tol.dist;
rtip->rti_tol.perp = 1.0e-6;
rtip->rti_tol.para = 1.0 - rtip->rti_tol.perp;
if (!_rt_brep_set_surface_tree_depth(rtip,
ADJACENT_SPAN_SURFACE_TREE_DEPTH)) {
std::printf("FAIL: BREP surface tree setup\n");
rt_i_destroy(rtip);
return 1;
}
struct resource resource = {};
rt_init_resource(&resource, 0, rtip);
ON_Brep *brep = make_adjacent_span_sphere();
if (!brep || !brep->IsSolid()) {
std::printf("FAIL: adjacent-span sphere construction\n");
delete brep;
free_rtip(rtip, &resource);
return 1;
}
struct soltab *solid = prepare_brep_solid(rtip, brep);
if (!solid) {
std::printf("FAIL: adjacent-span sphere preparation\n");
delete brep;
free_rtip(rtip, &resource);
return 1;
}
const bool span_tree_valid = prepared_span_tree_available(solid) &&
prepared_face_tree_available(solid);
if (!span_tree_valid)
std::printf("FAIL: adjacent-span hierarchy preparation\n");
const point_t forward_origin = {
ADJACENT_SPAN_RADIAL_OFFSET, 0.0,
-ADJACENT_SPAN_ORIGIN_DISTANCE
};
const vect_t forward_direction = {0.0, 0.0, 1.0};
const point_t reverse_origin = {
ADJACENT_SPAN_RADIAL_OFFSET, 0.0,
ADJACENT_SPAN_ORIGIN_DISTANCE
};
const vect_t reverse_direction = {0.0, 0.0, -1.0};
const double distance_tolerance = std::max(1.0e-9,
(double)rtip->rti_tol.dist);
struct rt_brep_shot_trace forward_trace = {};
const int forward_hits = shoot_trace(solid, rtip, &resource,
forward_origin, forward_direction, forward_trace);
const bool forward_valid = trace_has_expected_events(forward_trace,
forward_hits, distance_tolerance);
if (!forward_valid) {
std::printf("FAIL: forward adjacent-span stream\n");
report_trace(forward_trace);
}
struct rt_brep_shot_trace reverse_trace = {};
const int reverse_hits = shoot_trace(solid, rtip, &resource,
reverse_origin, reverse_direction, reverse_trace);
const bool reverse_valid = trace_has_expected_events(reverse_trace,
reverse_hits, distance_tolerance);
if (!reverse_valid) {
std::printf("FAIL: reverse adjacent-span stream\n");
report_trace(reverse_trace);
}
bool hierarchy_valid = false;
ON_Brep *hierarchy_brep = make_hierarchy_span_sphere();
if (hierarchy_brep && hierarchy_brep->IsSolid()) {
struct soltab *hierarchy_solid = prepare_brep_solid(rtip, hierarchy_brep);
if (hierarchy_solid) {
const bool hierarchy_tree_valid =
prepared_span_tree_available(hierarchy_solid) &&
prepared_face_tree_available(hierarchy_solid) &&
prepared_face_bvh_available(hierarchy_solid);
struct rt_brep_shot_trace hierarchy_forward_trace = {};
const int hierarchy_forward_hits = shoot_trace(hierarchy_solid, rtip,
&resource, forward_origin, forward_direction,
hierarchy_forward_trace);
struct rt_brep_shot_trace hierarchy_reverse_trace = {};
const int hierarchy_reverse_hits = shoot_trace(hierarchy_solid, rtip,
&resource, reverse_origin, reverse_direction,
hierarchy_reverse_trace);
const bool hierarchy_pruned = hierarchy_tree_valid &&
trace_has_hierarchy_pruning(hierarchy_solid,
hierarchy_forward_trace, hierarchy_forward_hits,
distance_tolerance) && trace_has_hierarchy_pruning(hierarchy_solid,
hierarchy_reverse_trace, hierarchy_reverse_hits,
distance_tolerance);
bool spatial_fallback_valid = false;
if (disable_prepared_face_bvh(hierarchy_solid)) {
const bool spatial_fallback_tree_valid =
prepared_span_tree_available(hierarchy_solid) &&
prepared_face_tree_available(hierarchy_solid) &&
!prepared_face_bvh_available(hierarchy_solid);
struct rt_brep_shot_trace fallback_forward_trace = {};
const int fallback_forward_hits = shoot_trace(hierarchy_solid,
rtip, &resource, forward_origin, forward_direction,
fallback_forward_trace);
struct rt_brep_shot_trace fallback_reverse_trace = {};
const int fallback_reverse_hits = shoot_trace(hierarchy_solid,
rtip, &resource, reverse_origin, reverse_direction,
fallback_reverse_trace);
spatial_fallback_valid = spatial_fallback_tree_valid &&
trace_has_hierarchy_pruning(hierarchy_solid, fallback_forward_trace,
fallback_forward_hits, distance_tolerance) &&
trace_has_hierarchy_pruning(hierarchy_solid, fallback_reverse_trace,
fallback_reverse_hits, distance_tolerance);
}
bool face_fallback_valid = false;
if (disable_prepared_face_tree(hierarchy_solid)) {
const bool face_fallback_tree_valid =
prepared_span_tree_available(hierarchy_solid) &&
!prepared_face_tree_available(hierarchy_solid) &&
!prepared_face_bvh_available(hierarchy_solid);
struct rt_brep_shot_trace fallback_forward_trace = {};
const int fallback_forward_hits = shoot_trace(hierarchy_solid,
rtip, &resource, forward_origin, forward_direction,
fallback_forward_trace);
struct rt_brep_shot_trace fallback_reverse_trace = {};
const int fallback_reverse_hits = shoot_trace(hierarchy_solid,
rtip, &resource, reverse_origin, reverse_direction,
fallback_reverse_trace);
face_fallback_valid = face_fallback_tree_valid &&
trace_has_hierarchy_pruning(hierarchy_solid, fallback_forward_trace,
fallback_forward_hits, distance_tolerance) &&
trace_has_hierarchy_pruning(hierarchy_solid, fallback_reverse_trace,
fallback_reverse_hits, distance_tolerance);
}
bool span_fallback_valid = false;
if (disable_prepared_span_tree(hierarchy_solid)) {
struct rt_brep_shot_trace fallback_forward_trace = {};
const int fallback_forward_hits = shoot_trace(hierarchy_solid,
rtip, &resource, forward_origin, forward_direction,
fallback_forward_trace);
struct rt_brep_shot_trace fallback_reverse_trace = {};
const int fallback_reverse_hits = shoot_trace(hierarchy_solid,
rtip, &resource, reverse_origin, reverse_direction,
fallback_reverse_trace);
span_fallback_valid = trace_has_expected_events(fallback_forward_trace,
fallback_forward_hits, distance_tolerance) &&
trace_has_expected_events(fallback_reverse_trace,
fallback_reverse_hits, distance_tolerance);
}
hierarchy_valid = hierarchy_pruned && spatial_fallback_valid &&
face_fallback_valid && span_fallback_valid;
free_solid(hierarchy_solid);
} else {
delete hierarchy_brep;
}
} else {
delete hierarchy_brep;
}
free_solid(solid);
free_rtip(rtip, &resource);
return span_tree_valid && forward_valid && reverse_valid &&
hierarchy_valid ? 0 : 1;
}
/*
* Local Variables:
* tab-width: 8
* mode: C++
* c-basic-offset: 4
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+616
View File
@@ -0,0 +1,616 @@
/* B R E P _ S P A N _ J U N C T I O N . C P P
* BRL-CAD
*
* Internal Bezier-span junction BREP ray regression.
*/
#include "common.h"
#include <cerrno>
#include <climits>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include "bu/app.h"
#include "bu/malloc.h"
#include "raytrace.h"
#include "rt/geom.h"
#include "brep.h"
#include "brep/cobb.h"
#include "../librt_private.h"
static const int SPAN_JUNCTION_SURFACE_TREE_DEPTH = 0;
static const int SPAN_JUNCTION_DEFAULT_FACE_INDEX = 5;
static const double SPAN_JUNCTION_RADIUS = 10.0;
static const double SPAN_JUNCTION_RAY_CLEARANCE = 20.0;
static const double SPAN_JUNCTION_OBLIQUE_TANGENT_FRACTION = 0.25;
static const double SPAN_JUNCTION_BOUNDARY_TRANSVERSE_FRACTION = 0.25;
static const size_t SPAN_JUNCTION_STREAM_EVENTS = 2;
static const size_t SPAN_JUNCTION_JUNCTION_BOXES = 4;
static const size_t SPAN_JUNCTION_ADJACENT_BOXES = 2;
static const size_t SPAN_JUNCTION_SINGLETON_BOXES = 1;
static const double SPAN_JUNCTION_ORIGIN_RADIUS =
SPAN_JUNCTION_RADIUS + SPAN_JUNCTION_RAY_CLEARANCE;
static const double SPAN_JUNCTION_ENTRY_DISTANCE =
SPAN_JUNCTION_RAY_CLEARANCE;
static const double SPAN_JUNCTION_EXIT_DISTANCE =
SPAN_JUNCTION_ORIGIN_RADIUS + SPAN_JUNCTION_RADIUS;
static struct soltab *
prepare_solid(struct rt_i *rtip, struct rt_db_internal *intern, int type)
{
struct soltab *stp = (struct soltab *)bu_calloc(1, sizeof(struct soltab),
"span junction ray soltab");
stp->l.magic = RT_SOLTAB_MAGIC;
stp->l2.magic = RT_SOLTAB2_MAGIC;
stp->st_rtip = rtip;
struct directory *directory = (struct directory *)bu_calloc(1,
sizeof(struct directory), "span junction ray directory");
directory->d_magic = RT_DIR_MAGIC;
directory->d_namep = (char *)"span_junction_ray.s";
stp->st_dp = directory;
stp->st_id = type;
stp->st_meth = &OBJ[type];
if (OBJ[type].ft_prep(stp, intern, rtip)) {
if (stp->st_specific && stp->st_meth && stp->st_meth->ft_free)
stp->st_meth->ft_free(stp);
bu_free((void *)stp->st_dp, "span junction ray directory");
bu_free(stp, "span junction ray soltab");
return NULL;
}
return stp;
}
static void
free_solid(struct soltab *stp)
{
if (!stp)
return;
if (stp->st_meth && stp->st_meth->ft_free)
stp->st_meth->ft_free(stp);
bu_free((void *)stp->st_dp, "span junction ray directory");
bu_free(stp, "span junction ray soltab");
}
static void
free_rtip(struct rt_i *rtip, struct resource *resource)
{
if (!rtip || !resource)
return;
rt_clean_resource_basic(rtip, resource);
BU_PTBL_SET(&rtip->rti_resources, 0, NULL);
rt_i_destroy(rtip);
}
static ON_Brep *
make_span_junction_sphere()
{
const ON_3dPoint center(0.0, 0.0, 0.0);
ON_Brep *brep = ON_Brep_CobbSphereSewn(SPAN_JUNCTION_RADIUS, center);
if (!brep)
return NULL;
for (int face_index = 0; face_index < brep->m_F.Count(); ++face_index) {
const int surface_index = brep->m_F[face_index].m_si;
ON_NurbsSurface *surface = surface_index >= 0 &&
surface_index < brep->m_S.Count() ? ON_NurbsSurface::Cast(
brep->m_S[surface_index]) : NULL;
if (!surface) {
delete brep;
return NULL;
}
for (int direction = 0; direction < 2; ++direction) {
const ON_Interval domain = surface->Domain(direction);
if (!domain.IsIncreasing() ||
!surface->InsertKnot(direction, domain.Mid())) {
delete brep;
return NULL;
}
}
}
return brep;
}
static ON_NurbsSurface *
span_junction_face_surface(ON_Brep *brep, int face_index)
{
if (!brep || face_index < 0 || face_index >= brep->m_F.Count())
return NULL;
const int surface_index = brep->m_F[face_index].m_si;
return surface_index >= 0 && surface_index < brep->m_S.Count() ?
ON_NurbsSurface::Cast(brep->m_S[surface_index]) : NULL;
}
static bool
span_junction_radial(ON_Brep *brep, int face_index, ON_3dVector &radial)
{
const ON_3dPoint center(0.0, 0.0, 0.0);
ON_NurbsSurface *surface = span_junction_face_surface(brep, face_index);
if (!surface)
return false;
const ON_3dPoint junction = surface->PointAt(surface->Domain(0).Mid(),
surface->Domain(1).Mid());
radial = junction - center;
return junction.IsValid() && radial.Unitize();
}
static bool
span_junction_boundary_radial(ON_Brep *brep, int face_index,
double transverse_fraction, ON_3dVector &radial)
{
const ON_3dPoint center(0.0, 0.0, 0.0);
if (!(transverse_fraction > 0.0) || !(transverse_fraction < 1.0))
return false;
ON_NurbsSurface *surface = span_junction_face_surface(brep, face_index);
if (!surface)
return false;
const ON_Interval u_domain = surface->Domain(0);
const ON_Interval v_domain = surface->Domain(1);
if (!u_domain.IsIncreasing() || !v_domain.IsIncreasing())
return false;
const ON_3dPoint boundary = surface->PointAt(u_domain.Mid(),
v_domain.ParameterAt(transverse_fraction));
radial = boundary - center;
return boundary.IsValid() && radial.Unitize();
}
static struct soltab *
prepare_brep_solid(struct rt_i *rtip, ON_Brep *brep)
{
if (!rtip || !brep)
return NULL;
struct rt_brep_internal brep_internal = {};
brep_internal.magic = RT_BREP_INTERNAL_MAGIC;
brep_internal.brep = brep;
struct rt_db_internal database_internal;
RT_DB_INTERNAL_INIT(&database_internal);
database_internal.idb_major_type = DB5_MAJORTYPE_BRLCAD;
database_internal.idb_type = ID_BREP;
database_internal.idb_meth = &OBJ[ID_BREP];
database_internal.idb_ptr = &brep_internal;
return prepare_solid(rtip, &database_internal, ID_BREP);
}
static int
shoot_trace(struct soltab *stp, struct rt_i *rtip, struct resource *resource,
const point_t origin, const vect_t direction,
struct rt_brep_shot_trace &trace)
{
struct application application;
struct seg seghead;
struct xray ray;
RT_APPLICATION_INIT(&application);
application.a_rt_i = rtip;
application.a_resource = resource;
VMOVE(ray.r_pt, origin);
VMOVE(ray.r_dir, direction);
ray.magic = RT_RAY_MAGIC;
BU_LIST_INIT(&seghead.l);
const int hits = _rt_brep_shot_trace(stp, &ray, &application, &seghead,
&trace);
struct seg *segment;
while (BU_LIST_WHILE(segment, seg, &seghead.l)) {
BU_LIST_DEQUEUE(&segment->l);
RT_FREE_SEG(segment, resource);
}
return hits;
}
static void
report_trace(const struct rt_brep_shot_trace &trace)
{
std::printf(" selected/fallback/hits=%zu/%d/%zu boxes/roots/events="
"%zu/%zu/%zu stream=%zu/%zu/%zu/%zu/%zu stage=%d\n",
trace.prepared_production_selected, trace.prepared_production_fallback,
trace.prepared_production_hits, trace.stored_surface_boxes,
trace.stored_local_roots, trace.stored_physical_events,
trace.physical_event_regular_stream_attempts,
trace.physical_event_regular_stream_certified,
trace.physical_event_regular_stream_components,
trace.physical_event_regular_stream_boxes,
trace.physical_event_regular_stream_roots,
trace.physical_event_regular_stream_failure_stage);
for (size_t root_index = 0; root_index < trace.stored_local_roots;
++root_index) {
const struct rt_brep_trace_local_root &root =
trace.local_roots[root_index];
std::printf(" root[%zu] face/span=%d/%d uv=(%.17g,%.17g) t=%.17g "
"normal/direction=%.17g/%d\n", root_index, root.face_index,
root.span_index, root.uv[0], root.uv[1], root.dist,
root.normal_dot, root.direction);
}
for (size_t box_index = 0; box_index < trace.stored_surface_boxes;
++box_index) {
const struct rt_brep_trace_surface_box &box =
trace.surface_boxes[box_index];
std::printf(" box[%zu] face/span=%d/%d uv=[%.17g,%.17g]x"
"[%.17g,%.17g] t=[%.17g,%.17g] state/sign=%d/%d\n",
box_index, box.face_index, box.span_index, box.uv_min[0],
box.uv_max[0], box.uv_min[1], box.uv_max[1], box.t_min,
box.t_max, box.disposition, box.determinant_sign);
}
for (size_t event_index = 0;
event_index < trace.stored_physical_events; ++event_index) {
const struct rt_brep_trace_physical_event &event =
trace.physical_events[event_index];
std::printf(" event[%zu] face/span=%d/%d t=[%.17g,%.17g] "
"distance=%.17g root/boxes=%zu/%zu certificate/direction=%d/%d\n",
event_index, event.face_index, event.span_index, event.t_min,
event.t_max, event.dist, event.source_root, event.source_box_count,
event.certificate, event.direction);
}
}
static bool
trace_has_expected_events(const struct rt_brep_shot_trace &trace, int hits,
double distance_tolerance)
{
if (hits != 2 || trace.prepared_production_selected != 1 ||
trace.prepared_production_fallback != RT_BREP_PREPARED_FALLBACK_NONE ||
trace.prepared_production_hits != 2 || trace.physical_event_complete != 1 ||
trace.physical_event_regular_stream_attempts != 1 ||
trace.physical_event_regular_stream_certified != 1 ||
trace.physical_event_regular_stream_components != 2 ||
trace.physical_event_regular_stream_boxes !=
trace.stored_surface_boxes ||
trace.physical_event_regular_stream_roots !=
trace.stored_local_roots ||
trace.physical_event_regular_stream_failure_stage ||
trace.stored_local_roots != 8 || trace.stored_physical_events != 2 ||
trace.physical_event_regular != 2 || trace.physical_event_unresolved ||
trace.physical_event_direction_mismatches ||
trace.physical_event_state_failures ||
trace.physical_event_material_segments != 1 ||
trace.physical_event_subminimum_contacts ||
trace.physical_event_tolerance_ambiguous)
return false;
const double expected_distance[2] = {
SPAN_JUNCTION_ENTRY_DISTANCE, SPAN_JUNCTION_EXIT_DISTANCE
};
const int expected_direction[2] = {
RT_BREP_TRACE_ENTERING, RT_BREP_TRACE_LEAVING
};
size_t source_boxes = 0;
for (size_t event_index = 0; event_index < 2; ++event_index) {
const struct rt_brep_trace_physical_event &event =
trace.physical_events[event_index];
if (event.certificate != RT_BREP_TRACE_EVENT_REGULAR_INTERIOR ||
event.source_kind != RT_BREP_TRACE_EVENT_SOURCE_LOCAL_ROOT ||
event.source_root >= trace.stored_local_roots ||
event.source_box >= trace.stored_surface_boxes ||
event.source_box_count < 4 || !event.determinant_sign ||
event.direction != expected_direction[event_index] ||
fabs(event.dist - expected_distance[event_index]) >
distance_tolerance || event.dist < event.t_min ||
event.dist > event.t_max)
return false;
source_boxes += event.source_box_count;
}
if (source_boxes != trace.stored_surface_boxes)
return false;
for (size_t box_index = 0; box_index < trace.stored_surface_boxes;
++box_index) {
const struct rt_brep_trace_surface_box &box =
trace.surface_boxes[box_index];
if (box.disposition != RT_BREP_TRACE_BOX_RESOLVED_REGULAR_STREAM ||
!box.determinant_sign)
return false;
}
return true;
}
static bool
trace_has_span_grid_events(const struct rt_brep_shot_trace &trace, int hits,
size_t forward_box_count, size_t reverse_box_count, bool reverse)
{
if (!forward_box_count || !reverse_box_count)
return false;
const size_t local_root_count = forward_box_count + reverse_box_count;
if (hits != 2 || trace.prepared_production_selected != 1 ||
trace.prepared_production_fallback != RT_BREP_PREPARED_FALLBACK_NONE ||
trace.prepared_production_hits != 2 || trace.physical_event_complete != 1 ||
trace.physical_event_regular_stream_attempts != 1 ||
trace.physical_event_regular_stream_certified != 1 ||
trace.physical_event_regular_stream_components !=
SPAN_JUNCTION_STREAM_EVENTS ||
trace.physical_event_regular_stream_boxes !=
trace.stored_surface_boxes ||
trace.physical_event_regular_stream_roots !=
trace.stored_local_roots ||
trace.physical_event_regular_stream_failure_stage ||
trace.stored_surface_boxes != local_root_count ||
trace.stored_local_roots != local_root_count ||
trace.stored_physical_events != SPAN_JUNCTION_STREAM_EVENTS ||
trace.physical_event_regular != SPAN_JUNCTION_STREAM_EVENTS ||
trace.physical_event_unresolved ||
trace.physical_event_direction_mismatches ||
trace.physical_event_state_failures ||
trace.physical_event_material_segments != 1 ||
trace.physical_event_subminimum_contacts ||
trace.physical_event_tolerance_ambiguous)
return false;
const size_t expected_boxes[SPAN_JUNCTION_STREAM_EVENTS] = {
reverse ? reverse_box_count : forward_box_count,
reverse ? forward_box_count : reverse_box_count
};
const int expected_direction[SPAN_JUNCTION_STREAM_EVENTS] = {
RT_BREP_TRACE_ENTERING, RT_BREP_TRACE_LEAVING
};
size_t source_boxes = 0;
for (size_t event_index = 0;
event_index < SPAN_JUNCTION_STREAM_EVENTS; ++event_index) {
const struct rt_brep_trace_physical_event &event =
trace.physical_events[event_index];
if (event.certificate != RT_BREP_TRACE_EVENT_REGULAR_INTERIOR ||
event.source_kind != RT_BREP_TRACE_EVENT_SOURCE_LOCAL_ROOT ||
event.source_root >= trace.stored_local_roots ||
event.source_box >= trace.stored_surface_boxes ||
event.source_box_count != expected_boxes[event_index] ||
!event.determinant_sign ||
event.direction != expected_direction[event_index] ||
event.dist < event.t_min || event.dist > event.t_max)
return false;
source_boxes += event.source_box_count;
}
if (source_boxes != trace.stored_surface_boxes)
return false;
for (size_t box_index = 0; box_index < trace.stored_surface_boxes;
++box_index) {
const struct rt_brep_trace_surface_box &box =
trace.surface_boxes[box_index];
if (box.disposition != RT_BREP_TRACE_BOX_RESOLVED_REGULAR_STREAM ||
!box.determinant_sign)
return false;
}
return true;
}
static bool
trace_oblique_span_grid(struct soltab *solid, struct rt_i *rtip,
struct resource *resource, const ON_3dVector &radial,
ON_3dVector tangent, size_t forward_box_count,
size_t reverse_box_count)
{
const double projection = tangent.x * radial.x + tangent.y * radial.y +
tangent.z * radial.z;
tangent.x -= projection * radial.x;
tangent.y -= projection * radial.y;
tangent.z -= projection * radial.z;
if (!tangent.Unitize())
return false;
ON_3dVector forward_direction(
-radial.x + SPAN_JUNCTION_OBLIQUE_TANGENT_FRACTION * tangent.x,
-radial.y + SPAN_JUNCTION_OBLIQUE_TANGENT_FRACTION * tangent.y,
-radial.z + SPAN_JUNCTION_OBLIQUE_TANGENT_FRACTION * tangent.z);
if (!forward_direction.Unitize())
return false;
const double radial_dot = radial.x * forward_direction.x +
radial.y * forward_direction.y + radial.z * forward_direction.z;
const double exit_distance = SPAN_JUNCTION_RAY_CLEARANCE -
2.0 * SPAN_JUNCTION_RADIUS * radial_dot;
if (!std::isfinite(exit_distance) ||
exit_distance <= SPAN_JUNCTION_RAY_CLEARANCE)
return false;
point_t forward_origin;
vect_t forward_ray;
VSET(forward_origin, SPAN_JUNCTION_RADIUS * radial.x -
SPAN_JUNCTION_RAY_CLEARANCE * forward_direction.x,
SPAN_JUNCTION_RADIUS * radial.y -
SPAN_JUNCTION_RAY_CLEARANCE * forward_direction.y,
SPAN_JUNCTION_RADIUS * radial.z -
SPAN_JUNCTION_RAY_CLEARANCE * forward_direction.z);
VSET(forward_ray, forward_direction.x, forward_direction.y,
forward_direction.z);
struct rt_brep_shot_trace forward_trace = {};
const int forward_hits = shoot_trace(solid, rtip, resource,
forward_origin, forward_ray, forward_trace);
const bool forward_valid = trace_has_span_grid_events(forward_trace,
forward_hits, forward_box_count, reverse_box_count, false);
if (!forward_valid)
report_trace(forward_trace);
point_t reverse_origin;
vect_t reverse_ray;
VSET(reverse_origin, SPAN_JUNCTION_RADIUS * radial.x +
exit_distance * forward_direction.x,
SPAN_JUNCTION_RADIUS * radial.y + exit_distance * forward_direction.y,
SPAN_JUNCTION_RADIUS * radial.z + exit_distance * forward_direction.z);
VSET(reverse_ray, -forward_direction.x, -forward_direction.y,
-forward_direction.z);
struct rt_brep_shot_trace reverse_trace = {};
const int reverse_hits = shoot_trace(solid, rtip, resource,
reverse_origin, reverse_ray, reverse_trace);
const bool reverse_valid = trace_has_span_grid_events(reverse_trace,
reverse_hits, forward_box_count, reverse_box_count, true);
if (!reverse_valid)
report_trace(reverse_trace);
return forward_valid && reverse_valid;
}
static ON_3dVector
span_junction_tangent_axis(const ON_3dVector &radial)
{
ON_3dVector tangent(1.0, 0.0, 0.0);
if (fabs(radial.y) < fabs(radial.x) &&
fabs(radial.y) <= fabs(radial.z))
tangent = ON_3dVector(0.0, 1.0, 0.0);
else if (fabs(radial.z) < fabs(radial.x) &&
fabs(radial.z) < fabs(radial.y))
tangent = ON_3dVector(0.0, 0.0, 1.0);
return tangent;
}
static bool
trace_mixed_span_junction(struct soltab *solid, struct rt_i *rtip,
struct resource *resource, const ON_3dVector &radial)
{
return trace_oblique_span_grid(solid, rtip, resource, radial,
span_junction_tangent_axis(radial), SPAN_JUNCTION_JUNCTION_BOXES,
SPAN_JUNCTION_ADJACENT_BOXES);
}
static bool
trace_singleton_span_junction(struct soltab *solid, struct rt_i *rtip,
struct resource *resource, const ON_3dVector &radial)
{
const ON_3dVector tangent(1.0, 1.0, 1.0);
return trace_oblique_span_grid(solid, rtip, resource, radial, tangent,
SPAN_JUNCTION_JUNCTION_BOXES,
SPAN_JUNCTION_SINGLETON_BOXES);
}
static bool
trace_adjacent_singleton_span(struct soltab *solid, struct rt_i *rtip,
struct resource *resource, ON_Brep *brep, int face_index)
{
ON_3dVector boundary_radial;
if (!span_junction_boundary_radial(brep, face_index,
SPAN_JUNCTION_BOUNDARY_TRANSVERSE_FRACTION, boundary_radial))
return false;
return trace_oblique_span_grid(solid, rtip, resource,
boundary_radial, span_junction_tangent_axis(boundary_radial),
SPAN_JUNCTION_ADJACENT_BOXES, SPAN_JUNCTION_SINGLETON_BOXES);
}
int
main(int argc, char **argv)
{
bu_setprogname(argv[0]);
int face_index = SPAN_JUNCTION_DEFAULT_FACE_INDEX;
if (argc == 2) {
char *end = NULL;
errno = 0;
const long parsed_index = strtol(argv[1], &end, 10);
if (errno || !end || end == argv[1] || *end ||
parsed_index < 0 || parsed_index > INT_MAX)
return 1;
face_index = (int)parsed_index;
} else if (argc != 1) {
return 1;
}
struct rt_i *rtip = rt_dirbuild_inmem(NULL, 0, NULL, 0);
if (!rtip) {
std::printf("FAIL: in-memory raytrace setup\n");
return 1;
}
rtip->rti_tol.magic = BN_TOL_MAGIC;
rtip->rti_tol.dist = 0.0005;
rtip->rti_tol.dist_sq = rtip->rti_tol.dist * rtip->rti_tol.dist;
rtip->rti_tol.perp = 1.0e-6;
rtip->rti_tol.para = 1.0 - rtip->rti_tol.perp;
if (!_rt_brep_set_surface_tree_depth(rtip,
SPAN_JUNCTION_SURFACE_TREE_DEPTH)) {
std::printf("FAIL: BREP surface tree setup\n");
rt_i_destroy(rtip);
return 1;
}
struct resource resource = {};
rt_init_resource(&resource, 0, rtip);
ON_Brep *brep = make_span_junction_sphere();
if (!brep || !brep->IsSolid()) {
std::printf("FAIL: span-junction sphere construction\n");
delete brep;
free_rtip(rtip, &resource);
return 1;
}
struct soltab *solid = prepare_brep_solid(rtip, brep);
if (!solid) {
std::printf("FAIL: span-junction sphere preparation\n");
delete brep;
free_rtip(rtip, &resource);
return 1;
}
ON_3dVector radial;
if (!span_junction_radial(brep, face_index, radial)) {
free_solid(solid);
free_rtip(rtip, &resource);
return 1;
}
point_t forward_origin;
vect_t forward_direction;
point_t reverse_origin;
vect_t reverse_direction;
VSET(forward_origin, SPAN_JUNCTION_ORIGIN_RADIUS * radial.x,
SPAN_JUNCTION_ORIGIN_RADIUS * radial.y,
SPAN_JUNCTION_ORIGIN_RADIUS * radial.z);
VSET(forward_direction, -radial.x, -radial.y, -radial.z);
VSET(reverse_origin, -SPAN_JUNCTION_ORIGIN_RADIUS * radial.x,
-SPAN_JUNCTION_ORIGIN_RADIUS * radial.y,
-SPAN_JUNCTION_ORIGIN_RADIUS * radial.z);
VSET(reverse_direction, radial.x, radial.y, radial.z);
const double distance_tolerance = std::max(1.0e-9,
(double)rtip->rti_tol.dist);
struct rt_brep_shot_trace forward_trace = {};
const int forward_hits = shoot_trace(solid, rtip, &resource,
forward_origin, forward_direction, forward_trace);
const bool forward_valid = trace_has_expected_events(forward_trace,
forward_hits, distance_tolerance);
if (!forward_valid) {
std::printf("FAIL: forward span-junction stream\n");
report_trace(forward_trace);
}
struct rt_brep_shot_trace reverse_trace = {};
const int reverse_hits = shoot_trace(solid, rtip, &resource,
reverse_origin, reverse_direction, reverse_trace);
const bool reverse_valid = trace_has_expected_events(reverse_trace,
reverse_hits, distance_tolerance);
if (!reverse_valid) {
std::printf("FAIL: reverse span-junction stream\n");
report_trace(reverse_trace);
}
const bool mixed_valid = face_index != SPAN_JUNCTION_DEFAULT_FACE_INDEX ||
trace_mixed_span_junction(solid, rtip, &resource, radial);
const bool singleton_valid =
face_index != SPAN_JUNCTION_DEFAULT_FACE_INDEX ||
trace_singleton_span_junction(solid, rtip, &resource, radial);
const bool adjacent_singleton_valid =
face_index != SPAN_JUNCTION_DEFAULT_FACE_INDEX ||
trace_adjacent_singleton_span(solid, rtip, &resource, brep, face_index);
free_solid(solid);
free_rtip(rtip, &resource);
return forward_valid && reverse_valid && mixed_valid && singleton_valid &&
adjacent_singleton_valid ? 0 : 1;
}
/*
* Local Variables:
* tab-width: 8
* mode: C++
* c-basic-offset: 4
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+31 -2
View File
@@ -28,6 +28,7 @@
#include "bu/color.h"
#include "bu/parallel.h"
#include "bu/snooze.h"
#include "vmath.h"
#include "bn.h"
#include "rt/db_attr.h"
@@ -246,11 +247,18 @@ _rt_gettree_region_end(struct db_tree_state *tsp, const struct db_full_path *pat
db_mater_color_region(tsp->ts_dbip, rp);
bu_avs_free(&avs);
/* d_uses also indexes solid instances and must use the same directory-
* hash semaphore as _rt_find_identical_solid(). */
{
int hash = db_dirhash(dp->d_namep);
ACQUIRE_SEMAPHORE_TREE(hash);
rp->reg_instnum = dp->d_uses++;
RELEASE_SEMAPHORE_TREE(hash);
}
/* enter critical section */
bu_semaphore_acquire(RT_SEM_RESULTS);
rp->reg_instnum = dp->d_uses++;
/*
* Add the region to the linked list of regions.
* Positions in the region bit vector are established at this time.
@@ -346,6 +354,7 @@ _rt_find_identical_solid(const matp_t mat, struct directory *dp, struct rt_i *rt
hash = db_dirhash(dp->d_namep);
retry:
/* Enter the appropriate dual critical-section */
ACQUIRE_SEMAPHORE_TREE(hash);
@@ -386,6 +395,16 @@ _rt_find_identical_solid(const matp_t mat, struct directory *dp, struct rt_i *rt
*/
if (stp->st_rtip != rtip) continue;
/* A matching soltab is linked before its potentially expensive
* primitive prep begins. Do not publish or reference its mutable
* prep result until the creating worker has completed under this
* same hash semaphore. */
if (!stp->st_prep_complete) {
RELEASE_SEMAPHORE_TREE(hash);
bu_snooze(1000);
goto retry;
}
/*
* stp now points to re-referenced solid. stp->st_id is
* non-zero, indicating pre-existing solid.
@@ -558,6 +577,7 @@ _rt_gettree_leaf(struct db_tree_state *tsp, const struct db_full_path *pathp, st
hash = db_dirhash(dp->d_namep);
ACQUIRE_SEMAPHORE_TREE(hash);
stp->st_aradius = -1;
stp->st_prep_complete = 1;
stp->st_uses--;
RELEASE_SEMAPHORE_TREE(hash);
return TREE_NULL; /* BAD */
@@ -619,6 +639,15 @@ _rt_gettree_leaf(struct db_tree_state *tsp, const struct db_full_path *pathp, st
bu_vls_free(&str);
}
/* Publish every primitive-specific pointer, bound, and path write before
* another reference is allowed to consume this soltab. */
{
int hash = db_dirhash(dp->d_namep);
ACQUIRE_SEMAPHORE_TREE(hash);
stp->st_prep_complete = 1;
RELEASE_SEMAPHORE_TREE(hash);
}
found_it:
BU_GET(curtree, union tree);
RT_TREE_INIT(curtree);
+1 -1
View File
@@ -10,7 +10,7 @@ set(PROC_DB_WDB_NMG_LIBS "libwdb;libnmg;${M_LIBRARY}")
set(PROC_DB_WDB_OPENNURBS_LIBS "libwdb;OPENNURBS::OPENNURBS;${M_LIBRARY}")
brlcad_addexec(bottest bottest.c "${PROC_DB_WDB_LIBS}" NO_INSTALL)
brlcad_addexec(brep_cobb brep_cobb.cpp "${PROC_DB_WDB_OPENNURBS_LIBS}" NO_STRICT NO_INSTALL)
brlcad_addexec(brep_cobb brep_cobb.cpp "libbrep;${PROC_DB_WDB_OPENNURBS_LIBS}" NO_STRICT NO_INSTALL)
brlcad_addexec(brep_cube "brep_cube.cpp;twistedcube.cpp" "${PROC_DB_WDB_OPENNURBS_LIBS}" NO_STRICT NO_INSTALL)
target_include_directories(brep_cube BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
brlcad_addexec(brep_invalid "brep_invalid.cpp" "${PROC_DB_WDB_OPENNURBS_LIBS}" NO_STRICT NO_INSTALL)
+20 -278
View File
@@ -1,293 +1,35 @@
/* B R E P _ C O B B . C P P
/* B R E P _ C O B B . C P P
* BRL-CAD
*
* Copyright (c) 2013-2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* Based off of code from Ayam:
*
* This software is copyrighted (c) 1998-2012 by
* Randolf Schultz (randolf.schultz@gmail.com).
* All rights reserved.
*
* The author hereby grants permission to use, copy, modify, distribute,
* and license this software and its documentation for any purpose, provided
* that existing copyright notices are retained in all copies and that this
* notice is included verbatim in any distributions. No written agreement,
* license, or royalty fee is required for any of the authorized uses.
* Modifications to this software may be copyrighted by their authors
* and need not follow the licensing terms described here, provided that
* the new terms are clearly indicated on the first page of each file where
* they apply.
*
* IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY
* FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
* ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
* DERIVATIVES THEREOF, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE
* IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAVE
* NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
* MODIFICATIONS.
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*/
/** @file proc-db/brep_cobb.cpp
*
* Creates a Cobb NURBS sphere with cube topology, per
*
* J.E. Cobb, “Tiling the Sphere with Rational Bezier Patches,”
* Tech. Report TR UUCS-88-009, Computer Science Dept.,
* Univ. of Utah, Salt Lake City, 1988.
* J. E. Cobb, "Tiling the Sphere with Rational Bezier Patches,"
* Tech. Report TR UUCS-88-009, University of Utah, 1988.
*/
#include "common.h"
#include "bio.h"
#include "brep.h"
#include "bu/app.h"
#include "wdb.h"
ON_BezierSurface *
ON_CobbSphereFace(double rotation_x, double rotation_z)
{
ON_3dVector rotation_x_axis(1, 0, 0);
ON_3dVector rotation_z_axis(0, 0, 1);
ON_3dVector rotation_center(0, 0, 0);
double rot_x = rotation_x * ON_PI/180.0;
double rot_z = rotation_z * ON_PI/180.0;
double t = sqrt(3.0);
double d = sqrt(2.0);
ON_4dPoint cv;
ON_BezierSurface *p1 = new ON_BezierSurface(3, true, 5, 5);
p1->ReserveCVCapacity(100);
cv.x = 4.0*(1.0-t);
cv.y = 4.0*(1.0-t);
cv.z = 4.0*(1.0-t);
cv.w = 4.0*(3.0-t);
p1->SetCV(0,0,cv);
cv.x = -d;
cv.y = d * (t - 4.0);
cv.z = d * (t - 4.0);
cv.w = d * (3.0*t - 2.0);
p1->SetCV(0,1,cv);
cv.x = 0.0;
cv.y = 4.0*(1.0-2.0*t)/3.0;
cv.z = 4.0*(1.0-2.0*t)/3.0;
cv.w = 4.0*(5.0-t)/3.0;
p1->SetCV(0,2,cv);
cv.x = d;
cv.y = d * (t - 4.0);
cv.z = d * (t - 4.0);
cv.w = d * (3*t - 2.0);
p1->SetCV(0,3,cv);
cv.x = 4.0*(t - 1.0);
cv.y = 4.0*(1.0-t);
cv.z = 4.0*(1.0-t);
cv.w = 4.0*(3.0-t);
p1->SetCV(0,4,cv);
cv.x = d*(t - 4.0);
cv.y = -d;
cv.z = d*(t-4.0);
cv.w = d*(3.0*t-2.0);
p1->SetCV(1,0,cv);
cv.x = (2.0-3.0*t)/2.0;
cv.y = (2.0-3.0*t)/2.0;
cv.z = -(t+6.0)/2.0;
cv.w = (t+6.0)/2.0;
p1->SetCV(1,1,cv);
cv.x = 0.0;
cv.y = d*(2.0*t-7.0)/3.0;
cv.z = -5.0*sqrt(6.0)/3.0;
cv.w = d*(t+6.0)/3.0;
p1->SetCV(1,2,cv);
cv.x = (3.0*t-2.0)/2.0;
cv.y = (2.0-3.0*t)/2.0;
cv.z = -(t+6.0)/2.0;
cv.w = (t+6.0)/2.0;
p1->SetCV(1,3,cv);
cv.x = d*(4.0-t);
cv.y = -d;
cv.z = d*(t-4.0);
cv.w = d*(3.0*t-2.0);
p1->SetCV(1,4,cv);
cv.x = 4.0*(1.0-2*t)/3.0;
cv.y = 0.0;
cv.z = 4.0*(1.0-2*t)/3.0;
cv.w = 4.0*(5.0-t)/3.0;
p1->SetCV(2,0,cv);
cv.x = d*(2.0*t-7.0)/3.0;
cv.y = 0.0;
cv.z = -5.0*sqrt(6.0)/3.0;
cv.w = d*(t+6.0)/3.0;
p1->SetCV(2,1,cv);
cv.x = 0.0;
cv.y = 0.0;
cv.z = 4.0*(t-5.0)/3.0;
cv.w = 4.0*(5.0*t-1.0)/9.0;
p1->SetCV(2,2,cv);
cv.x = -d*(2*t-7.0)/3.0;
cv.y = 0.0;
cv.z = -5.0*sqrt(6.0)/3.0;
cv.w = d*(t+6.0)/3.0;
p1->SetCV(2,3,cv);
cv.x = -4.0*(1.0-2.0*t)/3.0;
cv.y = 0.0;
cv.z = 4.0*(1.0-2.0*t)/3.0;
cv.w = 4.0*(5.0-t)/3.0;
p1->SetCV(2,4,cv);
cv.x = d*(t-4.0);
cv.y = d;
cv.z = d*(t-4.0);
cv.w = d*(3.0*t-2);
p1->SetCV(3,0,cv);
cv.x = (2.0-3.0*t)/2.0;
cv.y = -(2.0-3.0*t)/2.0;
cv.z = -(t+6.0)/2.0;
cv.w = (t+6.0)/2.0;
p1->SetCV(3,1,cv);
cv.x = 0.0;
cv.y = -d*(2.0*t-7.0)/3.0;
cv.z = -5.0*sqrt(6.0)/3.0;
cv.w = d*(t+6.0)/3.0;
p1->SetCV(3,2,cv);
cv.x = (3.0*t-2.0)/2.0;
cv.y = -(2.0-3.0*t)/2.0;
cv.z = -(t+6.0)/2.0;
cv.w = (t+6.0)/2.0;
p1->SetCV(3,3,cv);
cv.x = d*(4.0-t);
cv.y = d;
cv.z = d*(t-4.0);
cv.w = d*(3.0*t-2.0);
p1->SetCV(3,4,cv);
cv.x = 4.0*(1.0-t);
cv.y = -4.0*(1.0-t);
cv.z = 4.0*(1.0-t);
cv.w = 4.0*(3.0-t);
p1->SetCV(4,0,cv);
cv.x = -d;
cv.y = -d*(t-4.0);
cv.z = d*(t-4.0);
cv.w = d*(3.0*t-2.0);
p1->SetCV(4,1,cv);
cv.x = 0.0;
cv.y = -4.0*(1.0-2.0*t)/3.0;
cv.z = 4.0*(1.0-2.0*t)/3.0;
cv.w = 4.0*(5.0-t)/3.0;
p1->SetCV(4,2,cv);
cv.x = d;
cv.y = -d*(t-4.0);
cv.z = d*(t-4.0);
cv.w = d*(3.0*t-2.0);
p1->SetCV(4,3,cv);
cv.x = 4.0*(t-1.0);
cv.y = -4.0*(1.0-t);
cv.z = 4.0*(1.0-t);
cv.w = 4.0*(3.0-t);
p1->SetCV(4,4,cv);
p1->Rotate(rot_x, rotation_x_axis, rotation_center);
p1->Rotate(rot_z, rotation_z_axis, rotation_center);
return p1;
}
/* TODO - Need to find a more compact, efficient way to
* do this - shouldn't need 24 3d curves... */
ON_Brep *
Cobb_Sphere(double UNUSED(radius), ON_3dPoint *UNUSED(origin))
{
ON_Brep *b = ON_Brep::New();
// Patch 1 of 6
ON_BezierSurface *b1 = ON_CobbSphereFace(0, 0);
ON_NurbsSurface *p1_nurb = ON_NurbsSurface::New();
b1->GetNurbForm(*p1_nurb);
b->NewFace(*p1_nurb);
delete b1;
// Patch 2 of 6
ON_BezierSurface *b2 = ON_CobbSphereFace(90, 0);
ON_NurbsSurface *p2_nurb = ON_NurbsSurface::New();
b2->GetNurbForm(*p2_nurb);
b->NewFace(*p2_nurb);
delete b2;
// Patch 3 of 6
ON_BezierSurface *b3 = ON_CobbSphereFace(180, 0);
ON_NurbsSurface *p3_nurb = ON_NurbsSurface::New();
b3->GetNurbForm(*p3_nurb);
b->NewFace(*p3_nurb);
delete b3;
// Patch 4 of 6
ON_BezierSurface *b4 = ON_CobbSphereFace(270, 0);
ON_NurbsSurface *p4_nurb = ON_NurbsSurface::New();
b4->GetNurbForm(*p4_nurb);
b->NewFace(*p4_nurb);
delete b4;
// Patch 5 of 6
ON_BezierSurface *b5 = ON_CobbSphereFace(90, 90);
ON_NurbsSurface *p5_nurb = ON_NurbsSurface::New();
b5->GetNurbForm(*p5_nurb);
b->NewFace(*p5_nurb);
delete b5;
// Patch 6 of 6
ON_BezierSurface *b6 = ON_CobbSphereFace(90, 270);
ON_NurbsSurface *p6_nurb = ON_NurbsSurface::New();
b6->GetNurbForm(*p6_nurb);
b->NewFace(*p6_nurb);
delete b6;
b->Standardize();
b->Compact();
return b;
}
int
main(int argc, char** argv)
main(int argc, char **argv)
{
struct rt_wdb* outfp;
ON_Brep* brep;
ON_TextLog error_log;
ON_3dPoint origin(0.0,0.0,0.0);
const char* id_name = "B-Rep Cobb Sphere";
const char* geom_name = "cobb.s";
const char* db_name = "brep_cobb.g";
const char *id_name = "B-Rep Cobb Sphere";
const char *geom_name = "cobb.s";
const char *db_name = "brep_cobb.g";
bu_setprogname(argv[0]);
if (argc > 1) {
@@ -303,10 +45,8 @@ main(int argc, char** argv)
}
ON::Begin();
/* export brep to file */
bu_log("Writing a Cobb unit sphere b-rep to %s...\n", db_name);
outfp = wdb_fopen(db_name);
struct rt_wdb *outfp = wdb_fopen(db_name);
if (!outfp) {
bu_log("ERROR: unable to open %s for writing\n", db_name);
ON::End();
@@ -314,23 +54,25 @@ main(int argc, char** argv)
}
mk_id(outfp, id_name);
brep = Cobb_Sphere(1, &origin);
const ON_3dPoint origin(0.0, 0.0, 0.0);
ON_Brep *brep = ON_Brep_CobbSphereUnsewn(1.0, origin);
if (!brep) {
bu_log("ERROR: unable to construct the Cobb sphere\n");
db_close(outfp->dbip);
ON::End();
return 1;
}
mk_brep(outfp, geom_name, (void *)brep);
//mk_comb1(outfp, "cube.r", geom_name, 1);
unsigned char rgb[] = {50, 255, 50};
mk_region1(outfp, "cobb.r", geom_name, "plastic", "", rgb);
db_close(outfp->dbip);
delete brep;
ON::End();
return 0;
}
/*
* Local Variables:
* tab-width: 8