Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc130e1e7a | ||
|
|
dc3b4abaa1 | ||
|
|
3f1eb8f584 | ||
|
|
561b3c3aab | ||
|
|
d03b9838a1 | ||
|
|
48cd318a6d | ||
|
|
1f0193dbaf | ||
|
|
2e7e827ba0 | ||
|
|
e7c61497df | ||
|
|
08da151a6e |
@@ -43,6 +43,7 @@ dependencies also receive substantial updates.
|
||||
* fixed mged view panning, edit rotation, & ray queries - Cliff Yapp
|
||||
* preserved ARB, CLINE, TGC, & extrusion geometry edits - Cliff Yapp
|
||||
* restored mged command prompt focus at GUI startup - Cliff Yapp
|
||||
* new documentation on geometry representation schemes - Sean Morrison
|
||||
* improved pixscale & bwscale scaling and image piping - Sean Morrison
|
||||
* restored pixdiff behavior on .pix, added -b for .bw - Sean Morrison
|
||||
* improved facetize fallback methods and reliability - Cliff Yapp
|
||||
|
||||
@@ -2668,15 +2668,6 @@ DOCUMENTATION
|
||||
Comparison of how BRL-CAD overlaps feature-wise with various
|
||||
major commercial CAD systems.
|
||||
|
||||
* Solid Geometry Representation Comparisons
|
||||
Describe the implications, tradeoffs, and conversion issues
|
||||
involved when converting between different geometry
|
||||
representations. Includes describing implicit geometry,
|
||||
explicit polygonal boundary representations, explicit spline
|
||||
surface representations, volumetric models, wireframes,
|
||||
boolean operations, feature edit operations, and parametric
|
||||
modeling.
|
||||
|
||||
* BRL-CAD Open Source
|
||||
An overview of how BRL-CAD is organized as an open source
|
||||
project and community describing the developer meritocracy,
|
||||
|
||||
@@ -20,6 +20,7 @@ set(
|
||||
procedural_geometry.adoc
|
||||
projection_shader.adoc
|
||||
renderer_showcase.adoc
|
||||
solid_geometry_comparison.adoc
|
||||
taxonomy.adoc
|
||||
tire.adoc
|
||||
)
|
||||
@@ -227,6 +228,17 @@ set(
|
||||
images/renderer_gallery_rthide.png
|
||||
images/renderer_gallery_rtdepth.png
|
||||
images/renderer_gallery_rtxray.png
|
||||
images/solid_rep_csg.png
|
||||
images/solid_rep_brep_boolean.png
|
||||
images/solid_rep_brep_evaluated.png
|
||||
images/solid_rep_polygonal_boolean.png
|
||||
images/solid_rep_nmg.png
|
||||
images/solid_rep_bot.png
|
||||
images/solid_rep_revolve.png
|
||||
images/solid_rep_voxel_cells.png
|
||||
images/solid_rep_volume.png
|
||||
images/solid_rep_point_cloud.png
|
||||
images/solid_rep_wireframe.png
|
||||
images/adrt_castle_path.png
|
||||
images/adrt_castle_rt.png
|
||||
images/adrt_castle_rt_ao.png
|
||||
@@ -409,6 +421,54 @@ if(
|
||||
set_target_properties(renderer-gallery-snapshot PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1)
|
||||
endif()
|
||||
|
||||
# Solid geometry comparison -- opt-in regeneration of committed snapshots.
|
||||
#
|
||||
# The article and solid_rep_*.png images are normal committed documentation.
|
||||
# This target rebuilds the common pawn, derives each representation with the
|
||||
# built tools, and refreshes the images for review.
|
||||
option(
|
||||
BRLCAD_GENERATE_SOLID_REPRESENTATION_COMPARISON
|
||||
"Regenerate the solid geometry representation comparison snapshots"
|
||||
OFF
|
||||
)
|
||||
if(
|
||||
BRLCAD_GENERATE_SOLID_REPRESENTATION_COMPARISON
|
||||
AND TARGET mged
|
||||
AND TARGET rt
|
||||
AND TARGET g-voxel
|
||||
AND TARGET asc2pix
|
||||
AND TARGET fbclear
|
||||
AND TARGET plot3-fb
|
||||
AND TARGET fb-png
|
||||
)
|
||||
add_custom_target(
|
||||
solid-representation-comparison-snapshot
|
||||
COMMAND
|
||||
${CMAKE_COMMAND}
|
||||
-DMGED=$<TARGET_FILE:mged>
|
||||
-DRT=$<TARGET_FILE:rt>
|
||||
-DGVOXEL=$<TARGET_FILE:g-voxel>
|
||||
-DASC2PIX=$<TARGET_FILE:asc2pix>
|
||||
-DFBCLEAR=$<TARGET_FILE:fbclear>
|
||||
-DPLOT3FB=$<TARGET_FILE:plot3-fb>
|
||||
-DFBPNG=$<TARGET_FILE:fb-png>
|
||||
-DMODEL_SCRIPT=${CMAKE_CURRENT_SOURCE_DIR}/solid_geometry_comparison/create_pawn.tcl
|
||||
-DIMGDIR=${CMAKE_CURRENT_SOURCE_DIR}/images
|
||||
-DWORKDIR=${CMAKE_CURRENT_BINARY_DIR}/solid_representation_comparison_work
|
||||
-DSIZE=512
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/solid_geometry_comparison/generate_comparison.cmake
|
||||
DEPENDS mged rt g-voxel asc2pix fbclear plot3-fb fb-png
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Regenerating solid geometry representation comparison snapshots"
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
)
|
||||
set_target_properties(
|
||||
solid-representation-comparison-snapshot
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD 1
|
||||
)
|
||||
endif()
|
||||
|
||||
# ADRT path-tracing gallery -- opt-in regeneration of the committed snapshot.
|
||||
#
|
||||
# Like the galleries above, the adrt_pathtrace.adoc article is hand-written and
|
||||
@@ -464,6 +524,8 @@ cmakefiles(
|
||||
gallery/generate_gallery.cmake
|
||||
renderer_gallery/renderer_gallery_manifest.cmake
|
||||
renderer_gallery/generate_renderer_gallery.cmake
|
||||
solid_geometry_comparison/create_pawn.tcl
|
||||
solid_geometry_comparison/generate_comparison.cmake
|
||||
adrt_gallery/generate_adrt_gallery.cmake
|
||||
gallery/inputs/pipe.stdin
|
||||
gallery/inputs/wire.stdin
|
||||
|
||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,294 @@
|
||||
= Solid Geometry Representation Comparisons
|
||||
:doctype: article
|
||||
:toc:
|
||||
:toclevels: 2
|
||||
|
||||
The same shape can be stored in very different ways. This comparison uses a
|
||||
purpose-built chess pawn to show which information BRL-CAD retains, evaluates,
|
||||
or samples when moving among its principal geometry representations. It is a
|
||||
guide to semantics and conversion choices, not a claim that one representation
|
||||
is universally best.
|
||||
|
||||
== Common model and method
|
||||
|
||||
The reference pawn is a region containing seven overlapping ellipsoids. Their
|
||||
union is non-trivial enough to exercise curved surfaces, overlapping interiors,
|
||||
and several component boundaries while remaining reliable across the
|
||||
conversions shown here. The sketch-and-revolve version independently follows
|
||||
the same nominal profile; it is not an exact conversion of the seven-piece CSG
|
||||
model.
|
||||
|
||||
Every shaded image uses the same orthographic view, 512x512 image size,
|
||||
material, and single-threaded renderer. The wireframe uses the same view and
|
||||
image size. The faceting and sampling settings are intentionally coarse enough
|
||||
to remain visible. A similar silhouette therefore does not mean that two
|
||||
database objects have the same structure or numerical boundary.
|
||||
|
||||
NOTE: This article and its images are a committed snapshot. Developers can
|
||||
regenerate the figures from built BRL-CAD tools by configuring with
|
||||
`-DBRLCAD_GENERATE_SOLID_REPRESENTATION_COMPARISON=ON` and building the
|
||||
`solid-representation-comparison-snapshot` target.
|
||||
|
||||
== At a glance
|
||||
|
||||
[cols="2,2,1,2,4",options="header"]
|
||||
|===
|
||||
|Form
|
||||
|BRL-CAD storage
|
||||
|Solid?
|
||||
|Boolean state
|
||||
|Editing and conversion consequences
|
||||
|
||||
|Analytic CSG
|
||||
|Seven `ell` leaves in a combination
|
||||
|Yes
|
||||
|Retained
|
||||
|Compact primitive parameters and tree edits preserve construction intent.
|
||||
|
||||
|B-rep tree
|
||||
|Closed `brep` leaves in a combination
|
||||
|Yes
|
||||
|Retained
|
||||
|NURBS surfaces and trims replace analytic leaves, but the assembly tree remains.
|
||||
|
||||
|Evaluated B-rep
|
||||
|One closed `brep`
|
||||
|Yes
|
||||
|Evaluated
|
||||
|Direct face, edge, trim, and control-point editing is possible; original
|
||||
features and hierarchy are gone.
|
||||
|
||||
|Polygonal tree
|
||||
|Solid-mode `bot` leaves in a combination
|
||||
|Yes
|
||||
|Retained
|
||||
|Leaf tessellation is approximate, but components and their union tree remain
|
||||
independently replaceable.
|
||||
|
||||
|Evaluated NMG
|
||||
|One `nmg`
|
||||
|Yes
|
||||
|Evaluated
|
||||
|Stores explicit, topology-rich polygonal boundaries and can record
|
||||
non-manifold relationships.
|
||||
|
||||
|Evaluated triangle mesh
|
||||
|One solid-mode `bot`
|
||||
|Yes
|
||||
|Evaluated
|
||||
|Portable triangle connectivity, but curved geometry and construction history
|
||||
have been sampled away.
|
||||
|
||||
|Sketch and revolve
|
||||
|One `sketch` referenced by one `revolve`
|
||||
|Yes
|
||||
|Not needed here
|
||||
|The profile, axis, and sweep angle are compact, meaningful parameters.
|
||||
|
||||
|Voxel cells
|
||||
|A region containing many `arb8` boxes
|
||||
|Yes (sampled)
|
||||
|Cell union retained
|
||||
|Individual cells are ordinary CSG geometry; size grows rapidly as cells
|
||||
shrink.
|
||||
|
||||
|Sampled volume
|
||||
|A file-backed `vol` scalar grid
|
||||
|Yes (thresholded)
|
||||
|None
|
||||
|Compact regular-grid occupancy; resolution and threshold determine the boundary.
|
||||
|
||||
|Point cloud
|
||||
|One `pnts` object with positions and normals
|
||||
|No pawn interior
|
||||
|None
|
||||
|Useful as measured or sampled evidence, but it has no inherent connected
|
||||
boundary or inside/outside rule.
|
||||
|
||||
|Wireframe
|
||||
|Derived line display of the CSG object
|
||||
|No
|
||||
|Uses source tree
|
||||
|A view-dependent drawing aid, not an independent solid representation.
|
||||
|===
|
||||
|
||||
"Solid" in this table means that the stored object supplies usable
|
||||
inside/outside semantics for the pawn. An arbitrary B-rep or mesh is not
|
||||
automatically a solid: its boundary must be closed and consistently oriented.
|
||||
The point size used to render `pnts` makes each sample a small sphere, but those
|
||||
spheres do not reconstruct the pawn interior. A wireframe likewise does not
|
||||
become a solid merely because its curves appear closed in one view.
|
||||
|
||||
== Implicit and parametric forms
|
||||
|
||||
[cols="1,1"]
|
||||
[%noheader]
|
||||
|===
|
||||
|image:images/solid_rep_csg.png[Analytic CSG pawn,512]
|
||||
|image:images/solid_rep_revolve.png[Sketch and revolve pawn,512]
|
||||
|
||||
|*Analytic CSG.* Native primitives retain their equations and dimensions; the
|
||||
region evaluates their union during ray tracing.
|
||||
|*Sketch plus revolve.* A closed line-segment profile is swept through 360
|
||||
degrees. The stepped profile is intentional and remains editable in two
|
||||
dimensions.
|
||||
|===
|
||||
|
||||
Analytic CSG is an implicit representation: intersections with ideal primitive
|
||||
surfaces are computed as needed rather than read from a stored boundary mesh.
|
||||
It is usually the most compact form here and makes edits such as changing the
|
||||
head radius or stem proportions direct. Its Boolean hierarchy also records why the
|
||||
shape was assembled.
|
||||
|
||||
`sketch` plus `revolve` is another procedural, parameterized description. It
|
||||
works especially well for an axisymmetric part, although a sketch with only
|
||||
line segments produces conical and cylindrical bands rather than the pawn's
|
||||
original ellipsoidal surfaces. BRL-CAD supports primitive parameters, placement
|
||||
matrices, sketches, and sweeps; that should not be confused with a general
|
||||
constraint solver or an associative feature-history system.
|
||||
|
||||
== Spline boundary representations
|
||||
|
||||
[cols="1,1"]
|
||||
[%noheader]
|
||||
|===
|
||||
|image:images/solid_rep_brep_boolean.png[B-rep leaves with Boolean tree,512]
|
||||
|image:images/solid_rep_brep_evaluated.png[Single evaluated B-rep,512]
|
||||
|
||||
|*B-rep with Booleans.* Each analytic leaf becomes a closed B-rep whose
|
||||
boundaries are trimmed NURBS surfaces. The original union tree is retained.
|
||||
|*B-rep without Booleans.* Boolean evaluation produces one explicit boundary;
|
||||
operand identities and the construction tree no longer exist as editable
|
||||
features.
|
||||
|===
|
||||
|
||||
A B-rep stores faces, trims, edges, vertices, and their topology explicitly.
|
||||
Rational NURBS can represent the source quadrics exactly as surfaces, but that
|
||||
does not make every CSG-to-B-rep conversion exact: intersections, trimming,
|
||||
topology classification, and repair use numerical tolerances. Keeping a tree
|
||||
of B-rep leaves isolates conversion to each primitive and retains high-level
|
||||
editing. Evaluating the tree yields a more self-contained boundary for
|
||||
exchange and face-level work, at the cost of that history. This snapshot's
|
||||
evaluated result passes both `brep ... valid` and `brep ... solid`; conversion
|
||||
results should be checked rather than assumed to be closed.
|
||||
|
||||
== Polygonal boundaries
|
||||
|
||||
[cols="1,1,1"]
|
||||
[%noheader]
|
||||
|===
|
||||
|image:images/solid_rep_polygonal_boolean.png[BoT leaves with Boolean tree,512]
|
||||
|image:images/solid_rep_nmg.png[Evaluated NMG pawn,512]
|
||||
|image:images/solid_rep_bot.png[Evaluated BoT pawn,512]
|
||||
|
||||
|*Polygonal with Booleans.* Each leaf is independently tessellated to a BoT,
|
||||
then placed in the original union tree.
|
||||
|*Evaluated NMG.* One non-manifold geometry object records the evaluated
|
||||
polygonal boundary and its richer topology.
|
||||
|*Evaluated triangle mesh.* One solid, counter-clockwise BoT stores 982
|
||||
vertices and 1,960 triangular faces in this snapshot.
|
||||
|===
|
||||
|
||||
All three images expose the same coarse tessellation. Their similar appearance
|
||||
hides an important distinction: the first still has seven editable operands,
|
||||
whereas the NMG and single BoT are evaluated results. NMG can represent
|
||||
polygonal faces and non-manifold adjacency; BoT faces are triangles only.
|
||||
Neither recreates analytic curvature when converted back. Finer tessellation
|
||||
reduces visible error but increases storage and processing cost.
|
||||
|
||||
== Sampled forms
|
||||
|
||||
[cols="1,1,1"]
|
||||
[%noheader]
|
||||
|===
|
||||
|image:images/solid_rep_voxel_cells.png[Pawn represented by CSG voxel cells,512]
|
||||
|image:images/solid_rep_volume.png[Pawn represented by a VOL grid,512]
|
||||
|image:images/solid_rep_point_cloud.png[Pawn represented by surface points,512]
|
||||
|
||||
|*Explicit voxel cells.* `voxelize` tests 3 mm cells and creates a CSG union of
|
||||
699 boxes for cells exceeding the occupancy threshold.
|
||||
|*Sampled volume.* `g-voxel` samples a 2 mm grid; a file-backed `vol` interprets
|
||||
the resulting 17x17x30 byte array through an occupancy window.
|
||||
|*Point cloud.* `pnts gen` samples the analytic surface on a 2 mm grid. Small
|
||||
rendered spheres make the otherwise zero-area sample locations visible.
|
||||
|===
|
||||
|
||||
The two voxel images are deliberately separate. The `voxelize` command emits
|
||||
ordinary box primitives and a combination, while `vol` reads scalar samples on
|
||||
a regular grid and derives a threshold boundary at ray-trace time. Both are
|
||||
volumetric approximations whose accuracy, memory, and feature size depend on
|
||||
cell spacing. A point cloud samples only the surface and has no connectivity;
|
||||
surface reconstruction requires additional assumptions and may invent or omit
|
||||
geometry.
|
||||
|
||||
== Wireframe display
|
||||
|
||||
image::images/solid_rep_wireframe.png[Wireframe display of the analytic CSG pawn,512]
|
||||
|
||||
This wireframe is a view-plane plot of MGED's unevaluated display of the
|
||||
analytic CSG pawn; hidden lines are not removed. Wireframes are useful for
|
||||
selection, orientation, and seeing component curves, but they are ambiguous:
|
||||
curves alone do not say which side is material, which loops bound faces, or
|
||||
which surfaces enclose volume. Keep the solid that generated the display
|
||||
rather than treating the drawing as an equivalent conversion target.
|
||||
|
||||
== Conversion and editing consequences
|
||||
|
||||
A conversion command is not an equivalence guarantee. Before replacing source
|
||||
data, check at least these effects:
|
||||
|
||||
* *Tolerance and resolution:* B-rep Boolean evaluation uses intersection and
|
||||
topology tolerances; tessellation uses chord and angular tolerances; voxels
|
||||
and points use sample spacing and thresholds. Each can erase a feature
|
||||
smaller than its controlling tolerance.
|
||||
* *Construction intent:* evaluating a Boolean tree discards operand identity,
|
||||
primitive parameters, and ordering. Converting leaves while retaining the
|
||||
tree keeps more of that intent.
|
||||
* *Topology:* a visually plausible B-rep or mesh may contain gaps, duplicate
|
||||
faces, self-intersections, or inconsistent orientation. Validate closure
|
||||
and ray behavior, not only a shaded image.
|
||||
* *Attributes:* collapsing a hierarchy may change where region identifiers,
|
||||
materials, colors, and other attributes reside. Verify them separately
|
||||
from geometry.
|
||||
* *Round trips:* CSG to mesh to B-rep does not recover the original equations;
|
||||
points to a surface requires reconstruction; resampling a volume compounds
|
||||
discretization. Retain the highest-level trustworthy source.
|
||||
|
||||
For parameter changes and repeated design work, prefer analytic primitives,
|
||||
sketch sweeps, and preserved combination trees. Prefer a validated B-rep when
|
||||
trimmed-surface topology or exchange requires it. Use NMG for topology-rich
|
||||
polygonal processing and BoT for triangle-oriented interchange or algorithms.
|
||||
Use voxels for occupancy operations and point clouds for measured or sampled
|
||||
data, not as silent substitutes for a watertight solid.
|
||||
|
||||
BRL-CAD also supports specialized forms such as DSP height fields, EBM
|
||||
extrusions, ARS surfaces, metaballs, and legacy `poly` and `bspline` objects.
|
||||
They are not shown as interchangeable pawn encodings: DSP and EBM are
|
||||
domain-specific, metaballs change the modeling method, and BoT and B-rep are the
|
||||
preferred general successors to the legacy polygon and spline forms.
|
||||
|
||||
== Reproducing the conversions
|
||||
|
||||
The snapshot generator creates the database and runs these principal MGED
|
||||
operations (names shortened here only where noted):
|
||||
|
||||
[source]
|
||||
----
|
||||
brep comparison.csg.r brep --no-evaluation
|
||||
brep comparison.csg.r brep comparison.brep_evaluated.s
|
||||
facetize -n comparison.csg.r comparison.nmg.s
|
||||
facetize --methods NMG comparison.csg.r comparison.bot.s
|
||||
facetize --methods NMG base.s base.bot # repeated for all seven leaves
|
||||
voxelize -s "3 3 3" -d 4 -t 0.45 comparison.voxel_cells.r comparison.csg.r
|
||||
pnts gen -t 2 --surface --grid comparison.csg.r comparison.samples
|
||||
pnts write comparison.samples comparison.xyz
|
||||
pnts read -f xyzijk --size 0.45 comparison.xyz comparison.point_cloud.s
|
||||
----
|
||||
|
||||
The generator also runs `g-voxel` and expands its sparse occupied-cell report
|
||||
into the x-fastest byte array used by `vol`. It then renders every shaded
|
||||
object with `rt -B -M -P1` and the saved view; MGED's `plot -2d`, `plot3-fb`,
|
||||
and `fb-png` produce the wireframe image. See
|
||||
`solid_geometry_comparison/generate_comparison.cmake` and
|
||||
`solid_geometry_comparison/create_pawn.tcl` for the complete reproducible
|
||||
inputs.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Create the shared pawn used by the solid-representation comparison.
|
||||
|
||||
title {Solid geometry representation comparison pawn}
|
||||
units mm
|
||||
|
||||
# Deliberate overlaps keep the pawn connected while exercising curved-surface
|
||||
# intersections during Boolean evaluation.
|
||||
db put base.s ell V {0 0 4.5} A {17 0 0} B {0 17 0} C {0 0 4.5}
|
||||
db put base_bead.s ell V {0 0 9.5} A {14 0 0} B {0 14 0} C {0 0 5}
|
||||
db put body.s ell V {0 0 18} A {12 0 0} B {0 12 0} C {0 0 9}
|
||||
db put stem.s ell V {0 0 29} A {7 0 0} B {0 7 0} C {0 0 13}
|
||||
db put collar.s ell V {0 0 39} A {9 0 0} B {0 9 0} C {0 0 3.5}
|
||||
db put neck.s ell V {0 0 43} A {6.5 0 0} B {0 6.5 0} C {0 0 5}
|
||||
db put head.s ell V {0 0 50} A {9 0 0} B {0 9 0} C {0 0 9}
|
||||
|
||||
db put comparison.csg.r comb region yes id 1000 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {u {u {u {l base.s} {l base_bead.s}} {u {l body.s} {l stem.s}}} {u {u {l collar.s} {l neck.s}} {l head.s}}}
|
||||
|
||||
# The revolve uses a deliberately independent outline. Its single closed
|
||||
# sketch approximates the same pawn silhouette without copying the CSG tree.
|
||||
db put comparison.profile sketch V {0 0 0} A {1 0 0} B {0 0 1} VL {{0 0} {17 0} {17 2} {16.3 4} {15 6} {14 8} {15.4 7} {16 9} {15.4 11} {13.5 12.5} {11.2 14} {12 18} {11 21} {9.5 23} {8 26} {7.5 32} {6.5 36} {7.5 37} {9 39} {7.5 41} {6.5 42} {6.5 43} {7.5 44} {8.5 46} {9 50} {8.5 54} {6.7 56.5} {0 59}} SL {{line S 0 E 1} {line S 1 E 2} {line S 2 E 3} {line S 3 E 4} {line S 4 E 5} {line S 5 E 6} {line S 6 E 7} {line S 7 E 8} {line S 8 E 9} {line S 9 E 10} {line S 10 E 11} {line S 11 E 12} {line S 12 E 13} {line S 13 E 14} {line S 14 E 15} {line S 15 E 16} {line S 16 E 17} {line S 17 E 18} {line S 18 E 19} {line S 19 E 20} {line S 20 E 21} {line S 21 E 22} {line S 22 E 23} {line S 23 E 24} {line S 24 E 25} {line S 25 E 26} {line S 26 E 27} {line S 27 E 0}}
|
||||
db put comparison.revolve.s revolve V {0 0 0} axis {0 0 1} R {1 0 0} ang 6.283185307179586 sk_name comparison.profile
|
||||
db put comparison.revolve.r comb region yes id 1001 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.revolve.s}
|
||||
|
||||
q
|
||||
@@ -0,0 +1,372 @@
|
||||
# Regenerate the solid geometry representation comparison snapshots.
|
||||
#
|
||||
# The article is hand-written. This script builds its common pawn model,
|
||||
# derives each representation using BRL-CAD tools, and replaces the committed
|
||||
# images. All databases and sidecar data remain in WORKDIR.
|
||||
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
foreach(v MGED RT GVOXEL ASC2PIX FBCLEAR PLOT3FB FBPNG
|
||||
MODEL_SCRIPT IMGDIR WORKDIR)
|
||||
if(NOT DEFINED ${v})
|
||||
message(FATAL_ERROR "generate_comparison: -D${v}=... is required")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(NOT DEFINED SIZE)
|
||||
set(SIZE 512)
|
||||
endif()
|
||||
|
||||
set(RENDER_TIMEOUT 1800)
|
||||
set(PAWN_DB "${WORKDIR}/solid_representation_pawn.g")
|
||||
set(VIEW_SCRIPT "${WORKDIR}/pawn.view")
|
||||
set(MGED_SCRIPT "${WORKDIR}/assemble_variants.tcl")
|
||||
set(VOXELIZE_SCRIPT "${WORKDIR}/voxelize.tcl")
|
||||
set(VOXEL_LIST "${WORKDIR}/voxels1.txt")
|
||||
set(VOLUME_ASCII "${WORKDIR}/comparison_volume.asc")
|
||||
set(VOLUME_FILE "${WORKDIR}/comparison.vol")
|
||||
|
||||
file(REMOVE_RECURSE "${WORKDIR}")
|
||||
file(MAKE_DIRECTORY "${WORKDIR}")
|
||||
file(MAKE_DIRECTORY "${IMGDIR}")
|
||||
|
||||
function(run_process label)
|
||||
execute_process(
|
||||
COMMAND ${ARGN}
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
RESULT_VARIABLE process_result
|
||||
OUTPUT_VARIABLE process_stdout
|
||||
ERROR_VARIABLE process_stderr
|
||||
)
|
||||
if(NOT process_result EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"${label} failed (exit ${process_result})\n${process_stdout}${process_stderr}"
|
||||
)
|
||||
endif()
|
||||
message(STATUS "${label}")
|
||||
endfunction()
|
||||
|
||||
function(require_brep_property object property expected_output)
|
||||
execute_process(
|
||||
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}" brep "${object}" "${property}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
RESULT_VARIABLE property_result
|
||||
OUTPUT_VARIABLE property_stdout
|
||||
ERROR_VARIABLE property_stderr
|
||||
)
|
||||
set(property_output "${property_stdout}${property_stderr}")
|
||||
if(NOT property_result EQUAL 0 OR NOT property_output MATCHES "${expected_output}")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"B-rep ${object} is not ${property}\n${property_output}"
|
||||
)
|
||||
endif()
|
||||
message(STATUS "Validated B-rep ${object}: ${property}")
|
||||
endfunction()
|
||||
|
||||
execute_process(
|
||||
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
INPUT_FILE "${MODEL_SCRIPT}"
|
||||
RESULT_VARIABLE create_result
|
||||
OUTPUT_VARIABLE create_stdout
|
||||
ERROR_VARIABLE create_stderr
|
||||
)
|
||||
if(NOT create_result EQUAL 0 OR NOT EXISTS "${PAWN_DB}")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Creating the comparison pawn failed (exit ${create_result})\n${create_stdout}${create_stderr}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Preserve one Boolean hierarchy as B-rep leaves, and evaluate another into a
|
||||
# single B-rep. The default suffix is intentional: supplying an explicit
|
||||
# suffix changes parsing in older MGED versions.
|
||||
set(PAWN_LEAVES base base_bead body stem collar neck head)
|
||||
run_process(
|
||||
"Created Boolean tree of B-rep leaves"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" brep comparison.csg.r brep --no-evaluation
|
||||
)
|
||||
foreach(leaf IN LISTS PAWN_LEAVES)
|
||||
require_brep_property("${leaf}.s.brep" solid "brep is solid")
|
||||
endforeach()
|
||||
run_process(
|
||||
"Created evaluated B-rep"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" brep comparison.csg.r brep comparison.brep_evaluated.s
|
||||
)
|
||||
require_brep_property(comparison.brep_evaluated.s valid "brep is valid")
|
||||
require_brep_property(comparison.brep_evaluated.s solid "brep is solid")
|
||||
|
||||
# Generate both the topology-rich NMG form and the triangle-only BoT form.
|
||||
run_process(
|
||||
"Created evaluated NMG"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" facetize -n comparison.csg.r comparison.nmg.s
|
||||
)
|
||||
run_process(
|
||||
"Created evaluated BoT"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" facetize --methods NMG comparison.csg.r comparison.bot.s
|
||||
)
|
||||
|
||||
# Tessellating leaves independently preserves the editable Boolean hierarchy.
|
||||
foreach(leaf IN LISTS PAWN_LEAVES)
|
||||
run_process(
|
||||
"Tessellated ${leaf}.s"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" facetize --methods NMG "${leaf}.s" "${leaf}.bot"
|
||||
)
|
||||
endforeach()
|
||||
|
||||
file(WRITE "${VOXELIZE_SCRIPT}" "voxelize -s {3 3 3} -d 4 -t 0.45 comparison.voxel_cells.r comparison.csg.r\nq\n")
|
||||
execute_process(
|
||||
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
INPUT_FILE "${VOXELIZE_SCRIPT}"
|
||||
RESULT_VARIABLE voxelize_result
|
||||
OUTPUT_VARIABLE voxelize_stdout
|
||||
ERROR_VARIABLE voxelize_stderr
|
||||
)
|
||||
if(NOT voxelize_result EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Creating explicit voxel cells failed (exit ${voxelize_result})\n${voxelize_stdout}${voxelize_stderr}"
|
||||
)
|
||||
endif()
|
||||
message(STATUS "Created explicit voxel cells")
|
||||
|
||||
# PNTS stores point size at import time. The generator first writes all
|
||||
# sampled fields (xyzijk), then reads them back with a visible point diameter.
|
||||
run_process(
|
||||
"Sampled pawn surface"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" pnts gen -t 2 --surface --grid
|
||||
comparison.csg.r comparison.samples
|
||||
)
|
||||
run_process(
|
||||
"Exported pawn point samples"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" pnts write comparison.samples comparison.xyz
|
||||
)
|
||||
run_process(
|
||||
"Created visible point cloud"
|
||||
"${MGED}" --no-rc -c "${PAWN_DB}" pnts read -f xyzijk --size 0.45
|
||||
comparison.xyz comparison.point_cloud.s
|
||||
)
|
||||
|
||||
# g-voxel reports occupied cells. Expand that sparse report into the regular,
|
||||
# x-fastest byte array required by VOL. The pawn bounds are [-17,17] in X/Y
|
||||
# and [0,60] in Z; 2 mm cells therefore form a 17 x 17 x 30 grid.
|
||||
file(REMOVE "${VOXEL_LIST}")
|
||||
run_process(
|
||||
"Sampled VOL grid"
|
||||
"${GVOXEL}" -s "2 2 2" -d 4 -t 0.5 "${PAWN_DB}" comparison.csg.r
|
||||
)
|
||||
if(NOT EXISTS "${VOXEL_LIST}")
|
||||
message(FATAL_ERROR "g-voxel did not create ${VOXEL_LIST}")
|
||||
endif()
|
||||
|
||||
file(STRINGS "${VOXEL_LIST}" voxel_lines)
|
||||
list(LENGTH voxel_lines occupied_voxels)
|
||||
if(occupied_voxels EQUAL 0)
|
||||
message(FATAL_ERROR "g-voxel reported no occupied cells")
|
||||
endif()
|
||||
|
||||
foreach(line IN LISTS voxel_lines)
|
||||
string(
|
||||
REGEX MATCH
|
||||
"^\\((-?[0-9]+)\\.0+, (-?[0-9]+)\\.0+, (-?[0-9]+)\\.0+\\)[ \t]+[^ \t]+[ \t]+[0-9.]+$"
|
||||
parsed "${line}"
|
||||
)
|
||||
if(NOT parsed)
|
||||
message(FATAL_ERROR "Cannot parse g-voxel output: ${line}")
|
||||
endif()
|
||||
set(x_coord "${CMAKE_MATCH_1}")
|
||||
set(y_coord "${CMAKE_MATCH_2}")
|
||||
set(z_coord "${CMAKE_MATCH_3}")
|
||||
math(EXPR x_index "(${x_coord} + 16) / 2")
|
||||
math(EXPR y_index "(${y_coord} + 16) / 2")
|
||||
math(EXPR z_index "(${z_coord} - 1) / 2")
|
||||
if(x_index LESS 0 OR x_index GREATER 16 OR
|
||||
y_index LESS 0 OR y_index GREATER 16 OR
|
||||
z_index LESS 0 OR z_index GREATER 29)
|
||||
message(FATAL_ERROR "g-voxel coordinate outside the expected pawn grid: ${line}")
|
||||
endif()
|
||||
set("voxel_${x_index}_${y_index}_${z_index}" 255)
|
||||
endforeach()
|
||||
|
||||
set(volume_text "")
|
||||
foreach(z_index RANGE 0 29)
|
||||
foreach(y_index RANGE 0 16)
|
||||
foreach(x_index RANGE 0 16)
|
||||
if(DEFINED voxel_${x_index}_${y_index}_${z_index})
|
||||
set(sample ff)
|
||||
else()
|
||||
set(sample 00)
|
||||
endif()
|
||||
string(APPEND volume_text "${sample}\n")
|
||||
endforeach()
|
||||
endforeach()
|
||||
endforeach()
|
||||
file(WRITE "${VOLUME_ASCII}" "${volume_text}")
|
||||
|
||||
execute_process(
|
||||
COMMAND "${ASC2PIX}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
INPUT_FILE "${VOLUME_ASCII}"
|
||||
OUTPUT_FILE "${VOLUME_FILE}"
|
||||
RESULT_VARIABLE asc2pix_result
|
||||
ERROR_VARIABLE asc2pix_error
|
||||
)
|
||||
if(NOT asc2pix_result EQUAL 0)
|
||||
message(FATAL_ERROR "asc2pix failed (exit ${asc2pix_result})\n${asc2pix_error}")
|
||||
endif()
|
||||
file(SIZE "${VOLUME_FILE}" volume_size)
|
||||
if(NOT volume_size EQUAL 8670)
|
||||
message(FATAL_ERROR "VOL sidecar is ${volume_size} bytes; expected 8670")
|
||||
endif()
|
||||
message(STATUS "Encoded ${occupied_voxels} occupied VOL cells")
|
||||
|
||||
# Wrap single primitives in regions so every shaded comparison uses the same
|
||||
# material. The polygonal tree repeats the original CSG union hierarchy with
|
||||
# BoT leaves. VOL references a sidecar basename resolved beside the database.
|
||||
file(WRITE "${MGED_SCRIPT}" [=[
|
||||
db put comparison.brep_evaluated.r comb region yes id 1010 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.brep_evaluated.s}
|
||||
db put comparison.nmg.r comb region yes id 1020 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.nmg.s}
|
||||
db put comparison.bot.r comb region yes id 1030 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.bot.s}
|
||||
db put comparison.polygonal_boolean.r comb region yes id 1040 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {u {u {u {l base.bot} {l base_bead.bot}} {u {l body.bot} {l stem.bot}}} {u {u {l collar.bot} {l neck.bot}} {l head.bot}}}
|
||||
attr set comparison.voxel_cells.r region R region_id 1050 material_id 1 los 100 shader plastic color 235/190/90
|
||||
db put comparison.point_cloud.r comb region yes id 1060 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.point_cloud.s}
|
||||
db put comparison.volume.s vol file {comparison.vol} name {comparison.vol} src f w 17 n 17 d 30 lo 1 hi 255 size {2 2 2} mat {1 0 0 -17 0 1 0 -17 0 0 1 0 0 0 0 1}
|
||||
db put comparison.volume.r comb region yes id 1070 los 100 GIFTmater 1 rgb {235 190 90} shader {plastic} tree {l comparison.volume.s}
|
||||
q
|
||||
]=])
|
||||
|
||||
execute_process(
|
||||
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
INPUT_FILE "${MGED_SCRIPT}"
|
||||
RESULT_VARIABLE assemble_result
|
||||
OUTPUT_VARIABLE assemble_stdout
|
||||
ERROR_VARIABLE assemble_stderr
|
||||
)
|
||||
if(NOT assemble_result EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Assembling comparison regions failed (exit ${assemble_result})\n${assemble_stdout}${assemble_stderr}"
|
||||
)
|
||||
endif()
|
||||
|
||||
file(WRITE "${VIEW_SCRIPT}" "viewsize 8.000000000000000e+01;\n")
|
||||
file(APPEND "${VIEW_SCRIPT}" "orientation 2.480970000000000e-01 4.765910000000000e-01 7.480970000000000e-01 3.894350000000000e-01;\n")
|
||||
file(APPEND "${VIEW_SCRIPT}" "eye_pt 4.199070000000000e+01 2.940060000000000e+01 5.329140000000000e+01;\n")
|
||||
file(APPEND "${VIEW_SCRIPT}" "start 0; clean;\n")
|
||||
file(APPEND "${VIEW_SCRIPT}" "end;\n")
|
||||
|
||||
function(copy_snapshot local_image)
|
||||
if(NOT EXISTS "${local_image}")
|
||||
message(FATAL_ERROR "Expected image was not created: ${local_image}")
|
||||
endif()
|
||||
file(SIZE "${local_image}" image_size)
|
||||
if(image_size EQUAL 0)
|
||||
message(FATAL_ERROR "Rendered image is empty: ${local_image}")
|
||||
endif()
|
||||
file(COPY "${local_image}" DESTINATION "${IMGDIR}")
|
||||
get_filename_component(image_name "${local_image}" NAME)
|
||||
file(
|
||||
CHMOD "${IMGDIR}/${image_name}"
|
||||
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(render_snapshot name object)
|
||||
set(filename "solid_rep_${name}.png")
|
||||
set(local_image "${WORKDIR}/${filename}")
|
||||
file(REMOVE "${local_image}")
|
||||
execute_process(
|
||||
COMMAND "${RT}" -B -M -W "-s${SIZE}" -P1 -o "${filename}" "${PAWN_DB}" "${object}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
INPUT_FILE "${VIEW_SCRIPT}"
|
||||
RESULT_VARIABLE render_result
|
||||
OUTPUT_VARIABLE render_stdout
|
||||
ERROR_VARIABLE render_stderr
|
||||
)
|
||||
if(NOT render_result EQUAL 0)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"Rendering ${name} failed (exit ${render_result})\n${render_stdout}${render_stderr}"
|
||||
)
|
||||
endif()
|
||||
copy_snapshot("${local_image}")
|
||||
message(STATUS "Rendered ${filename}")
|
||||
endfunction()
|
||||
|
||||
render_snapshot(csg comparison.csg.r)
|
||||
render_snapshot(brep_boolean comparison.csg.r.brep)
|
||||
render_snapshot(brep_evaluated comparison.brep_evaluated.r)
|
||||
render_snapshot(polygonal_boolean comparison.polygonal_boolean.r)
|
||||
render_snapshot(nmg comparison.nmg.r)
|
||||
render_snapshot(bot comparison.bot.r)
|
||||
render_snapshot(revolve comparison.revolve.r)
|
||||
render_snapshot(voxel_cells comparison.voxel_cells.r)
|
||||
render_snapshot(volume comparison.volume.r)
|
||||
render_snapshot(point_cloud comparison.point_cloud.r)
|
||||
|
||||
# MGED exports its current wireframe display as view-plane plot3. Rasterize
|
||||
# that plot through a disk framebuffer so regeneration needs no display.
|
||||
set(WIREFRAME_PLOT "${WORKDIR}/solid_rep_wireframe.plot3")
|
||||
set(WIREFRAME_PNG "${WORKDIR}/solid_rep_wireframe.png")
|
||||
set(WIREFRAME_FB_NAME "solid_rep_wireframe.fb")
|
||||
set(WIREFRAME_SCRIPT "${WORKDIR}/wireframe.tcl")
|
||||
file(REMOVE "${WIREFRAME_PLOT}" "${WORKDIR}/${WIREFRAME_FB_NAME}" "${WIREFRAME_PNG}")
|
||||
file(WRITE "${WIREFRAME_SCRIPT}" [=[
|
||||
draw comparison.csg.r
|
||||
size 80
|
||||
orientation 0.248097 0.476591 0.748097 0.389435
|
||||
eye_pt 41.9907 29.4006 53.2914
|
||||
plot -2d solid_rep_wireframe.plot3
|
||||
q
|
||||
]=])
|
||||
execute_process(
|
||||
COMMAND "${MGED}" --no-rc -c "${PAWN_DB}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
INPUT_FILE "${WIREFRAME_SCRIPT}"
|
||||
RESULT_VARIABLE wireframe_result
|
||||
OUTPUT_VARIABLE wireframe_stdout
|
||||
ERROR_VARIABLE wireframe_stderr
|
||||
)
|
||||
if(NOT wireframe_result EQUAL 0 OR NOT EXISTS "${WIREFRAME_PLOT}")
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"MGED wireframe export failed (exit ${wireframe_result})\n${wireframe_stdout}${wireframe_stderr}"
|
||||
)
|
||||
endif()
|
||||
message(STATUS "Exported wireframe plot")
|
||||
run_process(
|
||||
"Cleared wireframe framebuffer"
|
||||
"${FBCLEAR}" -c -F "${WIREFRAME_FB_NAME}" -w "${SIZE}" -n "${SIZE}" 24 24 24
|
||||
)
|
||||
execute_process(
|
||||
COMMAND "${PLOT3FB}" -F "${WIREFRAME_FB_NAME}" -o -w "${SIZE}" -n "${SIZE}"
|
||||
WORKING_DIRECTORY "${WORKDIR}"
|
||||
TIMEOUT ${RENDER_TIMEOUT}
|
||||
INPUT_FILE "${WIREFRAME_PLOT}"
|
||||
RESULT_VARIABLE plot_result
|
||||
OUTPUT_VARIABLE plot_stdout
|
||||
ERROR_VARIABLE plot_stderr
|
||||
)
|
||||
if(NOT plot_result EQUAL 0)
|
||||
message(FATAL_ERROR "plot3-fb failed (exit ${plot_result})\n${plot_stdout}${plot_stderr}")
|
||||
endif()
|
||||
run_process(
|
||||
"Rasterized wireframe snapshot"
|
||||
"${FBPNG}" -F "${WIREFRAME_FB_NAME}" -w "${SIZE}" -n "${SIZE}"
|
||||
"solid_rep_wireframe.png"
|
||||
)
|
||||
copy_snapshot("${WIREFRAME_PNG}")
|
||||
|
||||
message(STATUS "Solid representation comparison: 11 snapshots regenerated")
|
||||
@@ -22,4 +22,5 @@ Task-focused guides and how-to articles.
|
||||
* link:articles/procedural_geometry.html[BRL-CAD Procedural Geometry Gallery]
|
||||
* link:articles/projection_shader.html[Using the Projection Shader]
|
||||
* link:articles/renderer_showcase.html[BRL-CAD Renderer Showcase]
|
||||
* link:articles/solid_geometry_comparison.html[Solid Geometry Representation Comparisons]
|
||||
* link:articles/tire.html[Vehicle Tire and Wheel Creation in BRL-CAD]
|
||||
|
||||