Compare commits

..
6 Commits
Author SHA1 Message Date
Clifford Yapp 1ab8c379e9 Stash some thoughts about what to do with BoTs. 2026-09-18 10:12:00 -04:00
Clifford Yapp c12e55973a Stash some info gleaned from old reports and manuals. 2026-09-18 10:12:00 -04:00
Clifford Yapp 1968ba02a3 Quick stab at adding some support for pre-GIFT COMGEOM
Only known surviving examples would be fuzzy printouts in old report scans -
would need really good OCR work to get those set up to actually test, so in the
meantime just checkpointing the V0 work to revisit later.
2026-09-18 10:12:00 -04:00
Clifford Yapp 40621be317 Various minor adjustments 2026-09-18 10:12:00 -04:00
Clifford Yapp 92ba4caa42 Use correct type for opt target 2026-09-18 10:12:00 -04:00
Clifford Yapp ef75af075a Initial checkpoint of C++ rework of comgeom-g 2026-09-18 10:12:00 -04:00
64 changed files with 4788 additions and 4365 deletions
-246
View File
@@ -1,246 +0,0 @@
Annotation and view-object rendering notes
==========================================
Scope
-----
BRL-CAD has two related, but deliberately distinct, ways to represent visual
markup:
* An annotation is an ID_ANNOT database primitive. It is persistent model
content with enough semantic and presentation information to be reproduced
outside the application that created it.
* A view object is application-owned scene state. It is useful for interactive
construction and manipulation, but is intentionally not backed by a database
object.
They share portions of the interactive display infrastructure, but they do not
currently have interchangeable ownership, lifetime, or rendering semantics.
Annotation capabilities
-----------------------
An rt_annot_internal stores a model-space anchor, a set of 2D vertices, and
either a model plane basis or screen-space placement. Its segment types cover
lines, circular arcs, NURBS, Beziers, text, and filled polygons. Segment style
records include a semantic role, line pattern and width, RGBA color, font or
symbol, text scaling, and decorative flags.
The libged annotate command can create styled text, leaders, linear/angular/
radius/diameter/ordinate dimensions, and bounding-box autodim groups. It can
regenerate derived annotations from saved attributes and associate them with
source model objects without making them part of those objects' CSG trees.
Annotations therefore have the properties needed for persistent markup:
* database naming, paths, transforms, and instances;
* survival across save/reopen and transfer to another application;
* semantic segment roles and per-segment presentation data;
* model-space and view-dependent screen-space placement; and
* high-level generation metadata and source-object associations.
An annotation is informational coverage, not a CSG solid. The raytracing
implementation preserves that distinction. It prepares annotation coverage
from active database paths and composites it on primary samples after ordinary
geometry has established color and depth. Model-space annotation coverage is
occluded by nearer solid geometry. Screen-space annotation coverage uses the
render view and image dimensions. Annotations do not acquire artificial
volume, alter model bounds, cast shadows, or participate in reflection and
refraction rays.
Visible coverage is returned in back-to-front layers. Model-space layers are
ordered by ray distance, followed by view-space layers ordered by projected
anchor depth; segment draw order breaks ties within an annotation. This lets
the image renderers apply source-over alpha consistently while retaining the
single-frontmost-hit query for compatibility. NIRT continues to report one
frontmost segment per model-space annotation rather than exposing these
presentation layers as solid partitions.
Color follows normal database tree state. An annotation segment with an
explicit RGBA style wins; otherwise it inherits the resolved combination-path
color; otherwise it is white. The legacy region-ID color table is not applied
to annotation display or coverage, since annotations are informational leaves
rather than regions.
Prepared coverage uses the existing libbg RTree implementation. A 3D RTree
culls model-space annotation instances and a 2D local RTree culls segments
inside every annotation. Screen-space instance extents cannot be placed in a
static world-space index because they change with the view, so those instances
remain in a compact list while still using their local segment RTree. This
avoids a new spatial data structure and keeps prepared scenes immutable and
safe for concurrent sample queries.
The rt, ART, and ADRT/ISST integrations follow this overlay model. ERT invokes
rt with the database, displayed database paths, and view, so database-backed
annotations can follow that route as well. ERT does not currently serialize
or pass application-owned view objects to its subprocess.
NIRT keeps annotation diagnostics opt-in. `useannot 1` enables model-space
annotation intersections, and the `fmt a` action reports them. Default NIRT
output is unchanged. Screen-space annotations are deliberately omitted
because a NIRT ray does not define an image size, pixel footprint, or complete
model-to-view mapping.
View-object capabilities
------------------------
The core representation is bv_scene_obj. A view set owns shared view objects,
and a particular bview can also own local objects. Independent views force
local ownership. bv_obj_put recycles objects when their application lifetime
ends; the objects are not written to the .g database.
The `view obj` command exposes the principal markup types:
* Lines are model-coordinate polylines represented by vlists. Generic scene
rendering can add arrowheads to segment ends.
* Labels have a model anchor and optional model-space target/leader. Their text
is projected and drawn as screen-facing HUD text. If the command omits Z,
screen X/Y coordinates are projected onto the current view plane at creation
time.
* Polygons include general polygons, circles, ellipses, rectangles, and
squares. Creation and editing operate on a saved view plane with a view-Z
offset. Selection, movement, open/close, area, overlap, and 2D boolean
operations are supported. A "fill" is currently a set of hatch lines, not
an opaque surface. Polygons can be explicitly imported from or exported to
persistent sketch primitives.
Scene objects also provide object color, transparency, pixel line width, dash
style, and arrow dimensions. Not all of these properties have a uniform
command interface for every object type.
There are other visual layers that should not be conflated with these objects.
Legacy gv_tcl data/sdata arrows, axes, lines, labels, and polygons have a
separate representation. Faceplate/HUD elements are screen UI. Database
objects temporarily cloned for editing are also transient, but retain database
primitive semantics and live in the database-object scene containers.
Comparison
----------
Both annotations and view objects eventually produce display-manager pixels,
often through bv_scene_obj and vlists. That common final path does not make
their source representations equivalent.
Annotations are the canonical choice when markup must be saved, named in a
database, instanced, shared, raytraced from a .g file, or understood by another
program. View objects are the right choice for application/session state that
must be created and manipulated without changing the database.
Some view objects have a direct annotation analogue, but conversion is not
lossless without type-aware logic:
* A view polyline maps naturally to line segments, including arrowhead style.
* A polygon outline and its hatch lines can map to annotation coverage, but the
existing view "fill" must not silently become an opaque filled polygon.
* A label is hybrid. Its target and leader are model-space geometry while its
glyphs remain screen-facing at a projected model anchor.
* Arbitrary vlist extraction is insufficient because display-matrix commands,
point and line sizes, child objects, glyph behavior, and semantic roles are
otherwise lost or ambiguous.
View-object line width and annotation line width also need an explicit unit
policy. View-object widths are display pixels. Screen-space annotation widths
are view-dependent coverage widths. A model-space annotation's line_width is
used as a scale on the automatically selected model-space stroke width. In
other words, removing a separate raytracer "stroke-width" setting does not
remove width control: the annotation's existing line_width/`--line-width`
style remains the single user-facing control. The renderer interprets it in
the appropriate coordinate system rather than exposing a second competing
knob.
Recommended transient rendering design
--------------------------------------
Do not make librt or the raytracers depend on bv_scene_obj, libbv application
containers, or libged. Do not manufacture tubes, TGCs, meshes, or geometry
just in front of the view plane. Artificial geometry would affect bounds,
depth, shadows, and secondary rays, and would make antialiasing and perspective
behavior depend on a fabricated distance from the eye.
If transient view objects need to appear in raytraced output, use an explicit
application-to-renderer snapshot boundary:
1. The GED/application layer snapshots selected live view objects when a render
is launched. This is the only layer that owns their lifetime and semantics.
2. A type-aware adapter in libged or libbv translates supported objects into a
renderer-neutral overlay/coverage description. It should reuse annotation
coverage preparation through an in-memory annotation-instance interface,
rather than duplicate line, glyph, and arrow intersection algorithms.
3. The snapshot travels with the exact view state: model-to-view transform,
perspective, image width and height, and sampling convention. In-process
renderers can receive it directly; subprocess rt/ERT needs an explicit
serialized side channel.
4. Raytracers composite this coverage only on primary image samples, after
geometry color and depth are known. It remains outside CSG, model bounds,
shadows, reflections, and refractions.
5. Initial support should be deliberately finite: polyline strokes and
arrowheads, label glyphs and leaders, polygon outlines, and polygon hatches.
Faceplate elements and legacy gv_tcl overlays should be separately specified
opt-in layers rather than being included accidentally.
6. Snapshot records should preserve object name and type, RGBA, visibility,
local/shared ownership, presentation units, and source identity. Unsupported
object types should produce a deliberate diagnostic or documented omission.
Application-side compositing of the interactive display-manager layer over an
ERT framebuffer is a viable smaller feature if only ERT screen captures need
view objects. It would closely reproduce the interactive view, but it would
not make the objects available to standalone rt, ART, or ISST output and should
not be presented as the general renderer solution.
Conversion between the two systems should remain explicit. Automatically
turning view objects into database annotations would violate their deliberately
transient contract. A user command to promote selected view markup to a
persistent annotation would be reasonable; polygon-to-sketch export is an
existing precedent. Conversely, a view-object editor may be used as a front
end for constructing an annotation, provided the persistence action is clear.
Maintenance findings before implementing the adapter
----------------------------------------------------
Several representation issues should be corrected before type-driven snapshot
conversion is relied upon:
* Container-selection flags (BV_DB_OBJS, BV_VIEW_OBJS, BV_LOCAL_OBJS, and
BV_CHILD_OBJS) reuse numeric bits also used for semantic s_type_flags
(BV_DBOBJ_BASED, BV_VIEWONLY, BV_LINES, and others). bv_obj_create currently
assigns its type argument to s_type_flags. A local view object consequently
acquires BV_LINES incidentally, while the line command does not explicitly
tag lines. The namespaces should be separated and every object explicitly
typed.
* Lines, labels, and polygons should have unambiguous type tags and per-type
lifecycle/extraction callbacks. Snapshot conversion must inspect typed data,
not infer semantics from a rendered vlist.
* Label construction should initialize every bv_label field deliberately,
including size, line_flag, anchor, arrow, and target. Current construction
does not establish all of those values on every path.
* The status of legacy gv_tcl overlay data should be decided explicitly:
migrate it, support it through a separate adapter, or document its exclusion.
* Screen-space annotation display and raytrace units need a parity test. The
legacy display-matrix vlist path and the ray coverage path currently express
screen offsets through different mechanisms; their apparent agreement must
be verified rather than assumed.
* Per-segment annotation colors exceed the legacy interactive vlist object's
single-color model. Uniform annotate-command colors work today, but true
multicolor interactive parity may require split scene objects or a richer
display representation.
Tests for a future adapter should cover shared and local ownership, cleanup,
label anchors and leaders, polygon holes/hatches/view-Z, perspective, pixel
width, model-space occlusion, screen-space overlay, and serialization across
the ERT subprocess boundary.
Conclusion
----------
Persistent annotations and transient view objects should continue to coexist.
The annotation coverage scene is the correct renderer-side abstraction for
both saved annotations and a future snapshot of transient markup. The
application should perform the semantic conversion and transfer a view-complete
snapshot; the renderer should treat it as primary-ray coverage, never as
invented scene geometry.
+1 -53
View File
@@ -132,10 +132,6 @@ Command to set the backout flag. With no option, prints the current value. When
Sets the _useair_ member of the __rt_i__ structure to the integer _n_. If _n_ is 0, then *nirt* ignores any air in the geometry. If this command is invoked with no arguments, *nirt* prints the current value of _useair_. Default is 0.
*useannot [* _n_*]*::
Enables (_n_=1) or disables (_n_=0) diagnostic annotation intersections. When enabled, *nirt* evaluates all model-space annotations below the active database paths and invokes the *a* output action for each crossing. Screen-space annotations are not evaluated because an individual NIRT ray does not supply a pixel size or model-to-view transform. If this command is invoked with no arguments, *nirt* prints the current value. Default is 0.
*overlap_claims [* _n_*]*::
Specifies how to handle multiple regions' claims to segments of a ray. If _n_ is 0 or "resolve", then all overlaps are resolved in favor of a single region and any other claimants are ignored. If _n_ is 2 or "rebuild_all", then all overlaps are rebuilt, so any overlapping regions along the ray create individual (geometrically intersecting) partitions. If _n_ is 3 or "retain", then all overlaps are retained. In this case, the resulting partitions are always geometrically disjoint, each one is owned by a single region according to the current overlap resolution strategy, but every claimant is recorded. If _n_ is 1 or "rebuild_fastgen", then *nirt* takes on _FASTGEN_ behavior, so overlaps of plate-mode primitives are rebuilt, but other overlaps are retained. This command is useful with the __claimant_count__, __claimant_list__, and __claimant_listn__ output items. Default is "resolve".
@@ -201,11 +197,7 @@ Quits *nirt*.
[[output_statements]]
== OUTPUT STATEMENTS
*nirt* allows the user a high degree of control, via the _fmt_ command, over what information gets printed out for each ray and in what format. There are eight types of output statements, each of which is executed under appropriate circumstances. The types and their use are:
*a*::
Annotation. Executed once for each model-space annotation crossing when *useannot* is enabled. Crossings are reported in ray-distance order after the ordinary solid shotline output. The default format is empty.
*nirt* allows the user a high degree of control, via the _fmt_ command, over what information gets printed out for each ray and in what format. There are six types of output statements, each of which is executed under appropriate circumstances. The types and their use are:
*r*::
@@ -286,41 +278,6 @@ azimuth of view (i.e., of ray direction).
elevation of view (i.e., of ray direction).
Annotation Information
*annot_path*::
full database path of the annotation primitive.
*annot_segment*::
zero-based annotation segment index.
*annot_role*::
semantic role of the annotation segment, such as `text`, `leader`, `dimension`, or `arrowhead`.
*annot_x*::
_x_ coordinate of the annotation hit point.
*annot_y*::
_y_ coordinate of the annotation hit point.
*annot_z*::
_z_ coordinate of the annotation hit point.
*annot_d*::
_d_ grid-plane coordinate of the annotation hit point.
*annot_visible*::
1 if no non-air solid surface is closer to the ray origin, otherwise 0. Occluded annotation crossings are still reported so they can be inspected during debugging.
Partition Information
@@ -564,15 +521,6 @@ nirt -A material_id -A los_density model.g all
Each region the ray passes through is reported together with its _material_id_ and _los_density_ attribute values via the *attributes* output item, which is useful for vulnerability and mass-properties analyses.
====
.Reporting annotation crossings for debugging
====
[source,bash]
----
nirt -s -e 'useannot 1;fmt a "%s %d (%g %g %g) visible=%d\n" annot_path annot_segment annot_x annot_y annot_z annot_visible;s' model.g component dimensions
----
This opt-in format reports every model-space annotation crossing and whether it is in front of the first solid surface. Ordinary NIRT output is unchanged unless *useannot* is enabled and an *a* format is supplied.
====
[[hints]]
== HINTS
+2 -32
View File
@@ -25,7 +25,7 @@ where the "char" argument is replaced by a single character identifying the cate
[[nirt_format_categories]]
== Output Categories
NIRT offers eight categories of output reporting:
NIRT offers seven categories of output reporting:
[cols="3*"]
[%noheader]
@@ -33,9 +33,6 @@ NIRT offers eight categories of output reporting:
|*Category Name*
|*Char*
|*Description*
|*Annotation*
|*a*
|Output for each model-space annotation crossing when annotation reporting is enabled with *useannot 1*. The default annotation format is empty.
|*Ray*
|*r*
|Report of ray information. The formatting associated with r will print once per ray, regardless of whether a region is encountered.
@@ -65,7 +62,7 @@ NIRT offers eight categories of output reporting:
[[nirt_format_str]]
== Format Strings
NIRT's format specifications are a subset of those found in printf implementations. The d, i, f, e, E, g, G, and s types are supported, as well as width and precision specifiers.
NIRT's format specifications are a subset of those found in printf implementations. Specifically, the d, i, and f types are supported, as well as width and precision specifiers.
[[nirt_format_keys]]
@@ -103,33 +100,6 @@ Unlike the full C/C++ printf, NIRT accepts only pre-defined string keys that ide
Footnote and Miss categories have no particular keys unique to them, although they (like all categories) can use the Ray keys if desired. Partition, Overlap, and Gap, on the other hand, have keys that are unique to their contexts and valid only within those contexts:
.Annotation Variables
[cols="2*"]
[%noheader]
|===
|annot_path
|Full database path of the annotation primitive.
|annot_segment
|Zero-based annotation segment index.
|annot_role
|Semantic role of the annotation segment, such as `text`, `leader`, `dimension`, or `arrowhead`.
|annot_x
|x coordinate of the annotation hit point.
|annot_y
|y coordinate of the annotation hit point.
|annot_z
|z coordinate of the annotation hit point.
|annot_d
|d grid-plane coordinate of the annotation hit point.
|annot_visible
|1 if no non-air solid surface is closer to the ray origin, otherwise 0.
|===
Annotation reporting is diagnostic and opt-in. It returns all model-space
annotation crossings in ray-distance order, including occluded crossings.
Screen-space annotations are not reported because a NIRT shot does not define
the pixel dimensions and model-to-view transform needed to evaluate them.
.Partition Variables
[cols="2*"]
[%noheader]
+9 -8
View File
@@ -40,11 +40,12 @@ Coordinates and explicit sizes use the database's current modeling units.
Creation commands draw the new annotation immediately. Use *--no-draw* to
create it without changing the display. Names must not already exist.
Ray-traced annotations are zero-thickness informational coverage rather than
solid regions. Model-space annotations are depth-tested against ordinary
geometry. Screen-space annotations are generated for the current camera and
composited in the foreground; they do not participate in shadows, reflections,
line-of-sight thickness, or Boolean evaluation.
Model-space annotations also participate in raytracing. Librt expands their
strokes and filled areas into transient mesh geometry during preparation; the
generated geometry is not written to the database. It uses the normal region
material and color resolved from the containing combination tree. Screen-space
annotations remain display-only because primitive preparation has no render
view or image dimensions with which to place them.
The supported dimensions follow the general presentation conventions of ISO
129-1. Lettering defaults to OSIFont, an ISO 3098-style technical font shipped
@@ -208,9 +209,9 @@ Set the persistent annotation color using integer red, green, and blue values
from 0 through 255.
*--line-width width*::
Set the presentation line width. Display and screen-space ray rendering use
the value as a pixel width. Model-space ray rendering uses it to scale the
automatic stroke width derived from the annotation's text height or extent.
Set the display line width. For a model-space raytrace, this value scales the
physical stroke width inferred from the annotation's text height or overall
extent.
*--line-style style*::
Set semantic line-pattern metadata to *continuous*, *dashed*, *dotted*,
-46
View File
@@ -227,32 +227,6 @@ struct rt_crofton_params {
double time_ms; /**< wall-clock time budget (ms); 0 = disabled */
};
/** One solid partition observed by a Crofton ray. */
struct rt_crofton_segment {
point_t in_point;
vect_t in_normal;
point_t out_point;
vect_t out_normal;
fastf_t thickness;
size_t ray_id;
};
/**
* Structured samples and estimates produced by rt_crofton_collect().
* Release @p segments with rt_crofton_result_free().
*/
struct rt_crofton_result {
struct rt_crofton_segment *segments;
size_t segment_count;
size_t ray_count;
size_t crossing_count;
double surface_area;
double volume;
};
/** Initialize an rt_crofton_result before first use. */
#define RT_CROFTON_RESULT_INIT { NULL, 0, 0, 0, 0.0, 0.0 }
/**
* Run the Cauchy-Crofton ray-sampling estimator on an already-prepared
@@ -363,26 +337,6 @@ struct rt_crofton_result {
*/
RT_EXPORT extern int rt_crofton_shoot(double *out_surf_area, double *out_volume, point_t *out_aabb_min, point_t *out_aabb_max, point_t out_obb[8], point_t **out_points, size_t *out_point_count, struct rt_i *rtip, const struct rt_crofton_params *params, const fastf_t *bbox_min, const fastf_t *bbox_max);
/**
* Run Crofton sampling while preserving oriented entry/exit pairs.
*
* @p result must be initialized with RT_CROFTON_RESULT_INIT. On success it
* owns the returned segment array. @p rtip must already be prepared.
* @p ray_offset skips that many samples in the deterministic sequence and
* labels the returned rays starting at that value. This lets successive
* calls use disjoint validation streams.
*/
RT_EXPORT extern int rt_crofton_collect(struct rt_crofton_result *result,
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max);
/** Release storage owned by a structured Crofton result. */
RT_EXPORT extern void rt_crofton_result_free(
struct rt_crofton_result *result);
/**
* Cauchy-Crofton surface-area and/or volume estimator for a primitive.
-85
View File
@@ -29,19 +29,10 @@
#include "bu/list.h"
#include "bu/vls.h"
#include "bn/tol.h"
#include "rt/geom.h"
#include "rt/defines.h"
__BEGIN_DECLS
struct db_i;
struct xray;
#define RT_ANNOT_SCREEN_DPI 96.0
#define RT_ANNOT_MILLIMETERS_PER_INCH 25.4
#define RT_ANNOT_SCREEN_PIXELS_PER_MM \
(RT_ANNOT_SCREEN_DPI / RT_ANNOT_MILLIMETERS_PER_INCH)
RT_EXPORT extern struct rt_annot_internal *rt_copy_annot(const struct rt_annot_internal *annot_ip);
/** Validate annotation topology, model-space placement, and optional segment
@@ -49,82 +40,6 @@ RT_EXPORT extern struct rt_annot_internal *rt_copy_annot(const struct rt_annot_i
RT_EXPORT extern int rt_annot_validate(const struct rt_annot_internal *annot_ip,
struct bu_vls *messages);
/** Opaque, immutable collection of prepared annotation instances. */
struct rt_annot_scene;
/** Camera state needed to evaluate view-space annotations. Pixel sample
* coordinates use a lower-left origin. */
struct rt_annot_view {
mat_t model2view;
size_t width;
size_t height;
fastf_t perspective;
};
/** A zero-thickness annotation coverage event. path is owned by the scene. */
struct rt_annot_hit {
const char *path;
size_t segment;
uint32_t role;
point_t point;
fastf_t distance;
unsigned char color[4];
int screen_space;
int visible;
};
/** Prepare annotations referenced by the supplied top-level database paths.
* Boolean operations are intentionally ignored: annotations are
* informational leaves, not solids. */
RT_EXPORT extern struct rt_annot_scene *rt_annot_scene_create(
struct db_i *dbip, int path_count, const char * const *paths,
const struct bg_tess_tol *ttol, const struct bn_tol *tol);
RT_EXPORT extern void rt_annot_scene_destroy(struct rt_annot_scene *scene);
RT_EXPORT extern size_t rt_annot_scene_count(const struct rt_annot_scene *scene);
/** Return model-space bounds for prepared annotations. Screen-space geometry
* contributes its model-space anchor because its extent is view-dependent. */
RT_EXPORT extern int rt_annot_scene_bounds(const struct rt_annot_scene *scene,
point_t minimum, point_t maximum);
/** Return the top visible coverage event for a primary sample. */
RT_EXPORT extern int rt_annot_scene_query(const struct rt_annot_scene *scene,
const struct rt_annot_view *view, const struct xray *ray,
fastf_t sample_x, fastf_t sample_y, fastf_t scene_distance,
struct rt_annot_hit *hit);
/** Return all visible primary-sample coverage events in back-to-front order.
* Model-space events precede screen-space events. When hits is NULL or
* capacity is zero, only the required result count is returned. */
RT_EXPORT extern size_t rt_annot_scene_query_layers(
const struct rt_annot_scene *scene, const struct rt_annot_view *view,
const struct xray *ray, fastf_t sample_x, fastf_t sample_y,
fastf_t scene_distance, struct rt_annot_hit *hits, size_t capacity);
/** Return all model-space coverage events, sorted by ray distance. When hits
* is NULL or capacity is zero, only the required result count is returned. */
RT_EXPORT extern size_t rt_annot_scene_query_model(const struct rt_annot_scene *scene,
const struct xray *ray, fastf_t scene_distance,
struct rt_annot_hit *hits, size_t capacity);
/** Alpha-composite a coverage result over a linear RGB sample. */
RT_EXPORT extern void rt_annot_hit_blend(fastf_t color[3],
const struct rt_annot_hit *hit);
/** Alpha-composite all visible primary-sample coverage over color. If
* front_hit is non-NULL, it receives the frontmost event. */
RT_EXPORT extern int rt_annot_scene_composite(const struct rt_annot_scene *scene,
const struct rt_annot_view *view, const struct xray *ray,
fastf_t sample_x, fastf_t sample_y, fastf_t scene_distance,
fastf_t color[3], struct rt_annot_hit *front_hit);
/** Generate one annotation segment in annotation-local XY coordinates. This
* is shared by plotting and the coverage preparer to keep curve and font
* expansion identical. */
RT_EXPORT extern int rt_annot_segment_vlist(struct bu_list *vlfree,
struct bu_list *vhead, const struct bg_tess_tol *ttol,
const struct rt_annot_internal *annot_ip, size_t segment);
__END_DECLS
/** @} */
-1
View File
@@ -1,6 +1,5 @@
# default.nrt
# Description: default - Standard NIRT Reporting Format
fmt a ""
fmt r "Origin (x y z) = (%.8f %.8f %.8f) (h v d) = (%.4f %.4f %.4f)\nDirection (x y z) = (%.8f %.8f %.8f) (az el) = (%.8f %.8f)\n" x_orig y_orig z_orig h v d_orig x_dir y_dir z_dir a e
fmt h " Region Name Entry (x y z) LOS Obliq_in Attrib\n"
fmt p "%-20s (%9.4f %9.4f %9.4f) %8.4f %8.4f%s\n" reg_name x_in y_in z_in los obliq_in attributes
+2 -3
View File
@@ -53,7 +53,7 @@ set(
)
brlcad_addlib(librender "${LIBRENDER_SRCS}" "" "${ADRT_LOCAL_INCLUDE_DIRS}"
PRIVATE_LIBS libgcv librt ZLIB::ZLIB ${M_LIBRARY}
PRIVATE_LIBS libgcv ZLIB::ZLIB ${M_LIBRARY}
)
set_target_properties(librender PROPERTIES VERSION 20.0.1 SOVERSION 20)
add_dependencies(librender gcv_plugins)
@@ -109,9 +109,8 @@ cmakefiles(isst.c)
add_subdirectory(master)
add_subdirectory(slave)
add_subdirectory(tests)
set(adrt_ignore_files load.h load_g.h tienet.h)
set(adrt_ignore_files load.h tienet.h)
cmakefiles(${adrt_ignore_files})
cmakefiles(CMakeLists.txt)
-8
View File
@@ -90,19 +90,11 @@ enum
#define ADRT_NAME_SIZE 256
/* TIE geometry is normalized from millimeters to meters while loading. */
#define ADRT_MODEL_UNITS_PER_TIE_UNIT 1000.0
#define ADRT_LOAD_FORMAT_G 42
#define ADRT_LOAD_FORMAT_REG 43
#define ADRT_LOAD_FORMAT_KDTREE 44
struct rt_annot_scene;
RENDER_EXPORT extern int load_g(struct tie_s *tie, const char *db, int argc, const char **argv, struct adrt_mesh_s **);
RENDER_EXPORT extern int load_g_annotations(struct tie_s *tie, const char *db,
int argc, const char **argv, struct adrt_mesh_s **,
struct rt_annot_scene **annotations);
#endif
+38 -23
View File
@@ -59,7 +59,6 @@ struct isst_s {
struct render_camera_s camera;
struct camera_tile_s tile;
struct adrt_mesh_s *meshes;
struct rt_annot_scene *annotations;
tienet_buffer_t buffer_image;
int ogl, sflags, w, h, gs, ui;
double dt, fps, uic;
@@ -68,7 +67,6 @@ struct isst_s {
void *texdata;
vect_t camera_pos_init;
vect_t camera_focus_init;
fastf_t scene_radius;
int64_t t1;
int64_t t2;
int dirty;
@@ -121,7 +119,13 @@ resize_isst(struct isst_s *isstp)
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
isstp->texdata = realloc(isstp->texdata, isstp->camera.w * isstp->camera.h * 3);
{
/* realloc through a temporary so a failure does not leak (and null out)
* the existing buffer. */
void *tmp = realloc(isstp->texdata, isstp->camera.w * isstp->camera.h * 3);
if (tmp)
isstp->texdata = tmp;
}
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, isstp->camera.w, isstp->camera.h, 0, GL_RGB, GL_UNSIGNED_BYTE, isstp->texdata);
glDisable(GL_LIGHTING);
@@ -154,14 +158,11 @@ isst_load_g(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc,
argv = (char **)malloc(sizeof(char *) * (strlen(Tcl_GetString(objv[3])) + 1)); /* allocate way too much. */
argc = (int)bu_argv_from_string(argv, strlen(Tcl_GetString(objv[3])), Tcl_GetString(objv[3]));
rt_annot_scene_destroy(isst->annotations);
isst->annotations = NULL;
load_g_annotations(isst->tie, Tcl_GetString(objv[2]), argc,
(const char **)argv, &(isst->meshes), &isst->annotations);
load_g(isst->tie, Tcl_GetString(objv[2]), argc, (const char **)argv, &(isst->meshes));
free(argv);
isst->scene_radius = render_camera_fit_scene(&isst->camera, isst->tie,
isst->annotations, ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSETALL(isst->camera.pos, isst->tie->radius);
VMOVE(isst->camera.focus, isst->tie->mid);
VMOVE(isst->camera_pos_init, isst->camera.pos);
VMOVE(isst->camera_focus_init, isst->camera.focus);
@@ -238,9 +239,7 @@ paint_window(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Ob
isst->buffer_image.ind = 0;
render_camera_prep(&isst->camera);
render_camera_render_annotations(&isst->camera, isst->tie, &isst->tile,
&isst->buffer_image, isst->annotations,
ADRT_MODEL_UNITS_PER_TIE_UNIT);
render_camera_render(&isst->camera, isst->tie, &isst->tile, &isst->buffer_image);
isst->t1 = bu_gettime();
@@ -283,6 +282,7 @@ set_resolution(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_
}
CLAMP(resolution, 1, 20);
/* gs is the render grid size; 0 means render at full window resolution */
if (resolution == 20)
isst->gs = 0;
else
@@ -326,10 +326,13 @@ isst_zap(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *c
return TCL_ERROR;
}
/* Release the buffers owned directly by the workspace before the struct
* itself. (The tie and mesh list have a more involved lifecycle and are
* left for a dedicated teardown.) */
TIENET_BUFFER_FREE(isst->buffer_image);
if (isst->texdata)
free(isst->texdata);
rt_annot_scene_destroy(isst->annotations);
bu_free(isst, "isst free");
isst = NULL;
@@ -351,7 +354,7 @@ render_mode(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj
if (objc == 4)
buf = Tcl_GetString(objv[3]);
/* pack the 'rest' into buf - probably should use a vls for this*/
/* pack the 'rest' into buf - probably should use a vls for this */
if ( strlen(mode) == 3 && bu_strncmp("cut", mode, 3) == 0 ) {
struct adrt_mesh_s *mesh;
@@ -388,22 +391,26 @@ zero_view(ClientData UNUSED(clientData), Tcl_Interp *UNUSED(interp), int UNUSED(
static int
move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj *const *objv)
move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
vect_t vec;
int flag;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "pathName flag");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[2], &flag) != TCL_OK)
return TCL_ERROR;
if (flag >= 0) {
VSUB2(vec, isst->camera.focus, isst->camera.pos);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.focus, vec);
} else {
VSUB2(vec, isst->camera.pos, isst->camera.focus);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.focus, vec);
}
@@ -412,12 +419,16 @@ move_walk(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), T
}
static int
move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj *const *objv)
move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *const *objv)
{
vect_t vec, dir, up;
int flag;
if (objc < 3) {
Tcl_WrongNumArgs(interp, 1, objv, "pathName flag");
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[2], &flag) != TCL_OK)
return TCL_ERROR;
@@ -426,13 +437,13 @@ move_strafe(ClientData UNUSED(clientData), Tcl_Interp *interp, int UNUSED(objc),
if (flag >= 0) {
VSUB2(dir, isst->camera.focus, isst->camera.pos);
VCROSS(vec, dir, up);
VSCALE(vec, vec, 0.1 * isst->scene_radius);
VSCALE(vec, vec, 0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.pos, dir);
} else {
VSUB2(dir, isst->camera.focus, isst->camera.pos);
VCROSS(vec, dir, up);
VSCALE(vec, vec, -0.1 * isst->scene_radius);
VSCALE(vec, vec, -0.1 * isst->tie->radius);
VADD2(isst->camera.pos, isst->camera.pos, vec);
VADD2(isst->camera.focus, isst->camera.pos, dir);
}
@@ -540,10 +551,14 @@ aerotate(ClientData UNUSED(clientData), Tcl_Interp *interp, int objc, Tcl_Obj *c
VSCALE(vecdfoc, vecdfoc, mag_focus);
VADD2(isst->camera.focus, isst->camera_focus_init, vecdfoc);
}
/* Update the tcl copies of the az/el vars */
/* Update the tcl copies of the az/el vars. Store them in the same units
* as isst_load_g (radians via -DEG2RAD); previously aerotate wrote the raw
* degrees from bn_ae_vec, leaving the Tcl vars inconsistent after a drag. */
VSUB2(vec, isst->camera.focus, isst->camera.pos);
VUNITIZE(vec);
bn_ae_vec(&az, &el, vec);
az = az * -DEG2RAD;
el = el * -DEG2RAD;
bu_vls_sprintf(&tclstr, "%f", az);
Tcl_SetVar(interp, "az", bu_vls_addr(&tclstr), 0);
bu_vls_sprintf(&tclstr, "%f", el);
@@ -561,7 +576,7 @@ open_dm(ClientData UNUSED(cdata), Tcl_Interp *interp, int UNUSED(objc), Tcl_Obj
dmp = dm_open(NULL, (void *)interp, dm_default_type(), sizeof(av)/sizeof(void*)-1, (const char **)av);
if (dmp == DM_NULL) {
printf("dm failed?\n");
printf("Failed to open display manager\n");
return TCL_ERROR;
}
@@ -639,7 +654,7 @@ const char *fullname;
argv = __argv;
#endif
/* initialize progname for run-tim resource finding */
/* initialize progname for run-time resource finding */
bu_setprogname(argv[0]);
#ifdef HAVE_WINDOWS_H
+21 -225
View File
@@ -29,7 +29,6 @@
#include "bu/dylib.h"
#include "bu/log.h"
#include "bu/str.h"
#include "rt/primitives/annot.h"
#include "./camera.h"
@@ -44,27 +43,13 @@ struct render_shader_s {
static struct render_shader_s *shaders = NULL;
struct render_camera_annotation_data {
const struct rt_annot_scene *scene;
fastf_t model_units_per_tie_unit;
struct rt_annot_view view;
};
struct render_camera_parallel_data {
render_camera_thread_data_t thread;
struct render_camera_annotation_data annotation;
};
static const fastf_t render_camera_default_scene_radius = 1.0;
/* number of leading characters compared when matching a shader by name */
#define RENDER_SHADER_NAME_CMP_LEN 8
void render_camera_render_thread(int cpu, void *ptr); /* for bu_parallel */
static void render_camera_prep_ortho(render_camera_t *camera);
static void render_camera_prep_persp(render_camera_t *camera);
static void render_camera_prep_persp_dof(render_camera_t *camera);
static void render_camera_annotation_view(const render_camera_t *camera,
fastf_t model_units_per_tie_unit, struct rt_annot_view *view);
static struct render_shader_s *render_shader_register (const char *name, int (*init)(render_t *, const char *));
@@ -408,147 +393,20 @@ render_camera_prep(render_camera_t *camera)
}
fastf_t
render_camera_fit_scene(render_camera_t *camera, const struct tie_s *tie,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
point_t annotation_min, annotation_max;
point_t scene_min, scene_max;
vect_t offset;
fastf_t scene_radius;
if (!camera || !tie)
return 0.0;
if (!annotations || !rt_annot_scene_bounds(annotations,
annotation_min, annotation_max)) {
VSETALL(camera->pos, tie->radius);
VMOVE(camera->focus, tie->mid);
return tie->radius;
}
if (!isfinite(model_units_per_tie_unit) ||
model_units_per_tie_unit <= 0.0)
model_units_per_tie_unit = 1.0;
VSCALE(annotation_min, annotation_min,
1.0 / model_units_per_tie_unit);
VSCALE(annotation_max, annotation_max,
1.0 / model_units_per_tie_unit);
VMOVE(scene_min, annotation_min);
VMOVE(scene_max, annotation_max);
if (tie->tri_num) {
VMINMAX(scene_min, scene_max, tie->amin);
VMINMAX(scene_min, scene_max, tie->amax);
}
VADD2SCALE(camera->focus, scene_min, scene_max, 0.5);
VSUB2(offset, scene_max, scene_min);
scene_radius = 0.5 * MAGNITUDE(offset);
if (!isfinite(scene_radius) || scene_radius <= SMALL_FASTF)
scene_radius = render_camera_default_scene_radius;
/* Keep the established isometric initial direction while positioning it
* relative to the combined scene center. */
VSETALL(offset, scene_radius);
VADD2(camera->pos, camera->focus, offset);
return scene_radius;
}
static void
render_camera_annotation_view(const render_camera_t *camera,
fastf_t model_units_per_tie_unit, struct rt_annot_view *view)
{
vect_t look, up, side, temp, right;
point_t model_position;
fastf_t angle, sine, cosine, xy_scale = 1.0;
mat_t *matrix = &view->model2view;
VSUB2(look, camera->focus, camera->pos);
VUNITIZE(look);
VSET(up, 0.0, 0.0, 1.0);
VMOVE(temp, look);
angle = VDOT(up, temp);
VSCALE(temp, temp, angle);
VSUB2(up, up, temp);
VUNITIZE(up);
VCROSS(side, up, look);
sine = sin(-camera->tilt * DEG2RAD);
cosine = cos(-camera->tilt * DEG2RAD);
VSCALE(up, up, cosine);
VSCALE(side, side, sine);
VADD2(up, up, side);
VCROSS(side, up, look);
VREVERSE(right, side);
MAT_IDN(*matrix);
if (camera->type == RENDER_CAMERA_ORTHOGRAPHIC &&
camera->aspect > 0.0 && camera->gridsize > 0.0) {
xy_scale = 2.0 / (camera->aspect * camera->gridsize *
model_units_per_tie_unit);
} else if (camera->aspect > 0.0) {
xy_scale = 1.0 / camera->aspect;
}
for (size_t axis = 0; axis < 3; ++axis) {
(*matrix)[axis] = right[axis] * xy_scale;
(*matrix)[4 + axis] = up[axis] * xy_scale;
(*matrix)[8 + axis] = -look[axis];
}
VSCALE(model_position, camera->pos, model_units_per_tie_unit);
(*matrix)[3] = -VDOT(&(*matrix)[0], model_position);
(*matrix)[7] = -VDOT(&(*matrix)[4], model_position);
(*matrix)[11] = -VDOT(&(*matrix)[8], model_position);
view->width = camera->w;
view->height = camera->h;
view->perspective =
camera->type == RENDER_CAMERA_PERSPECTIVE ? 2.0 * camera->fov : 0.0;
}
static void
render_camera_composite_annotation(render_camera_thread_data_t *td,
const struct render_camera_annotation_data *annotation,
const struct tie_ray_s *tie_ray, fastf_t sample_x, fastf_t sample_y,
vect_t *pixel)
{
struct rt_annot_hit back_layer = {0};
struct xray ray;
fastf_t scene_distance = INFINITY;
if (!annotation || !annotation->scene)
return;
VSCALE(ray.r_pt, tie_ray->pos, annotation->model_units_per_tie_unit);
VMOVE(ray.r_dir, tie_ray->dir);
if (!rt_annot_scene_query_layers(annotation->scene, &annotation->view,
&ray, sample_x, sample_y, INFINITY, &back_layer, 1))
return;
if (!back_layer.screen_space) {
struct tie_id_s id;
struct tie_ray_s depth_ray = *tie_ray;
if (TIE_WORK(td->tie, &depth_ray, &id, render_hit, NULL))
scene_distance = id.dist * annotation->model_units_per_tie_unit;
}
(void)rt_annot_scene_composite(annotation->scene, &annotation->view,
&ray, sample_x, sample_y, scene_distance, *pixel, NULL);
}
static void
render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *td,
const struct render_camera_annotation_data *annotation)
void
render_camera_render_thread(int UNUSED(cpu), void *ptr)
{
render_camera_thread_data_t *td;
int d, n, res_ind, scanline, v_scanline;
vect_t pixel, accum;
vect_t v1 = VINIT_ZERO;
vect_t v2 = VINIT_ZERO;
struct tie_ray_s ray;
struct tie_ray_s annotation_ray;
fastf_t view_inv;
VSETALL(v1, 0);
td = (render_camera_thread_data_t *)ptr;
view_inv = 1.0 / td->camera->view_num;
td->camera->render.tie = td->tie;
@@ -599,15 +457,9 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VMOVE(ray.pos, td->camera->view_list[d].pos);
ray.depth = 0;
VUNITIZE(ray.dir);
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
render_camera_composite_annotation(td, annotation,
&annotation_ray,
(fastf_t)n + 0.5,
(fastf_t)td->camera->h - (fastf_t)v_scanline - 0.5,
&pixel);
VADD2(accum, accum, pixel);
}
@@ -624,7 +476,6 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VMOVE(ray.pos, td->camera->view_list[0].pos);
ray.depth = 0;
VUNITIZE(ray.dir);
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
@@ -639,19 +490,12 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
VSET(pixel, (TFLOAT)RENDER_CAMERA_BGR, (TFLOAT)RENDER_CAMERA_BGG, (TFLOAT)RENDER_CAMERA_BGB);
ray.depth = 0;
annotation_ray = ray;
/* Compute pixel value using this ray */
td->camera->render.work(&td->camera->render, td->tie, &ray, &pixel);
}
}
if (td->camera->view_num == 1)
render_camera_composite_annotation(td, annotation,
&annotation_ray, (fastf_t)n + 0.5,
(fastf_t)td->camera->h - (fastf_t)v_scanline - 0.5,
&pixel);
if (td->tile->format == RENDER_CAMERA_BIT_DEPTH_24) {
V_MIN(pixel[0], 1);
@@ -679,30 +523,9 @@ render_camera_render_thread_impl(int UNUSED(cpu), render_camera_thread_data_t *t
void
render_camera_render_thread(int cpu, void *ptr)
render_camera_render(render_camera_t *camera, struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result)
{
render_camera_render_thread_impl(cpu, (render_camera_thread_data_t *)ptr,
NULL);
}
static void
render_camera_render_annotations_thread(int cpu, void *ptr)
{
struct render_camera_parallel_data *data =
(struct render_camera_parallel_data *)ptr;
render_camera_render_thread_impl(cpu, &data->thread, &data->annotation);
}
static void
render_camera_render_internal(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
struct render_camera_parallel_data data = {0};
render_camera_thread_data_t td;
unsigned int scanline;
uint32_t ind;
@@ -720,47 +543,19 @@ render_camera_render_internal(render_camera_t *camera, struct tie_s *tie,
TCOPY(camera_tile_t, tile, 0, result->data, result->ind);
result->ind += sizeof(camera_tile_t);
data.thread.tie = tie;
data.thread.camera = camera;
data.thread.tile = tile;
data.thread.res_buf = &((char *)result->data)[result->ind];
td.tie = tie;
td.camera = camera;
td.tile = tile;
td.res_buf = &((char *)result->data)[result->ind];
scanline = 0;
data.thread.scanline = &scanline;
data.thread.sem_tie_worker = bu_semaphore_register("sem_tie_worker");
data.annotation.scene = annotations;
data.annotation.model_units_per_tie_unit =
model_units_per_tie_unit > 0.0 ? model_units_per_tie_unit : 1.0;
if (annotations)
render_camera_annotation_view(camera,
data.annotation.model_units_per_tie_unit, &data.annotation.view);
td.scanline = &scanline;
td.sem_tie_worker = bu_semaphore_register("sem_tie_worker");
if (annotations)
bu_parallel(render_camera_render_annotations_thread,
camera->thread_num, &data);
else
bu_parallel(render_camera_render_thread, camera->thread_num,
&data.thread);
bu_parallel(render_camera_render_thread, camera->thread_num, &td);
result->ind = ind;
}
void
render_camera_render(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result)
{
render_camera_render_internal(camera, tie, tile, result, NULL, 1.0);
}
void
render_camera_render_annotations(render_camera_t *camera, struct tie_s *tie,
camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit)
{
render_camera_render_internal(camera, tie, tile, result, annotations,
model_units_per_tie_unit);
return;
}
@@ -825,9 +620,9 @@ render_shader_unload_plugin(render_t *r, const char *name)
{
#ifdef HAVE_DLFCN_H
struct render_shader_s *t, *s = shaders, *meh;
if (!bu_strncmp(s->name, name, 8)) {
if (!bu_strncmp(s->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
t = s->next;
if (r && r->shader && !bu_strncmp(r->shader, name, 8)) {
if (r && r->shader && !bu_strncmp(r->shader, name, RENDER_SHADER_NAME_CMP_LEN)) {
meh = s->next;
while (meh) {
if (render_shader_init(r, meh->name, NULL) != -1)
@@ -846,7 +641,7 @@ LOADED:
}
while (s->next) {
if (!bu_strncmp(s->next->name, name, 8)) {
if (!bu_strncmp(s->next->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
if (r)
render_shader_init(r, s->name, NULL);
if (s->next->dlh)
@@ -856,6 +651,7 @@ LOADED:
bu_free(t, "unload shader");
return 0;
}
s = s->next;
}
bu_log("Could not find shader \"%s\"\n", name);
@@ -871,7 +667,7 @@ render_shader_init(render_t *r, const char *name, const char *buf)
{
struct render_shader_s *s = shaders;
while (s) {
if (!bu_strncmp(s->name, name, 8)) {
if (!bu_strncmp(s->name, name, RENDER_SHADER_NAME_CMP_LEN)) {
s->init(r, buf);
r->shader = s->name;
return 0;
-11
View File
@@ -99,18 +99,7 @@ __BEGIN_DECLS
RENDER_EXPORT extern void render_camera_init(render_camera_t *camera, size_t threads);
RENDER_EXPORT extern void render_camera_free(render_camera_t *camera);
RENDER_EXPORT extern void render_camera_prep(render_camera_t *camera);
/** Center a camera on TIE geometry and any prepared annotations. Returns the
* effective scene radius in TIE coordinates. */
RENDER_EXPORT extern fastf_t render_camera_fit_scene(render_camera_t *camera,
const struct tie_s *tie, const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit);
RENDER_EXPORT extern void render_camera_render(render_camera_t *camera, struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result);
/** Render with an externally owned annotation scene. The coordinate scale
* converts TIE coordinates to database model coordinates. */
RENDER_EXPORT extern void render_camera_render_annotations(render_camera_t *camera,
struct tie_s *tie, camera_tile_t *tile, tienet_buffer_t *result,
const struct rt_annot_scene *annotations,
fastf_t model_units_per_tie_unit);
RENDER_EXPORT extern int render_shader_init(render_t *, const char *name, const char *buf);
RENDER_EXPORT extern const char *render_shader_load_plugin(const char *filename);
+10 -86
View File
@@ -40,13 +40,11 @@
#include "vmath.h"
#include "nmg.h"
#include "rt/geom.h"
#include "rt/search.h"
#include "raytrace.h"
#include "rt/tie.h"
#include "adrt.h"
#include "adrt_struct.h"
#include "load_g.h"
static struct bn_tol tol; /* calculation tolerance */
@@ -106,42 +104,6 @@ struct gcv_data {
static struct gcv_data gcvwriter = {{load_nmg_to_adrt_gcvwrite, NULL, NULL}, NULL};
int
adrt_path_has_geometry(struct db_i *database, const char *path)
{
static const char *geometry_search = "! -type comb ! -type annot";
struct directory *root;
if (!database || !path)
return 0;
root = db_lookup(database, path, LOOKUP_QUIET);
/* Let db_walk_tree report a missing path rather than hiding the error. */
if (!root)
return 1;
return db_search(NULL, DB_SEARCH_QUIET, geometry_search, 1, &root,
database, NULL, NULL, NULL) != 0;
}
union tree *
adrt_leaf_tess(struct db_tree_state *tsp, const struct db_full_path *pathp,
struct rt_db_internal *ip, void *client_data)
{
union tree *result;
if (!ip || ip->idb_type != ID_ANNOT)
return rt_booltree_leaf_tess(tsp, pathp, ip, client_data);
/* Annotations are rendered directly and have no NMG representation. */
BU_GET(result, union tree);
RT_TREE_INIT(result);
result->tr_op = OP_NOP;
return result;
}
/* load the region into the tie image */
static void
load_nmg_to_adrt_internal(struct adrt_mesh_s *mesh, struct nmgregion *r)
@@ -191,8 +153,7 @@ load_nmg_to_adrt_internal(struct adrt_mesh_s *mesh, struct nmgregion *r)
NMG_CK_VERTEX(v);
/* convert mm to m */
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord,
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord, 1.0/1000.0);
vert_count++;
}
if (vert_count > 3)
@@ -287,12 +248,9 @@ load_nmg_to_adrt_regstart(struct db_tree_state *ts, const struct db_full_path *p
for (i=0;i<bot->num_faces;i++)
{
VSCALE((*tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]), 1.0/1000.0);
VSCALE((*tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]), 1.0/1000.0);
VSCALE((*tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]), 1.0/1000.0);
TIE_VAL(tie_push)(cur_tie, tribuf, 1, mesh, 0);
}
@@ -340,16 +298,11 @@ load_nmg_to_adrt_gcvwrite(struct nmgregion *r, const struct db_full_path *pathp,
int
load_g_annotations(struct tie_s *tie, const char *db, int argc,
const char **argv, struct adrt_mesh_s **meshes,
struct rt_annot_scene **annotations)
load_g(struct tie_s *tie, const char *db, int argc, const char **argv, struct adrt_mesh_s **meshes)
{
struct model *the_model;
struct bg_tess_tol ttol; /* tessellation tolerance in mm */
struct db_tree_state tree_state; /* includes tol & model */
const char **geometry_paths = NULL;
int geometry_count = 0;
int walk_result = 0;
cur_tie = tie; /* NOTE: cur_tie is a file-scope global; this is not
* reentrant and would need locking to be thread-safe. */
@@ -385,21 +338,13 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
if ((dbip = db_open(db, DB_OPEN_READONLY)) == DBI_NULL) {
perror(db);
bu_log("Unable to open geometry database file (%s)\n", db);
nmg_km(the_model);
return -1;
}
if (db_dirbuild(dbip)) {
bu_log("ERROR: db_dirbuild failed\n");
db_close(dbip);
dbip = DBI_NULL;
nmg_km(the_model);
return -1;
}
if (annotations) {
*annotations = rt_annot_scene_create(dbip, argc, argv, &ttol, &tol);
}
BN_CK_TOL(tree_state.ts_tol);
BG_CK_TESS_TOL(tree_state.ts_ttol);
@@ -417,26 +362,15 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
tribuf[1] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
tribuf[2] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
if (argc > 0) {
geometry_paths = (const char **)bu_calloc((size_t)argc,
sizeof(const char *), "ADRT geometry paths");
for (int i = 0; i < argc; ++i) {
if (adrt_path_has_geometry(dbip, argv[i]))
geometry_paths[geometry_count++] = argv[i];
}
}
if (geometry_count) {
walk_result = db_walk_tree(dbip,
geometry_count, /* number of toplevel regions */
geometry_paths, /* region names */
(void) db_walk_tree(dbip,
argc, /* number of toplevel regions */
argv, /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
load_nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
adrt_leaf_tess, /* leaf func */
rt_booltree_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
}
/* Release dynamic storage */
nmg_km(the_model);
@@ -446,20 +380,10 @@ load_g_annotations(struct tie_s *tie, const char *db, int argc,
bu_free(tribuf[1], "vert");
bu_free(tribuf[2], "vert");
bu_free(tribuf, "tri");
if (geometry_paths)
bu_free(geometry_paths, "ADRT geometry paths");
TIE_VAL(tie_prep)(cur_tie);
return walk_result;
}
int
load_g(struct tie_s *tie, const char *db, int argc, const char **argv,
struct adrt_mesh_s **meshes)
{
return load_g_annotations(tie, db, argc, argv, meshes, NULL);
return 0;
}
/*
-43
View File
@@ -1,43 +0,0 @@
/* L O A D _ G . H
* BRL-CAD / ADRT
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef ADRT_LOAD_G_H
#define ADRT_LOAD_G_H
#include "common.h"
#include "adrt.h"
struct db_i;
struct db_tree_state;
struct db_full_path;
struct rt_db_internal;
union tree;
__BEGIN_DECLS
RENDER_EXPORT extern int adrt_path_has_geometry(struct db_i *dbip,
const char *path);
RENDER_EXPORT extern union tree *adrt_leaf_tess(struct db_tree_state *tsp,
const struct db_full_path *pathp, struct rt_db_internal *ip,
void *client_data);
__END_DECLS
#endif /* ADRT_LOAD_G_H */
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
-16
View File
@@ -1,16 +0,0 @@
brlcad_addexec(adrt_annotation_test annotation.c
"librender;libwdb;librt;libbu;${M_LIBRARY}" TEST)
target_include_directories(adrt_annotation_test PRIVATE
${BRLCAD_SOURCE_DIR}/src/adrt
${BRLCAD_SOURCE_DIR}/src/adrt/librender
)
brlcad_add_test(NAME adrt_annotation_test COMMAND adrt_annotation_test)
cmakefiles(annotation.c CMakeLists.txt)
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
-179
View File
@@ -1,179 +0,0 @@
/* A N N O T A T I O N . C
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by
* the U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "common.h"
#include <math.h>
#include <stdio.h>
#include "bu/app.h"
#include "bu/file.h"
#include "raytrace.h"
#include "rt/primitives/annot.h"
#include "wdb.h"
#include "load_g.h"
#include "librender/camera.h"
static void
write_test_database(struct db_i *dbip)
{
struct rt_wdb *wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DISK);
struct rt_annot_internal annot = {0};
point2d_t vertices[2];
struct line_seg line = {0};
void *segments[1];
int reverse[1] = {0};
point_t center = VINIT_ZERO;
struct wmember members;
if (wdbp == RT_WDB_NULL)
bu_exit(1, "unable to open ADRT annotation test database\n");
annot.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(annot.V, 10.0, 0.0, 0.0);
annot.flags = RT_ANNOT_MODEL_SPACE;
VSET(annot.u_vec, 0.0, 1.0, 0.0);
VSET(annot.v_vec, 0.0, 0.0, 1.0);
annot.vert_count = 2;
annot.verts = vertices;
V2SET(vertices[0], -1.0, 0.0);
V2SET(vertices[1], 1.0, 0.0);
annot.ant.count = 1;
annot.ant.reverse = reverse;
annot.ant.segments = segments;
line.magic = CURVE_LSEG_MAGIC;
line.start = 0;
line.end = 1;
segments[0] = &line;
if (rt_annot_validate(&annot, NULL) ||
mk_annot(wdbp, "test.annot", &annot) < 0 ||
mk_sph(wdbp, "test.sph", center, 1.0) < 0)
bu_exit(1, "unable to write ADRT annotation test objects\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("test.annot", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "annotation-only.c", &members, 0, NULL, NULL,
NULL, 0))
bu_exit(1, "unable to write annotation-only test combination\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("test.annot", &members.l, NULL, WMOP_UNION) ||
!mk_addmember("test.sph", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "mixed.c", &members, 0, NULL, NULL, NULL, 0))
bu_exit(1, "unable to write mixed test combination\n");
}
static void
check_geometry_filter(struct db_i *dbip)
{
struct rt_db_internal intern;
union tree *leaf;
if (adrt_path_has_geometry(dbip, "test.annot") ||
adrt_path_has_geometry(dbip, "annotation-only.c") ||
!adrt_path_has_geometry(dbip, "test.sph") ||
!adrt_path_has_geometry(dbip, "mixed.c"))
bu_exit(1, "ADRT annotation geometry filtering failed\n");
RT_DB_INTERNAL_INIT(&intern);
intern.idb_type = ID_ANNOT;
leaf = adrt_leaf_tess(NULL, NULL, &intern, NULL);
if (!leaf || leaf->tr_op != OP_NOP)
bu_exit(1, "ADRT annotation leaf did not become a no-op\n");
db_free_tree(leaf);
}
static void
check_camera_fit(struct db_i *dbip)
{
const char *paths[] = {"test.annot"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
render_camera_t camera = {0};
struct tie_s tie = {0};
fastf_t radius;
const fastf_t combined_radius = 0.5 * sqrt(41.0);
if (!scene || rt_annot_scene_count(scene) != 1)
bu_exit(1, "unable to prepare ADRT annotation test scene\n");
tie.radius = 2.0;
VSET(tie.mid, 1.0, 2.0, 3.0);
radius = render_camera_fit_scene(&camera, &tie, NULL, 1.0);
if (!NEAR_EQUAL(radius, 2.0, BN_TOL_DIST) ||
!VNEAR_EQUAL(camera.focus, tie.mid, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[X], 2.0, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Y], 2.0, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Z], 2.0, BN_TOL_DIST))
bu_exit(1, "ADRT geometry-only camera framing changed\n");
radius = render_camera_fit_scene(&camera, &tie, scene, 10.0);
if (radius <= SMALL_FASTF ||
!NEAR_EQUAL(camera.focus[X], 1.0, BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Y], BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Z], BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[X] - camera.focus[X], radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Y] - camera.focus[Y], radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.pos[Z] - camera.focus[Z], radius, BN_TOL_DIST))
bu_exit(1, "ADRT annotation-only camera framing failed: radius=%g "
"focus=(%g %g %g) pos=(%g %g %g)\n", radius,
V3ARGS(camera.focus), V3ARGS(camera.pos));
tie.tri_num = 1;
VSET(tie.amin, -2.0, -2.0, -2.0);
VSET(tie.amax, 0.0, 2.0, 2.0);
radius = render_camera_fit_scene(&camera, &tie, scene, 10.0);
if (!NEAR_EQUAL(radius, combined_radius, BN_TOL_DIST) ||
!NEAR_EQUAL(camera.focus[X], -0.5, BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Y], BN_TOL_DIST) ||
!NEAR_ZERO(camera.focus[Z], BN_TOL_DIST))
bu_exit(1, "ADRT combined geometry and annotation framing failed\n");
rt_annot_scene_destroy(scene);
}
int
main(int argc, char **argv)
{
char path[MAXPATHLEN];
struct db_i *dbip;
bu_setprogname(argv[0]);
if (argc != 1)
return 1;
{
FILE *fp = bu_temp_file(path, MAXPATHLEN);
if (!fp)
bu_exit(1, "unable to create ADRT annotation test path\n");
fclose(fp);
}
dbip = db_create(path, 5);
if (dbip == DBI_NULL)
bu_exit(1, "unable to create ADRT annotation test database\n");
write_test_database(dbip);
check_geometry_filter(dbip);
check_camera_fit(dbip);
db_close(dbip);
bu_file_delete(path);
return 0;
}
/*
* Local Variables:
* tab-width: 8
* mode: C
* indent-tabs-mode: t
* c-file-style: "stroustrup"
* End:
* ex: shiftwidth=4 tabstop=8
*/
+480 -399
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -64,17 +64,16 @@ ArtTileCallback::on_tile_end(const renderer::Frame* frame, const size_t tile_x,
{
foundation::Tile& t = frame->image().tile(tile_x, tile_y);
const foundation::Tile rgb(t, PixelFormatUInt8);
const foundation::CanvasProperties& properties = frame->image().properties();
// fb_write(fbp, tile_x, tile_y, rgb.get_storage(), rgb.get_size());
// printf("yay!\n");
// printf("%lu %lu \n", tile_x, tile_y);
// printf("%lu %lu \n", rgb.get_width(), rgb.get_height());
for (size_t y = 0; y < rgb.get_height(); y++) {
for (size_t x = 0; x < rgb.get_width(); x++) {
size_t x_coord = tile_x * properties.m_tile_width + x;
size_t y_coord = tile_y * properties.m_tile_height + y;
size_t img_h = properties.m_canvas_height;
fb_write(fbp, (int)x_coord, (int)(img_h - y_coord - 1), rgb.get_storage()+((y * rgb.get_width() * 4) + (x * 4)), 1);
size_t x_coord = tile_x * rgb.get_width() + x;
size_t y_coord = tile_y * rgb.get_height() + y;
size_t img_h = frame->image().properties().m_canvas_height;
fb_write(fbp, (int)x_coord, (int)(img_h - y_coord), rgb.get_storage()+((y * rgb.get_width() * 4) + (x * 4)), 1);
}
}
}
+4 -2
View File
@@ -17,8 +17,10 @@ brlcad_addexec(asc-nmg nmg/asc-nmg.c "libwdb;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(bot_dump bot_dump.c "libged;librt" FOLDER Conv)
verbose_add_subdirectory("src/conv" comgeom)
set(
comgeom-g_SRCS
comgeom-g_old_SRCS
comgeom/cvt.c
comgeom/f2a.c
comgeom/mat.c
@@ -27,7 +29,7 @@ set(
comgeom/solid.c
comgeom/tools.c
)
brlcad_addexec(comgeom-g "${comgeom-g_SRCS}" "libwdb;libbn;libbu;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(comgeom-g_old "${comgeom-g_old_SRCS}" "libwdb;libbn;libbu;${M_LIBRARY}" FOLDER Conv)
brlcad_addexec(conv-vg2g conv-vg2g.c "libbu" FOLDER Conv)
+28
View File
@@ -0,0 +1,28 @@
set(comgeom_srcs
ComGeomConverter.cpp
ComGeomReader.cpp
RegionManager.cpp
SolidParser.cpp
main.cpp
)
brlcad_addexec(comgeom-g "${comgeom_srcs}" "libwdb;librt;libnmg;libbu" FOLDER Conv)
target_include_directories(comgeom-g BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
cmakefiles(
CMakeLists.txt
COMGEOM_V1.txt
COMGEOM_versions.txt
ComGeomConverter.h
ComGeomReader.h
RegionManager.h
SolidParser.h
bot_to_arbn_pipeline_thoughts.txt
)
# Local Variables:
# tab-width: 8
# mode: cmake
# indent-tabs-mode: t
# End:
# ex: shiftwidth=2 tabstop=8
+498
View File
@@ -0,0 +1,498 @@
COM-GEOM v1 FORMAT DEFINITION FOR THE GIFT CODE
Based on BRL 1975 Manuals (Vol. I: BRL Report 1802; Vol. II: ARBRL-TR-02189)
---------------------------------------------------------------------
SECTION 0. GENERAL PRINCIPLES
1. Coordinate System
- Right-handed Cartesian system (X,Y,Z).
- Origin is user-defined (e.g., turret datum intersection for a tank).
- All dimensional values (coordinates, lengths, radii) use a single unit family
declared on the Title Card (columns 79-80): IN, FT, CM, M, or MM (default
IN).
2. Data Organization (Target Description)
A COM-GEOM data set consists of (in order):
1) Title Card
2) Target Specification Card
3) Solid Table (one or more cards per solid)
4) Region Table (one or more cards per region; ends with "-1" sentinel card)
5) (Optional) Region RPP Table
6) Region Identification Table
7) Program Option / Control Cards (for GIFT processing and output runs)
3. Numeric Formats
Unless otherwise noted:
- Integers are right- or left-adjusted in their column ranges.
- Floating point fields: F10.5 (classic) or F10.n where width 10 columns,
decimal point explicit.
- Blank where a "+" intersection operator is implied.
4. Combination Logic (Region Construction)
Given solids S1, S2 ...
- Intersection: adjacency (implicit "+"). Example: 1 2 3 means S1 intersect S2
intersect S3.
- Subtraction: prefix "-" before a solid number: ... -7 means subtract volume
of S7 from accumulated set.
- Union: keyword OR splits disjoint boolean branches: BranchA OR BranchB.
- Dummy region: region card with only region number and no solids (assigned
special identification code 111).
- Recommended practice (per BRL guidance): Avoid OR on Region cards; instead
assign identical identification (Item) codes to multiple simple regions.
5. Air Space and Items
Each region is either:
- Item (component) region: has item code (e.g., armor plate, wheel).
- Air region: has air space code (e.g., crew compartment air).
Rules:
- Item regions must not overlap (beyond TOL).
- Air regions may overlap other air regions with same air code.
- Air regions must NOT overlap air regions with different air codes (beyond
TOL).
- Air regions may overlap item regions (they represent enclosing void).
6. Tolerances (Control Card)
- Overlap tolerance TOL: maximum allowable spatial overlap distance between
regions before flagged as error.
- Line-of-sight gap tolerance TOLLOS: maximum axial gap along a ray ignored
when assembling ray intersection chains.
7. End Sentinel & Codes
- Void Space Code: numeric code inserted where no component.
- Phantom Item Code: code assigned to "phantom armor" or shielding constructs
generated during checking.
- End-of-ray Signal Code: code marking termination of component stack for a ray.
8. Storage Model
MASTER-ASTER equivalence array holds all processed geometry. Size NDQ must be
large enough; formula given (Section 11 of Vol II) reproduced below (see
Section 7).
---------------------------------------------------------------------
SECTION 1. TITLE CARD
Columns (1-80):
1-60: Free text title (target name/description).
61-78: (Usually blank or ancillary description).
79-80: Units code (IN, FT, CM, M, MM). If blank defaults to IN.
Example:
SAMPLE LIGHT ARMORED CAR WITH DRIVER IN
---------------------------------------------------------------------
SECTION 2. TARGET SPECIFICATION CARD
Format: (Counts of solids / regions / surrounding RPPs)
Columns (fixed interpretation from manuals):
1-10 Number of Solids (NSOLID)
11-20 Number of Regions (NREGION, expected or max)
21-30 Number of Surrounding RPPs (if used) (NRPP) or MAX Regions (context dependent)
31-40 MAX pointers or storage (implementation field) - historically used internally
Remaining columns may be reserved or blank.
If a dedicated second card exists (variant implementations) it repeats extended capacity fields.
Example:
20 20 1
---------------------------------------------------------------------
SECTION 3. SOLID TABLE
Each solid has a unique consecutive integer ID starting at 1.
GENERAL CARD FORMAT (all solids):
Columns:
1-3 Solid Number (integer)
4-6 Solid Symbol (3 chars): RPP BOX RAW ARB ARS ELL SPH RCC REC TRC TEC TOR
7 Variant / Vertex Count / Option flag (for ARB, ARS, ELL1) else blank
11-70 Numeric fields (F10.5 blocks)
71-80 Comment (recommended: name or purpose)
3.1 Rectangular Parallelepiped (RPP)
No explicit vertex; define bounding box aligned to global axes.
Fields (F10.5 each):
XMIN, XMAX, YMIN, YMAX, ZMIN, ZMAX.
Example:
6 RPP 20.0000 23.0000 -10.0000 10.0000 28.0000 46.0000 BODY
3.2 BOX (Arbitrarily oriented)
Vertex V and three mutually perpendicular edge vectors H, W, D.
Fields:
Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
Example:
1 BOX 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 0.0000 0.0000 11.0000 0.0000 0.0000 STEERING WHEEL
3.3 RAW (Right Angle Wedge)
Vertex V; two perpendicular legs H,W in wedge plane; depth D.
Fields:
Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
Example:
12 RAW -75.0000 -36.0000 12.0000 60.0000 12.0000 0.0000 60.0000 12.0000 0.0000 -60.0000 12.0000 0.0000 FENDER
3.4 ARB (Convex Polyhedron) - ARB4 / ARB5 / ARB6 / ARB7 / ARB8
Column 7 = number of vertices (4-8).
Card sequence: Enough cards to list all vertices (each vertex: X Y Z).
One vertex triple per F10.5 block; continue across cards.
Face set is internally inferred by GIFT according to vertex count pattern (see manual).
Example ARB5 (two cards if needed):
3 ARB5 33.5000 37.0000 12.0000 46.0000 -75.0000 -75.0000 20.0000 50.0000 0.0000 ...
(continuation)
3.5 ARS (Triangular Surfaced Polyhedron)
Column 7: M (number of curves)
Column 11-20: N (points per curve)
Then M x N vertex points laid out curve by curve (each curve on one or more cards).
Repeated vertices allowed; triangles formed between successive curves (see Vol I Fig. 13).
Example:
11 ARS7 (points...) (comments)
3.6 ELL / ELL1 (Ellipsoid of Revolution)
Two input modes:
(a) ELL: Foci F1, F2 and major axis length L.
Fields: F1x F1y F1z F2x F2y F2z L
(b) ELL1 (Column 7 = 1): Center V, semi-major axis vector A, radius R of equatorial circle.
Fields: Vx Vy Vz Ax Ay Az R
Example:
14 ELL1 24.0000 52.0000 0.0000 35.0000 0.0000 20.0000 7.5000 DRIVER CHEST
3.7 SPH (Sphere)
Center V and radius R.
Fields: Vx Vy Vz R
Example:
15 SPH 45.0000 -30.0000 -30.0000 7.5000 HEAD
3.8 RCC (Right Circular Cylinder)
Center of one circular base (vertex V), height vector H, radius R.
Fields: Vx Vy Vz Hx Hy Hz R
Example:
16 RCC -30.0000 -70.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 LEG
3.9 REC (Right Elliptical Cylinder)
Center V, height vector H, semi-major axis vector A, semi-minor axis vector B (both in base plane).
Fields: Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz
Example:
17 REC -30.0000 -30.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 13.0000 0.0000 5.0000 0.0000 0.0000 ARM
3.10 TRC (Truncated Circular Cone)
Base center V, height vector H, base radius R1, top radius R2.
Fields: Vx Vy Vz Hx Hy Hz R1 R2
Example:
18 TRC -30.0000 -30.0000 -70.0000 48.0000 24.0000 0.0000 13.0000 9.0000 TORSO LOWER
3.11 TEC (Truncated Elliptic Cone)
Base center V, height vector H, base semi-major A, base semi-minor B, ratio P (BaseEllipse / TopEllipse).
Fields: Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz P
Example:
19 TEC -30.0000 -70.0000 0.0000 48.0000 24.0000 0.0000 24.0000 0.0000 20.0000 12.0000 0.0000 10.0000 1.5000
3.12 TOR (Torus)
Center V, normal vector N (to torus plane), major radius R1 (centerline), tube radius R2.
Fields: Vx Vy Vz Nx Ny Nz R1 R2
Example:
20 TOR 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 11.0000 STEERING RIM
---------------------------------------------------------------------
SECTION 4. REGION TABLE
Card Format (per region):
Columns:
1-2 Blank (recommended)
3-5 Region Number (integer, consecutive starting at 1)
6 Blank
7-8 "OR" if union logic is used (introductory OR for region; optional but required if any OR appears later)
9-69 Sequence of signed solid references (2-3 digit solid numbers with optional leading minus sign; blanks imply "+" intersection). Space or "OR" may appear to break into disjoint branches.
70-80 Comment (e.g., semantic label, region definition subset index)
Grammar rules:
- Intersection: adjacency (implicit +).
- Subtraction: a leading "-" before the solid number.
- Union: OR separating disjoint boolean expressions.
- End of Region Table: card with region number -1 in columns 4-5 (" -1" Card).
Example (two-card region expression 1 + 2 - 3 OR 4 + 5 - 6):
Card 1:
___111 OR 1 2 -3 4
Card 2:
___111 5 -6
Dummy region (no solids):
___012 (columns 9-69 blank) (Region 12 dummy)
Sentinel:
____-1
Best Practice:
Prefer multiple simple non-OR regions sharing a common identification code (see Section 5) over complex OR expressions.
---------------------------------------------------------------------
SECTION 5. REGION IDENTIFICATION TABLE
Each card assigns either an Item Code (component) or Air Space Code to a region.
Format:
Columns:
1-2 Blank
3-5 Region Number
6-10 Blank or separator
11-15 Item or Air Space Code (integer)
16-69 Optional additional codes or reserved blanks
70-80 Comment (verbal description + region formula)
Special Codes:
- 01 Exterior air / Surrounding void (example usage; manuals show 02,03,05 for interior categories).
- 02 Crew compartment air
- 03 Passenger compartment air
- 05 Engine compartment air
- 09 (DO NOT USE) reserved
- 111 Dummy region code
- 501 Track region (may be remapped to 502 for track edge by GIFT processing)
- 999 Forbidden as a component code
Item Code Ranges (per Table 3 of manual):
1- 99 Personnel & miscellaneous interior
100-199 Armor & structural
200-299 Powertrain / engine group
300-399 Suspension / drive
400-499 Mobility auxiliaries
500-599 Track / running gear (501 special)
600-699 Armament
700-799 Fire control
800-899 Communications / electronics
900-998 Ammunition
If a region is air: its code is an air space code; otherwise an item code.
Example:
__001 40 STEERING WHEEL 1-2
__002 100 STEERING SHAFT 2
__012 111 DUMMY
__019 02 INTERIOR AIR 19
__020 02 INTERIOR AIR 20
---------------------------------------------------------------------
SECTION 6. REGION RPP TABLE (OPTIONAL)
Purpose: Override automatically computed bounding boxes (per region).
Card Format:
Columns:
1-10 Region Number
11-20 XMIN
21-30 XMAX
31-40 YMIN
41-50 YMAX
51-60 ZMIN
61-70 ZMAX
71-80 Comment
Omit dummy regions (those with identification code 111).
If absent for a region, GIFT computes bounding RPP automatically.
Example:
1 20.0000 23.0000 -10.0000 10.0000 28.0000 46.0000
2 15.0000 21.5000 -11.0000 11.0000 28.0000 46.0000
---------------------------------------------------------------------
SECTION 7. STORAGE FORMULA (MASTER-ASTER SIZE NDQ)
Minimum NDQ (memory words) must satisfy largest of:
1) LDES - 1
2) 6 * (NSOLID) + LREGON - 1
3) K * (NREGION) + LRIN - 1 (K = 5 CDC, 8 UNIVAC, 11 IBM)
Plus if alphanumeric region descriptions stored:
NDQ >= LDES + (K - 1) * NREGION - 1
Detailed formula from Volume II (for precise sizing):
TOTAL STORAGE =
4 * (#Solids)
+ 7 * (#RCC)
+12 * (#REC)
+ 8 * (#TRC)
+15 * (#TEC)
+ 4 * (#SPH)
+12 * (#ELL/ELL1)
+11 * (#TOR)
+ 6 * (#RPP)
+15 * (#BOX)
+12 * (#RAW)
+ (ARB Storage)
+ (Non-convex ARS Storage)
+ (Convex ARS Storage)
+14 * (#Regions)
+ (#Region Descriptors)
+ 6
+ (Alphanumeric Storage optional)
ARB Storage per ARB:
4 * (#faces) + 1
Non-convex ARS:
88 + 4 * (L + 2 * N * M)
where L = #points read directly
M = #curves
N = #points per curve
Convex ARS:
4 * (#non-degenerate faces) + 1
Region Descriptors:
Count of non-zero entries in columns 9-69 of Region Table.
Alphanumeric storage per region:
CDC=4, UNIVAC=7, IBM=10 words.
---------------------------------------------------------------------
SECTION 8. CONTROL (PROGRAM OPTION) CARD (GIFT EXECUTION)
Columns (classic Control Card from Vol II):
1 Use processed geometry from binary Unit 4 (0=no, non-zero=yes)
2 Write processed geometry to binary Unit 4 (0=no, non-zero=yes)
3-5 FORTRAN unit number for raw COM-GEOM input (0 or 5 = Unit 5)
6 Print MASTER-ASTER array (debug) if non-zero
7 Print Ordered Region Identification Table if non-zero
8 Print Ordered Region RPP Equivalent Table if non-zero
9 Print Solid RPP Equivalent Table if non-zero
10 Suppress printing of Solid/Region tables if non-zero (No Print)
11-30 Reserved / (legacy fields)
31-40 Overlap tolerance TOL (F10)
41-50 Line-of-sight gap tolerance TOLLOS (F10)
51-55 Void Space Code (integer; default 1 if 0)
56-60 Phantom Item Code (default 111 if 0)
61-65 End-of-ray Signal Code (default 9 if 0)
66-80 Comments
Example:
0 1 5 0 1 1 0 0.010000 0.010000 1 111 9 RUN A
---------------------------------------------------------------------
SECTION 9. VALIDATION & RULES SUMMARY
1) Solid Numbers: Consecutive starting at 1. No gaps.
2) Region Numbers: Consecutive starting at 1; end sentinel: region -1 card.
3) Dummy Regions: Allowed; must carry identification code 111; no solids.
4) Item Regions: Must not overlap beyond TOL.
5) Air Regions: May overlap same air code; not different air codes beyond TOL.
6) Negative Descriptors: Subtraction only applies to immediately preceding accumulated set in branch.
7) OR Branches: Disjoint boolean unions; minimized use recommended.
8) Tolerances: Set to reflect modeling precision; typical TOL=TOLLOS=0.01 in same units.
9) Special Codes: Avoid 999, 09, ensure proper use of 111, 501/502.
---------------------------------------------------------------------
SECTION 10. MINIMAL WORKED EXAMPLE
Title:
SAMPLE LIGHT VEHICLE WITH DRIVER IN
Target Specification:
20 20 1
Solid (excerpt):
1 BOX 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 0.0000 0.0000 11.0000 0.0000 0.0000 STEERING WHEEL
2 TOR 21.5000 37.0000 0.0000 0.0000 45.0000 11.0000 40.0000 11.0000 RIM
3 SPH 33.5000 37.0000 12.0000 7.5000 HEAD
...
20 RCC -30.0000 -70.0000 -30.0000 48.0000 24.0000 0.0000 13.0000 LEG
Region Table (excerpt):
1 1 2 (STEERING WHEEL ASSEMBLY: BOX intersect TOR)
2 3 (HEAD)
3 4 -5 6 (ARM: Intersection of solids 4 & 6 minus 5)
...
12 (Dummy)
...
End: -1
Region Identification:
1 40 STEERING WHEEL 1 2
2 100 STEERING SHAFT 3
3 100 ARM (UPPER) 4 -5 6
12 111 DUMMY
19 02 INTERIOR AIR
20 02 INTERIOR AIR
Region RPP (optional):
1 20.0 23.0 -10.0 10.0 28.0 46.0
2 32.0 37.5 -11.0 11.0 28.0 46.0
...
Control Card:
0 1 5 0 1 0 0 0 0.010000 0.010000 1 111 9 STD RUN
---------------------------------------------------------------------
SECTION 11. COMMON PITFALLS AND RECOMMENDATIONS
1) Overlapping Items: Resolve by subtracting one from the other or adjusting tolerance; large overlaps are modeling errors, not tolerance issues.
2) Excessive OR Usage: Replace with multiple regions sharing identification codes to simplify ray tracing and checking.
3) Insufficient NDQ: Use storage formula; double NDQ if "NO MORE ROOM" messages appear after >75% of Solid Table processed.
4) Dummy Regions: Keep for numbering alignment only; always code 111.
5) Precision: If coordinate acquisition is from drawings with limited precision, set TOL/TOLLOS to at least the drawing resolution.
6) ARS Conversions: For legacy MAGIC/SHOTGEN conversions using ARS, verify point ordering and duplication to preserve intended triangular mesh.
7) TEC Ratio P: Ensure P > 1; if P < 1 code will treat cone as inverted (swap top/base); better to input consistent orientation.
---------------------------------------------------------------------
SECTION 12. DIFFERENCES VS. OTHER ERA GEOMETRY FORMATS
MAGIC / SHOTGEN vs COM-GEOM v1:
- COM-GEOM v1 uses boolean combinatorial solids with explicit card columns.
- Emphasizes minimal OR, encourages identification grouping.
- Tolerances (TOL/TOLLOS) user-adjustable on Control Card (MAGIC earlier versions required source edits).
- Storage formulas explicitly published; user responsible for NDQ sizing (contrast with more opaque internal allocation in other codes).
---------------------------------------------------------------------
SECTION 13. QUICK REFERENCE TABLES
13.1 Solid Symbols and Required Fields
Symbol | Type | Fields (Order)
RPP | Axis-aligned box | XMIN XMAX YMIN YMAX ZMIN ZMAX
BOX | Oriented box | Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
RAW | Right wedge | Vx Vy Vz Hx Hy Hz Wx Wy Wz Dx Dy Dz
ARB4-8 | Convex polyhedron | Vertex triples (count in col 7)
ARS | Triangulated polyhedron | M, N then M x N vertex triples
ELL | Ellipsoid (foci) | F1x F1y F1z F2x F2y F2z L
ELL1 | Ellipsoid (axis) | Vx Vy Vz Ax Ay Az R (col 7=1)
SPH | Sphere | Vx Vy Vz R
RCC | Right circular cylinder | Vx Vy Vz Hx Hy Hz R
REC | Right elliptical cylinder | Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz
TRC | Truncated circular cone | Vx Vy Vz Hx Hy Hz R1 R2
TEC | Truncated elliptic cone | Vx Vy Vz Hx Hy Hz Ax Ay Az Bx By Bz P
TOR | Torus | Vx Vy Vz Nx Ny Nz R1 R2
13.2 Special Identification Codes
111 Dummy region
501 Track (may become 502)
01,02,03,05 Air categories (examples)
999 Forbidden component code
09 Forbidden air code
---------------------------------------------------------------------
SECTION 14. EXTENSIBILITY NOTES
To add new solid types (historical practice for later internal variants):
- Reserve a new 3-char symbol in columns 4-6.
- Define fixed F10.5 field sequence and document vertex or vector semantics.
- Update MASTER-ASTER storage calculator with new per-solid word count.
- Maintain backward compatibility by keeping existing card column positions.
---------------------------------------------------------------------
SECTION 15. VALIDATION CHECKLIST
Before running GIFT:
[ ] Title card unit code set.
[ ] NSOLID equals number of solid definitions.
[ ] NREGION equals number of region definitions (excluding -1 sentinel).
[ ] Solid numbers consecutive from 1.
[ ] Region numbers consecutive from 1; -1 end card present.
[ ] Region Identification codes valid (no 999, no 09).
[ ] Dummy regions have 111 code and no solids.
[ ] NDQ sized using formula (or rule of thumb 45*NSOLID + margin).
[ ] TOL/TOLLOS appropriate for measurement precision.
[ ] Void / Phantom / End-of-ray codes non-zero or rely on defaults (1,111,9).
[ ] Optional Region RPP Table consistent (no dummy region entries).
[ ] No unintended overlaps (test with CHECK option).
[ ] Use CHECK / TESTG GIFT options for geometry validation before production runs.
---------------------------------------------------------------------
End of COM-GEOM v1 ASCII specification.
+350
View File
@@ -0,0 +1,350 @@
# COMGEOM Versions (1967–present)
This is a survey of the COMGEOM geometry input language across its lifecycle,
from the earliest MAGI/MAGIC card decks through the stable v1 and the tokenized
v4/v5 formats. It highlights what changed between versions, when those changes
occurred, and provides practical guidance for recognizing and migrating old
decks.
Quick summary: v0 (pre‑v1) was the early MAGIC card style with implicit
intersections and “OR” unions; v1 formalized strict punched‑card columns; v2/v3
were transitional internal experiments (partial free‑field parsing,
parentheses, generalized cones); v4 is the fully tokenized, parenthesized
grammar; v5 added attribute/metadata lines atop v4.
---
## Timeline
- 1967–1974: Pre‑formal MAGIC/MAGI “v0”
- 12 primitive shapes; implicit intersection (adjacency), `OR` for unions; no parentheses
- Dual ellipsoid input forms; 5‑card ARB; optional TESTG/VOLUM/AREA sections
- Region IDs (3‑digit components / 2‑digit spaces) already in place
- 1974–1976: COMGEOM v1 (formal punched‑card format)
- Strict column positions; fixed parameter counts per primitive
- Region table layout (cards 16A/16B) and `-1` terminator card
- Error taxonomy; RPP enclosure required
- 1977–1981: Transitional v2 → v3 (internal, not publicly specified)
- v2: Column‑first with free‑field fallback; early generalized cones; explicit `+` tokens begin to appear
- v3: Free‑field first; limited parentheses; explicit `+ - UNION/OR`; auto bounding boxes begin to appear
- 1982–1984: COMGEOM v4 (stable token grammar)
- Whitespace/tokenized parsing; parentheses; decoupled geometry vs. region structure
- 1985+: COMGEOM v5 (v4 + attributes)
- Attribute/metadata lines: material, aircode, color, region parameters; same boolean grammar as v4
Modern translators (e.g., `comgeom-g`) typically support v1, v4, and v5. Other
versions would require manual adaptation.
---
## Summary of Noteworthy Changes
|------------|---------|--------------------------------------|------------------------------------------------|
| Era | Version | Context | Noteworthy Changes |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1967–1970 | v0 | Early MAGI/MAGIC on CDC/BRL | 12 primitives; adjacency = intersection; |
| | | | `-` subtract; `OR` unions; no parentheses; |
| | | | TEC permits oblique height vectors |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1970–1974 | v0 | MAGIC manuals | Canonical 5‑card ARB; dual ellipsoid input |
| | | | (foci vs center form); optional |
| | | | TESTG/VOLUM/AREA cards; strict(er) carding |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1974–1976 | v1 | GIFT integration; CDC/FORTRAN | Formal fixed‑columns per primitive; |
| | | | region table cards (16A/16B); `-1` end marker; |
| | | | RPP enclosure required |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1977–1979 | v2 | Internal expansion | Column parsing w/ free‑field fallback; |
| | | | explicit `+` tokens; early generalized cones; |
| | | | torus parameter ordering fixes |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1979–1981 | v3 | Move to machine‑independent parsing | Whitespace tokenization; shallow parentheses; |
| | | | explicit `+ - UNION/OR`; auto RPP; |
| | | | early ARB face validation |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1982–1984 | v4 | Consolidation into BRL‑CAD pipeline | Fully tokenized whitespace grammar; |
| | | | stable parentheses; clean geometry/structure |
| | | | separation |
|------------|---------|--------------------------------------|------------------------------------------------|
| 1985+ | v5 | Attribute/metadata enrichment | Adds attribute lines (material, aircode, |
| | | | color, region params); same v4 |
| | | | geometry/boolean grammar |
|------------|---------|--------------------------------------|------------------------------------------------|
Provenance: v0 details are drawn from the 1967 MAGI report and the MAGIC JTCG
User Manual (61 JTCG/ME‑71‑7‑1). v1 is documented in BRL Report 1802 and
ARBRL‑TR‑02189. v2/v3 are reconstructed from tool comments and translator
heuristics. v4/v5 are reflected in BRL‑CAD-era translators and user guides.
---
## Primitive set by version
The early “MAGIC twelve” primitives were established by 1970 and remained the
backbone across versions:
- RPP (axis‑aligned box), BOX (oriented box), SPH, RCC (right circular
cylinder), REC (right elliptical cylinder), TRC (truncated right angle cone),
TEC (truncated elliptic cone; height vector need not be perpendicular to base),
ELL (ellipsoid of revolution), RAW/WED (right angle wedge), ARB (arbitrary
convex polyhedron), ARS (arbitrary curved surface), TOR (torus)
| Primitive | v0 (MAGIC) | v1 | v2/v3 (notes) | v4 | v5 |
|-----------|------------|----|------------------------------------|----|----|
| RPP | ✓ | ✓ | Auto‑derive allowed (v3) | ✓ | ✓ |
| BOX | ✓ | ✓ | | ✓ | ✓ |
| SPH | ✓ | ✓ | | ✓ | ✓ |
| RCC | ✓ | ✓ | Param order normalized (v3) | ✓ | ✓ |
| REC | ✓ | ✓ | | ✓ | ✓ |
| TRC | ✓ | ✓ | Distinct from TEC clarified (v3) | ✓ | ✓ |
| TEC | ✓ | ✓ | Pre‑TGC experiments | ✓ | ✓ |
| ELL | ✓ (dual) | ✓ | Dual input retained | ✓ | ✓ |
| RAW/WED | ✓ | ✓ | | ✓ | ✓ |
| ARB | ✓ (5‑card) | ✓ | Face validation begins (v3) | ✓ | ✓ |
| ARS | ✓ | ✓ | Same semantics | ✓ | ✓ |
| TOR | ✓ | ✓ | Minor<Major enforcement tightened | ✓ | ✓ |
Notes:
- ARB in v0/v1 is the “MAGIC 5‑card” convex polyhedron: 8 vertices then 6 faces
by ordinal indices; BRL‑CAD later canonicalizes to `arb8`.
- Ellipsoid dual input (foci+major length or center+axis+equatorial radius) is
an early quirk that persists; translators normalize internally.
- TEC (truncated elliptic cone) allows a non‑perpendicular height vector as
early as v0; generalized cone (TGC) is a later BRL‑CAD primitive outside
COMGEOM proper.
---
## Boolean and region semantics across versions
### v0 (pre‑v1)
- Intersection: implied by adjacency of body references (`Region = 1 2` means
inside both 1 and 2)
- Subtraction: leading minus (`-3` means outside body 3)
- Union: `OR` between clauses (`Region = (1 -2) OR (4 -5)`)
- No parentheses: evaluation is effectively left‑to‑right by clause; `OR` lumps
subregions
- “Buttressing” rule: where two bodies share a surface, each region must negate
the other body to avoid ambiguous boundaries (e.g., `+2 -3` vs `+3 -2`)
Example (v0 style):
```
200 = 2 -3
300 = 3 -2
```
### v1
- Same operators, strict fixed columns:
- Region number cols 1–5, optional `OR` marker (IA(1)), then up to 9
operator+body tokens per card
- Continuation cards permitted; `-1` in cols 1–5 terminates region table
- Parentheses still not part of the grammar; grouping via `OR` card positions
### v2 / v3 (transitional)
- v2: Column parsing with free‑field fallback; explicit `+` tokens increasingly
required in ambiguous decks
- v3: Free‑field tokenization; limited parentheses; `UNION`/`U` recognized
synonyms for `OR`
- Automatic bounding boxes if RPP omitted (conventional runs)
### v4 / v5
- Full token grammar; parentheses supported; explicit `+ - OR UNION` with
predictable precedence
- Geometry declarations cleanly separated from boolean structure; comments
supported
Comparative snippets:
- v0/v1 region equation (fixed columns):
```
001 +2 -3
002 +3 -2
-1
```
- v4/v5 region expression (tokenized):
```
region 001: +2 -3
region 002: +3 -2
```
- v4/v5 with grouping:
```
region 010: ( +2 -3 ) OR ( +4 -5 )
```
---
## Card / field structure changes
### v0 (MAGIC card decks)
- Mixed adherence to columns
- Multi‑card primitives:
- BOX: Vx Vy Vz / Ax Ay Az / Bx By Bz / Cx Cy Cz (2 cards)
- RCC/REC/TRC/RAW: 2 cards; TEC: 3 cards; ARB: 5 cards; ARS: 2 +
M*ceil((N+1)/2) cards
- Region identification card per region: region number + component code or
space code + description
### v1
- Formal fixed‑column definitions per primitive; field counts validated
- RPP mandatory enclosure
- Region table formatting codified; `-1` terminator
- Error messages categorized (fatal vs non‑fatal)
### v2/v3 (reconstructed)
- Free‑field parsing introduced to accept large/overflow coordinates
- Whole‑line comments (`#` or `!`) accepted in some parsers (internal)
- Early “DUMMY” region recognition; improved error diagnostics
### v4/v5
- Whitespace/token parsing only; columns ignored
- v5 adds attribute/metadata lines (see next section)
---
## Attributes and metadata
- v0/v1: Region identification via numeric codes only (3‑digit component;
2‑digit space). Materials/colors external or implicit.
- v5: Attribute lines augment region definitions:
- `material=Steel`, `aircode=02`, `color=0/255/0`, `los=...`, `density=...`
- Name=value or token style, parsed separately from geometry/boolean
---
## Recognizing versions in the wild (cheat sheet)
- Likely v0 (MAGIC):
- Cards referencing TESTG/VOLUM/AREA sections
- Primitive blocks matching MAGIC manual’s 2/3/5‑card counts and ordering
- Region tables with `-1` terminator but no parentheses anywhere; `OR`
appears literally
- Ellipsoid blocks with foci pair + major length
- v1:
- Strict columns; values neatly aligned; region table with IA(1) `OR` markers
- RPP enclosure present and required
- v2/v3 (transitional):
- Mixture of column and free‑field lines; occasional parentheses; `UNION`
token present
- Comments starting `#` or `!` in legacy sources; generalized cone parameters
pre‑TGC
- v4:
- Whitespace tokens; parentheses; clean separation of bodies from boolean
structure; no attribute lines
- v5:
- Same as v4 plus attribute/metadata lines (e.g., `material=`, `aircode=`,
`color=`)
---
## Migration guidance
1. Inventory and validate
- Count per‑primitive parameters; flag lines with wrong cardinality
- Verify region table completeness: every point belongs to exactly one
region; buttressing negations present
2. Convert v0 → v1
- Pad to fixed columns; split multi‑card primitives correctly
- Keep dual ellipsoid inputs; normalize internally if supported
3. Convert v1 → v4/v5
- Translate region boolean expressions to explicit tokens with parentheses
- Add attributes (v5): material, aircode (01 external, 02 crew, 05 engine,
09 terminus), region properties
4. Validate by ray tracing
- Reproduce small sample angles from manual output (cell listings,
obliquities, LOS/normal thickness) to confirm equivalence
Pitfalls:
- Ellipsoid dual form mapping; ensure axis vector direction/length matches
major axis semantics
- TEC/TRC confusion in transitional decks; verify height vector
perpendicularity (TEC: may be oblique)
- ARB face index blocks: ensure 6 faces present; repeat a face for 4/5‑face
forms (per v1 storage) or convert to `arb8`
---
## Examples
### v0 style (MAGIC region)
```
15 +15 -17 -14 -16
16 +15 +16 -14 -17 -21 -22
-1
```
### v1 fixed columns (same semantics)
```
00015 +15 -17 -14 -16
00016 +15 +16 -14 -17 -21 -22
-1
```
### v4/v5 tokenized with grouping
```
region 15: (+15 -17 -14 -16)
region 16: ((+15 +16 -14 -17) OR (-21 -22)) # if logically intended
```
### v5 attributes
```
region 15: (+15 -17 -14 -16)
material=Steel
aircode=02
color=128/128/128
```
---
## Region identification codes (early and stable)
Components (3 digits):
- 001–099 Internal components
- 100–199 Armor
- 200–299 Fuel (often reused in later targets)
- 300–399 Misc external
- 400–499 Gun components
- 500–599 Track suspension (501 reserved for track; edge hits become 502)
- 600–699 Wheel suspension
- 700–799 Power train
- 800–899 Miscellaneous
- 900–998 Unused
- 999 Ground/soil
Spaces (2 digits):
- 01 External air
- 02 Crew compartment air
- 05 Engine compartment air
- 09 No further target
---
## References
- MAGI (1967): Early geometric description technique for vulnerability analysis
(MAGI‑6701)
- MAGIC User Manual (JTCG/ME 61 JTCG/ME‑71‑7‑1; ‑7‑2‑1; ‑7‑2‑2): Combinatorial
geometry, body/region cards, optional QA routines, sample problems
- BRL Report 1802; ARBRL‑TR‑02189: Formal COMGEOM v1 card specifications
- BRL‑CAD documentation: Primitive mappings (e.g., `arb8`, TGC) and later
pipeline translators
---
## Appendix: Field counts per primitive (v0/v1 reference)
- RPP: 6 scalars (Xmin Xmax Ymin Ymax Zmin Zmax)
- BOX: 12 scalars (Vx Vy Vz Ax Ay Az Bx By Bz Cx Cy Cz) over 2 cards
- SPH: 4 scalars (Cx Cy Cz R)
- RCC: 7 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R)
- REC: 12 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R1x R1y R1z R2x R2y R2z)
- TRC: 8 scalars over 2 cards (Vx Vy Vz Hx Hy Hz R1 R2)
- ELL: (foci Fl(3) F2(3) + L) OR (center V(3) + A(3) + equatorial R)
- RAW/WED: 12 scalars over 2 cards (V(3) + leg A(3) + leg B(3) + width C(3); A and B interchangeable)
- ARB: 5 cards: vertices 1–2 on card A; pairs (3,4), (5,6), (7,8) on cards B; faces (6 x 4 ordinals) on card C
- TEC: 13 scalars over 3 cards (V(3) H(3) A(3) B(3) P)
- ARS: 2 header integers (M curves, N points) + M * ceil((N+1)/2) cards of point pairs
+186
View File
@@ -0,0 +1,186 @@
#include "ComGeomConverter.h"
#include <cstdio>
#include <cstring>
#include "ComGeomReader.h"
#include "SolidParser.h"
#include "RegionManager.h"
ComGeomConverter::ComGeomConverter(const std::string &in,
const std::string &out,
const ConverterOptions &opts)
: m_inPath(in), m_outPath(out), m_opts(opts)
{}
ComGeomConverter::~ComGeomConverter() {
if (m_in) fclose(m_in);
if (m_out) wdb_close(m_out);
}
bool ComGeomConverter::openInput() {
if (!bu_file_exists(m_inPath.c_str(), NULL)) {
std::perror(m_inPath.c_str());
return false;
}
m_in = fopen(m_inPath.c_str(), "rb");
if (!m_in) {
std::perror(m_inPath.c_str());
return false;
}
return true;
}
bool ComGeomConverter::openOutput() {
if (bu_file_exists(m_outPath.c_str(), NULL))
bu_log("Warning: appending to existing database\n");
m_out = wdb_fopen(m_outPath.c_str());
if (!m_out) {
std::perror(m_outPath.c_str());
return false;
}
return true;
}
bool ComGeomConverter::writeDbHeader(const std::string &title, const std::string &units) {
if (mk_conversion(units.c_str()) < 0) {
std::printf("WARNING: unknown units '%s', using inches\n", units.c_str());
(void)mk_conversion("in");
}
if (mk_id_units(m_out, title.c_str(), units.c_str()) < 0) {
std::printf("Unable to write database ID, units='%s'\n", units.c_str());
return false;
}
return true;
}
int ComGeomConverter::run() {
if (m_opts.version != 0 && m_opts.version != 1 && m_opts.version != 4 && m_opts.version != 5) {
std::fprintf(stderr,"version %d not supported\n", m_opts.version);
return 1;
}
if (!openInput() || !openOutput()) return 1;
std::printf("Reading version %d COMGEOM file\n", m_opts.version);
/* Title & units */
std::string title, units;
{
ComGeomReader r(m_in);
auto l = r.next("title card");
if (!l) { std::printf("Empty input file: no title record\n"); return 10; }
std::string_view sv(l->s);
if (m_opts.version == 0) {
title = ComGeomReader::rstrip(sv);
ComGeomReader::trimLeadingSpacesInplace(title);
units = "in";
} else if (m_opts.version == 1) {
title = ComGeomReader::rstrip(sv);
ComGeomReader::trimLeadingSpacesInplace(title);
units = "in";
} else {
units.assign(sv.substr(0,2));
title.assign(sv.substr(3));
ComGeomReader::trimLeadingSpacesInplace(title);
title = ComGeomReader::rstrip(title);
units = ComGeomReader::rstrip(units);
ComGeomReader::toLowerInplace(units);
}
std::printf("Title: %s\nUnits: %s\n", title.c_str(), units.c_str());
}
if (!writeDbHeader(title, units)) return 1;
int solTotal=0, regTotal=0;
{
ComGeomReader r(m_in);
if (m_opts.version == 0) {
// Attempt single read first
auto l = r.next("v0 constraints candidate");
if (l) {
std::string_view sv(l->s);
solTotal = ComGeomReader::fieldInt(sv, 30, 10);
regTotal = ComGeomReader::fieldInt(sv, 40, 10);
}
if (solTotal <= 0 || regTotal <= 0) {
// Scan up to 50 lines to find plausible NBODY/NRMAX pair
solTotal = regTotal = 0;
for (int scan=0; scan<50; ++scan) {
auto cand = r.next("scan constraints");
if (!cand) break;
std::string_view sv2(cand->s);
int nb = ComGeomReader::fieldInt(sv2, 30, 10);
int nr = ComGeomReader::fieldInt(sv2, 40, 10);
if (nb > 0 && nr > 0) {
solTotal = nb;
regTotal = nr;
break;
}
}
if (solTotal == 0 || regTotal == 0) {
std::printf("Warning: v0 NBODY/NRMAX not found, dynamic sizing will be used\n");
solTotal = 0; // dynamic
regTotal = 0;
}
}
} else if (m_opts.version == 1) {
solTotal = regTotal = 9999;
} else {
auto l = r.next("control card");
if (!l) { std::printf("No control card .... STOP\n"); return 10; }
std::string_view sv(l->s);
if (m_opts.version == 4) {
solTotal = ComGeomReader::fieldInt(sv, 0, 20);
regTotal = ComGeomReader::fieldInt(sv, 20, 10);
} else {
solTotal = ComGeomReader::fieldInt(sv, 0, 5);
regTotal = ComGeomReader::fieldInt(sv, 5, 5);
}
}
if (m_opts.verbose)
std::printf("Expecting %d solids, %d regions\n", solTotal, regTotal);
}
/* Solids */
{
if (m_opts.verbose) std::puts("Primitive table");
ComGeomReader r(m_in);
SolidParserOptions sopts{m_opts.version,m_opts.verbose,m_opts.suffix};
sopts.legacy_trunc = m_opts.legacy_truncate_names;
SolidParser sp(r, m_out, sopts);
sp.setSolidsExpected(solTotal);
while (true) {
auto res = sp.parseNext();
if (res.status == SolidParseResult::Eof) {
if (m_opts.verbose)
std::printf("\nprocessed %d of %d solids\n\n", sp.solidsProcessed(), solTotal);
if (solTotal > 0 && sp.solidsProcessed() < solTotal && m_opts.version > 1) {
std::printf("some solids are missing, aborting\n");
return 1;
}
break;
}
if (res.status == SolidParseResult::Error) {
std::fprintf(stderr,"error converting solid %d: %s\n",
sp.solidsProcessed(), res.message.c_str());
}
}
}
/* Regions */
{
if (m_opts.verbose) std::puts("\nRegion table");
ComGeomReader r(m_in);
RegionManagerOptions ropts;
ropts.version = m_opts.version;
ropts.verbose = m_opts.verbose;
ropts.suffix = m_opts.suffix;
ropts.regTotal = regTotal;
ropts.legacy_trunc = m_opts.legacy_truncate_names;
RegionManager rm(r, m_out, ropts);
if (!rm.parseRegions()) return 10;
if (m_opts.verbose) std::puts("\nGroups");
if (!rm.writeGroups()) return 10;
if (m_opts.verbose) std::puts("");
}
return 0;
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef BRLCAD_COMGEOM_CONVERTER_H
#define BRLCAD_COMGEOM_CONVERTER_H
#include "common.h"
#include <string>
extern "C" {
#include "wdb.h"
#include "bu/file.h"
#include "bu/log.h"
#include "bu/app.h"
}
struct ConverterOptions {
int version = 5;
int verbose = 0;
std::string suffix;
bool legacy_truncate_names = false; // enable legacy suffix truncation (max 13 chars)
};
class ComGeomConverter {
public:
ComGeomConverter(const std::string &inputPath,
const std::string &outputPath,
const ConverterOptions &opts);
~ComGeomConverter();
int run();
private:
bool openInput();
bool openOutput();
bool writeDbHeader(const std::string &title, const std::string &units);
private:
std::string m_inPath, m_outPath;
ConverterOptions m_opts;
FILE *m_in = nullptr;
rt_wdb *m_out = nullptr;
};
#endif
+76
View File
@@ -0,0 +1,76 @@
#include "ComGeomReader.h"
#include <cstdlib>
#include <cctype>
#include <algorithm>
ComGeomReader::ComGeomReader(FILE *fp) : m_fp(fp) {}
ComGeomReader::~ComGeomReader() = default;
std::optional<ComGeomReader::Line> ComGeomReader::next(const char * /*title*/) {
if (!m_fp) return std::nullopt;
std::string s;
int c = 0;
while ((c = fgetc(m_fp)) != EOF) {
if (c == '\r') {
int nc = fgetc(m_fp);
if (nc != '\n' && nc != EOF) ungetc(nc, m_fp);
break;
}
if (c == '\n') break;
s.push_back(static_cast<char>(c));
}
if (c == EOF && s.empty())
return std::nullopt;
m_line++;
return Line{std::move(s), m_line};
}
int ComGeomReader::fieldInt(std::string_view sv, size_t start, size_t len) {
if (start >= sv.size()) return 0;
size_t end = std::min(sv.size(), start + len);
// Work on the field window only
size_t b = start, e = end;
// Trim leading whitespace
while (b < e && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
// Trim trailing whitespace
while (e > b && std::isspace(static_cast<unsigned char>(sv[e-1]))) e--;
if (b >= e) return 0;
// Note that we preserve internal spaces to match the behavior of the
// original C code - "1 23" would be read as 1 rather than 123. Whether
// or not this is really desirable behavior is a question - i.e. whether
// there is a valid use case where a line would make the above separation
// with intent in the COMGEOM formats - but for now we match legacy code.
std::string token(sv.substr(b, e - b));
return std::atoi(token.c_str());
}
double ComGeomReader::fieldDouble(std::string_view sv, size_t start, size_t len) {
if (start >= sv.size()) return 0.0;
size_t end = std::min(sv.size(), start + len);
// Work on the field window only
size_t b = start, e = end;
// Trim leading whitespace
while (b < e && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
// Trim trailing whitespace
while (e > b && std::isspace(static_cast<unsigned char>(sv[e-1]))) e--;
if (b >= e) return 0.0;
std::string token(sv.substr(b, e - b)); // preserve internal spaces to match legacy C
return std::atof(token.c_str());
}
std::string ComGeomReader::rstrip(std::string_view s) {
size_t end = s.size();
while (end > 0 && s[end-1] == ' ') end--;
return std::string(s.substr(0, end));
}
void ComGeomReader::toLowerInplace(std::string &s) {
for (auto &c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
void ComGeomReader::trimLeadingSpacesInplace(std::string &s) {
size_t i = 0;
while (i < s.size() && std::isspace(static_cast<unsigned char>(s[i]))) i++;
if (i) s.erase(0, i);
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef BRLCAD_COMGEOM_READER_H
#define BRLCAD_COMGEOM_READER_H
#include "common.h"
#include <cstdio>
#include <string>
#include <optional>
#include <string_view>
class ComGeomReader {
public:
struct Line {
std::string s;
size_t number = 0;
std::string_view view() const { return s; }
};
explicit ComGeomReader(FILE *fp);
~ComGeomReader();
std::optional<Line> next(const char *title);
static int fieldInt(std::string_view sv, size_t start, size_t len);
static double fieldDouble(std::string_view sv, size_t start, size_t len);
static std::string rstrip(std::string_view s);
static void toLowerInplace(std::string &s);
static void trimLeadingSpacesInplace(std::string &s);
size_t lineNo() const { return m_line; }
private:
FILE *m_fp = nullptr;
size_t m_line = 0;
};
#endif
+424
View File
@@ -0,0 +1,424 @@
#include "RegionManager.h"
#include <cstdio>
#include <cstring>
#include <utility>
RegionManager::RegionManager(ComGeomReader &reader, rt_wdb *outfp, const RegionManagerOptions &opts)
: m_r(reader), m_out(outfp), m_opts(opts)
{
// For dynamic v0 decks (regTotal == 0) preallocate provisional capacity
size_t allocCount = static_cast<size_t>(m_opts.regTotal + 1);
if (m_opts.version == 0 && m_opts.regTotal == 0) {
allocCount = 4097; // provisional region cap
}
m_regionHeads.resize(allocCount);
for (auto &wm : m_regionHeads) {
WMEMBER_INIT(&wm);
}
groupInit();
}
RegionManager::~RegionManager() {
for (auto &wm : m_regionHeads) {
if (wm.wm_name) {
bu_free(wm.wm_name, "region wm_name");
wm.wm_name = nullptr;
}
}
for (int i = 0; i < m_groupCount; ++i) {
if (m_groups[i].head.wm_name) {
bu_free(m_groups[i].head.wm_name, "group wm_name");
m_groups[i].head.wm_name = nullptr;
}
}
}
std::string RegionManager::makeRegionName(int n) const {
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
char buf[64];
std::snprintf(buf, sizeof(buf), "r%d%s", n, suf.c_str());
return std::string(buf);
}
void RegionManager::colPrint(const std::string &s) {
if (!m_opts.verbose) return;
std::fputs(s.c_str(), stdout);
m_colCur += s.size();
while (m_colCur < 78 && (m_colCur % 10)) {
putchar(' ');
++m_colCur;
}
if (m_colCur >= 78) {
std::fputc('\n', stdout);
m_colCur = 0;
}
}
/* ---------------- v0 (MAGIC) region table with OR semantics ---------------- */
bool RegionManager::parseRegionTableV0() {
auto first = m_r.next("v0 region card");
if (!first) { std::fprintf(stderr,"v0 region: EOF\n"); return false; }
std::string current = first->s;
int lastRegNum = 0;
while (true) {
int reg_num = ComGeomReader::fieldInt(current, 0, 5);
if (reg_num < 0) break;
if (reg_num == 0) {
auto nl = m_r.next("v0 region card");
if (!nl) break;
current = nl->s;
continue;
}
// resize if needed (dynamic)
if (reg_num >= static_cast<int>(m_regionHeads.size())) {
std::fprintf(stderr,"v0 region number %d exceeds provisional allocation\n", reg_num);
return false;
}
lastRegNum = reg_num;
wmember &head = m_regionHeads[static_cast<size_t>(reg_num)];
if (!head.wm_name) head.wm_name = bu_strdup(makeRegionName(reg_num).c_str());
struct Term { int body; bool subtract; };
std::vector<std::vector<Term>> clauses;
clauses.emplace_back();
auto parseLine = [&](const std::string &line) {
for (int i=0;i<9;++i) {
size_t slot = 6 + i*7;
if (slot + 7 > line.size()) break;
std::string field = line.substr(slot, 7);
std::string op = field.substr(0,2);
std::string numstr = field.substr(2);
ComGeomReader::trimLeadingSpacesInplace(op);
numstr = ComGeomReader::rstrip(numstr);
ComGeomReader::trimLeadingSpacesInplace(numstr);
if (numstr.empty()) continue;
int bnum = std::atoi(numstr.c_str());
if (bnum == 0) continue;
if (op == "OR" || op == "or") {
if (!clauses.back().empty()) clauses.emplace_back();
continue;
}
bool subtract = (bnum < 0);
if (subtract) bnum = -bnum;
clauses.back().push_back({bnum, subtract});
}
};
parseLine(current);
while (true) {
auto nextLine = m_r.next("v0 region card");
if (!nextLine) break;
int newRegTest = ComGeomReader::fieldInt(nextLine->s, 0, 5);
std::string trimmed = ComGeomReader::rstrip(nextLine->s);
std::string t2 = trimmed; ComGeomReader::trimLeadingSpacesInplace(t2);
if (t2 == "end") {
if (m_opts.regTotal == 0) m_opts.regTotal = reg_num;
break;
}
if (newRegTest < 0) { // negative sentinel
if (m_opts.regTotal == 0) m_opts.regTotal = reg_num;
current = nextLine->s;
break;
}
if (newRegTest > 0) { // new region starts
current = nextLine->s;
break;
}
parseLine(nextLine->s);
}
int clauseIdx = 0;
for (auto &cl : clauses) {
if (cl.empty()) continue;
wmember clauseHead; WMEMBER_INIT(&clauseHead);
char cname[64]; std::snprintf(cname, sizeof(cname), "r%d_c%d", reg_num, ++clauseIdx);
clauseHead.wm_name = bu_strdup(cname);
bool firstTerm = true;
for (auto &t : cl) {
std::string memberName = std::string("s") + std::to_string(t.body) + m_opts.suffix;
int op;
if (firstTerm) {
op = WMOP_UNION; // baseline
firstTerm = false;
} else {
op = t.subtract ? WMOP_SUBTRACT : WMOP_INTERSECT;
}
if (!mk_addmember(memberName.c_str(), &clauseHead.l, nullptr, op)) {
std::fprintf(stderr,"v0 mk_addmember failed clause %s member %s\n", cname, memberName.c_str());
}
}
mk_comb(m_out, clauseHead.wm_name, &clauseHead.l, 0,
"", "", nullptr, 0,0,0,0,0,0,0);
if (!mk_addmember(clauseHead.wm_name, &head.l, nullptr, WMOP_UNION)) {
std::fprintf(stderr,"v0 mk_addmember region %d clause %s\n", reg_num, clauseHead.wm_name);
}
}
if (m_opts.verbose) colPrint(head.wm_name);
int sentinelTest = ComGeomReader::fieldInt(current, 0, 5);
if (sentinelTest < 0) break;
}
if (m_opts.regTotal == 0) m_opts.regTotal = lastRegNum;
return true;
}
bool RegionManager::parseRegions() {
if (m_opts.version == 0) {
if (!parseRegionTableV0()) return false;
for (int i=1;i<=m_opts.regTotal && i < static_cast<int>(m_regionHeads.size());++i) registerRegion(i,0,0,0,0);
return true;
}
if (!parseRegionTable()) return false;
if (m_opts.version == 1) {
for (int i = 1; i <= m_opts.regTotal; ++i) {
registerRegion(i, 0, 0, 0, 0);
}
return true;
}
return parseIdentTable();
}
bool RegionManager::parseRegionTable() {
auto first = m_r.next("region card");
if (!first) {
std::fprintf(stderr, "getregion: EOF\n");
return false;
}
if (ComGeomReader::fieldInt(first->s, 0, 5) != 1) {
std::fprintf(stderr, "First region card not #1\n");
return false;
}
std::string current = first->s;
while (true) {
int reg_num = ComGeomReader::fieldInt(current, 0, 5);
if (reg_num < 0) {
return true;
}
if (reg_num > m_opts.regTotal) {
std::fprintf(stderr, "%d regions > claimed %d\n", reg_num, m_opts.regTotal);
return false;
}
wmember &head = m_regionHeads[static_cast<size_t>(reg_num)];
if (!head.wm_name) {
head.wm_name = bu_strdup(makeRegionName(reg_num).c_str());
}
while (true) {
size_t base = (m_opts.version == 1) ? 10 : 6;
for (int i = 0; i < 9; ++i) {
size_t off = base + i * 7;
if (off + 7 > current.size()) break;
char token[8] = {0};
int k = 0;
for (int j = 2; j < 7; ++j) {
if (off + j >= current.size()) break;
char c = current[off + j];
if (!std::isspace(static_cast<unsigned char>(c))) {
token[k++] = c;
}
}
if (k == 0) continue;
int inst_num = std::atoi(token);
if (inst_num == 0) continue;
int op = WMOP_UNION;
bool region_ref = false;
if (m_opts.version == 5) {
char flag = (off + 1 < current.size()) ? current[off + 1] : ' ';
if (flag == 'g' || flag == 'G') {
region_ref = true;
} else if (flag == 'R' || flag == 'r') {
op = WMOP_UNION;
} else {
if (inst_num < 0) {
op = WMOP_SUBTRACT;
inst_num = -inst_num;
} else {
op = WMOP_INTERSECT;
}
}
} else {
char flag = (off + 1 < current.size()) ? current[off + 1] : ' ';
if (flag == ' ') {
if (inst_num < 0) {
op = WMOP_SUBTRACT;
inst_num = -inst_num;
} else {
op = WMOP_INTERSECT;
}
} else {
op = WMOP_UNION;
}
}
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
std::string memberName = region_ref
? makeRegionName(inst_num)
: (std::string("s") + std::to_string(inst_num) + suf);
if (!mk_addmember(memberName.c_str(), &head.l, nullptr, op)) {
std::fprintf(stderr, "mk_addmember failed for region %d member '%s'\n",
reg_num, memberName.c_str());
}
}
auto nextLine = m_r.next("region card");
if (!nextLine) {
std::fprintf(stderr, "getregion: EOF\n");
return false;
}
std::string trimmed = ComGeomReader::rstrip(nextLine->s);
ComGeomReader::trimLeadingSpacesInplace(trimmed);
if (trimmed == "end") {
m_opts.regTotal = reg_num;
return true;
}
int possible = ComGeomReader::fieldInt(nextLine->s, 0, 5);
if (possible != 0) {
current = nextLine->s;
break;
}
current = nextLine->s;
}
if (m_opts.verbose) colPrint(head.wm_name);
}
}
bool RegionManager::parseIdentTable() {
if (m_opts.version == 4) {
(void)m_r.next("blank card");
}
if (m_opts.verbose) std::puts("\nRegion ident table");
while (true) {
auto line = m_r.next("ident card");
if (!line) return true;
if (line->s.empty()) return true;
int reg_num, id, air, mat, los;
if (m_opts.version == 5) {
reg_num = ComGeomReader::fieldInt(line->s, 0, 5);
id = ComGeomReader::fieldInt(line->s, 5, 5);
air = ComGeomReader::fieldInt(line->s, 10, 5);
mat = ComGeomReader::fieldInt(line->s, 15, 5);
los = ComGeomReader::fieldInt(line->s, 20, 5);
} else {
reg_num = ComGeomReader::fieldInt(line->s, 0, 10);
id = ComGeomReader::fieldInt(line->s, 10, 10);
air = ComGeomReader::fieldInt(line->s, 20, 10);
mat = ComGeomReader::fieldInt(line->s, 74, 3);
los = ComGeomReader::fieldInt(line->s, 77, 3);
}
if (reg_num <= 0) return true;
registerRegion(reg_num, id, air, mat, los);
}
}
void RegionManager::registerRegion(int regNum, int id, int air, int mat, int los) {
if (regNum <= 0 || regNum >= static_cast<int>(m_regionHeads.size())) return;
wmember &head = m_regionHeads[static_cast<size_t>(regNum)];
if (BU_LIST_IS_EMPTY(&head.l)) {
if (m_opts.verbose && head.wm_name) {
colPrint(std::string("(") + head.wm_name + ")");
}
return;
}
int ret = mk_comb(m_out, head.wm_name, &head.l, 1,
"", "", nullptr,
id, air, mat, los, 0, 0, 1);
if (ret < 0) {
std::fprintf(stderr, "mk_comb failed for region %d (%s)\n", regNum, head.wm_name);
} else {
groupAdd(id, head.wm_name);
if (m_opts.verbose) colPrint(head.wm_name);
}
}
void RegionManager::groupInit() {
struct Def { const char *n; int lo; int hi; } defs[] = {
{"g00",0,0},{"g0",1,99},{"g1",100,199},{"g2",200,299},{"g3",300,399},
{"g4",400,499},{"g5",500,599},{"g6",600,699},{"g7",700,799},{"g8",800,899},
{"g9",900,999},{"g10",1000,1999},{"g11",2000,2999},{"g12",3000,3999},{"g13",4000,4999},
{"g14",5000,5999},{"g15",6000,6999},{"g16",7000,7999},{"g17",8000,8999},{"g18",9000,9999},
{"g19",10000,32767}
};
for (const auto &d : defs) {
if (m_groupCount >= kMaxGroups) {
std::fprintf(stderr, "Too many groups (limit %d)\n", kMaxGroups);
break;
}
groupRegister(d.n, d.lo, d.hi);
}
}
void RegionManager::groupRegister(const char *name, int lo, int hi) {
Group &g = m_groups[m_groupCount];
WMEMBER_INIT(&g.head);
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
std::string full = std::string(name) + suf;
g.head.wm_name = bu_strdup(full.c_str());
g.lo = lo;
g.hi = hi;
g.used = false;
++m_groupCount;
}
void RegionManager::groupAdd(int identVal, const char *regionName) {
for (int i = m_groupCount - 1; i >= 0; --i) {
if (identVal < m_groups[i].lo || identVal > m_groups[i].hi) continue;
if (!mk_addmember(regionName, &m_groups[i].head.l, nullptr, WMOP_UNION)) {
std::fprintf(stderr, "mk_addmember failed adding region '%s' to group '%s'\n",
regionName, m_groups[i].head.wm_name);
} else {
m_groups[i].used = true;
}
return;
}
std::fprintf(stderr, "Unable to find group for ident %d (region %s)\n", identVal, regionName);
}
bool RegionManager::writeGroups() {
wmember allHead;
WMEMBER_INIT(&allHead);
allHead.wm_name = const_cast<char *>("all.g");
for (int i = 0; i < m_groupCount; ++i) {
Group &g = m_groups[i];
if (BU_LIST_IS_EMPTY(&g.head.l)) continue;
int ret = mk_comb(m_out, g.head.wm_name, &g.head.l, 0,
nullptr, nullptr, nullptr,
0, 0, 0, 0, 0, 0, 0);
if (ret < 0) {
std::fprintf(stderr, "mk_comb (group) failed for '%s'\n", g.head.wm_name);
continue;
}
if (!mk_addmember(g.head.wm_name, &allHead.l, nullptr, WMOP_UNION)) {
std::fprintf(stderr, "mk_addmember failed adding '%s' to all.g\n", g.head.wm_name);
}
if (m_opts.verbose) colPrint(g.head.wm_name);
}
int retAll = mk_comb(m_out, "all.g", &allHead.l, 0,
nullptr, nullptr, nullptr,
0, 0, 0, 0, 0, 0, 0);
if (retAll < 0) {
std::fprintf(stderr, "mk_comb failed for all.g\n");
return false;
}
return true;
}
+74
View File
@@ -0,0 +1,74 @@
#pragma once
#include "common.h"
#include <array>
#include <string>
#include <vector>
extern "C" {
#include "vmath.h"
#include "raytrace.h"
#include "wdb.h"
}
#include "ComGeomReader.h"
struct RegionManagerOptions {
int version = 5;
long verbose = 0;
std::string suffix;
int regTotal = 0;
bool legacy_trunc = false; // optional legacy suffix truncation (max 13 chars)
};
/*
* Modernized RegionManager:
* - Uses WMEMBER_INIT for region and group heads (matches wdb.h)
* - Avoids container reallocation for group heads (std::array)
* - Relies on mk_addmember for node allocation and mk_comb/mk_lfcomb
* to free membership lists (per wdb.h contract)
* - No manual traversal freeing of member nodes (use mk_freemembers only
* for temporary aggregation lists we explicitly build, e.g. "all.g" head)
* - Stable addresses: region heads stored in std::vector sized once,
* group heads in fixed array
*/
class RegionManager {
public:
RegionManager(ComGeomReader &reader, rt_wdb *outfp, const RegionManagerOptions &opts);
~RegionManager();
bool parseRegions(); // read region membership cards
bool writeGroups(); // emit group combinations + all.g
private:
std::string makeRegionName(int n) const;
void colPrint(const std::string &s);
bool parseRegionTable();
bool parseRegionTableV0();
bool parseIdentTable();
void registerRegion(int regNum, int id, int air, int mat, int los);
void groupInit();
void groupRegister(const char *name, int lo, int hi);
void groupAdd(int identVal, const char *regionName);
private:
ComGeomReader &m_r;
rt_wdb *m_out = nullptr;
RegionManagerOptions m_opts;
std::vector<wmember> m_regionHeads; // index by region number (1..regTotal)
size_t m_colCur = 0;
struct Group {
wmember head; // combination head (membership list)
int lo = 0;
int hi = 0;
bool used = false; // whether at least one region fell into range
};
static constexpr int kMaxGroups = 21;
std::array<Group, kMaxGroups> m_groups;
int m_groupCount = 0;
};
+830
View File
@@ -0,0 +1,830 @@
#include "SolidParser.h"
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
/* Helper: build min/max from 6 RPP values */
static inline void vset_minmax(fastf_t *min, fastf_t *max, const fastf_t *dd) {
VSET(min, dd[0], dd[2], dd[4]);
VSET(max, dd[1], dd[3], dd[5]);
}
SolidParser::SolidParser(ComGeomReader &reader, rt_wdb *outfp, const SolidParserOptions &opts)
: m_r(reader), m_out(outfp), m_opts(opts)
{}
std::string SolidParser::makeSolidName(int n) const {
std::string suf = m_opts.suffix;
if (m_opts.legacy_trunc && suf.size() > 13) suf.resize(13);
char buf[64] = {0};
std::snprintf(buf, sizeof(buf), "s%d%s", n, suf.c_str());
return std::string(buf);
}
void SolidParser::colPrint(const std::string &s) {
if (!m_opts.verbose) return;
std::fputs(s.c_str(), stdout);
m_colCur += s.size();
while (m_colCur < 78 && (m_colCur % 10)) {
putchar(' ');
++m_colCur;
}
if (m_colCur >= 78) {
std::fputc('\n', stdout);
m_colCur = 0;
}
}
bool SolidParser::parseSolidHeader(const std::string &line, int &solidNum,
std::string &typeLower, std::string &lineOutForData) {
std::string_view sv(line);
std::string solidType;
switch (m_opts.version) {
case 5:
solidNum = ComGeomReader::fieldInt(sv, 0, 5);
solidType = ComGeomReader::rstrip(sv.substr(5, 5));
break;
case 4:
solidNum = ComGeomReader::fieldInt(sv, 0, 3);
solidType = ComGeomReader::rstrip(sv.substr(3, 4));
break;
case 1:
solidNum = ComGeomReader::fieldInt(sv, 5, 4);
solidType = ComGeomReader::rstrip(sv.substr(2, 3));
break;
default:
return false;
}
ComGeomReader::toLowerInplace(solidType);
typeLower = solidType;
lineOutForData = line;
return true;
}
bool SolidParser::getSolData(std::vector<fastf_t> &data, int num, int solidNum,
bool /*firstCardAlreadyRead*/, std::string lineBuf) {
data.clear();
data.reserve(num);
int cd = 1;
std::string line = std::move(lineBuf);
while (num > 0) {
std::string_view sv(line);
if (cd != 1) {
if ((m_opts.version == 5 && sv.size() > 5 && sv[5] != ' ') ||
(m_opts.version == 4 && sv.size() > 3 && sv[3] != ' ')) {
std::fprintf(stderr, "solid %d (continuation) card %d non-blank\n", solidNum, cd);
return false;
}
}
int j = std::min(num, 6);
for (int i = 0; i < j; ++i) {
data.push_back(static_cast<fastf_t>(ComGeomReader::fieldDouble(sv, 10 + i*10, 10)));
}
num -= j;
if (num <= 0) break;
auto l = m_r.next("solid continuation card");
if (!l) {
std::fprintf(stderr, "too few cards for solid %d\n", solidNum);
return false;
}
line = l->s;
++cd;
}
return true;
}
bool SolidParser::getXSolData(std::vector<fastf_t> &data, int num, int solidNum) {
data.clear();
data.reserve(num);
int cd = 1;
while (num > 0) {
auto l = m_r.next("x solid card");
if (!l) {
std::fprintf(stderr, "too few cards for solid %d\n", solidNum);
return false;
}
std::string_view sv(l->s);
if (cd != 1) {
if ((m_opts.version == 5 && sv.size() > 5 && sv[5] != ' ') ||
(m_opts.version == 4 && sv.size() > 3 && sv[3] != ' ')) {
std::fprintf(stderr, "solid %d (continuation) card %d non-blank\n", solidNum, cd);
return false;
}
}
int j = std::min(num, 6);
for (int i = 0; i < j; ++i) {
data.push_back(static_cast<fastf_t>(ComGeomReader::fieldDouble(sv, 10 + i*10, 10)));
}
num -= j;
++cd;
}
return true;
}
bool SolidParser::isIntegerField(std::string_view sv, size_t start, size_t len, int &value) const {
if (start >= sv.size()) { value = 0; return false; }
size_t end = std::min(sv.size(), start + len);
size_t b = start;
while (b < end && std::isspace(static_cast<unsigned char>(sv[b]))) b++;
if (b >= end) { value = 0; return false; }
bool hasDigit=false; bool hasDot=false;
for (size_t i=b;i<end;++i) {
char c = sv[i];
if (std::isspace(static_cast<unsigned char>(c))) break;
if (c == '.') hasDot = true;
if (std::isdigit(static_cast<unsigned char>(c))) hasDigit = true;
else if (c=='-' || c=='+') continue;
else { value=0; return false; }
}
if (!hasDigit || hasDot) { value=0; return false; }
std::string token(sv.substr(b, end-b));
value = std::atoi(token.c_str());
return true;
}
bool SolidParser::isLikelyFaceCard(const std::string &line) const {
// Heuristic: first four 10-char fields each parse as small integers (1..8) & no decimal points.
int vals=0;
for (int f=0; f<4; ++f) {
int v=0;
if (!isIntegerField(line, 10 + f*10, 10, v)) return false;
if (v < -8 || v > 8 || v==0) return false;
vals++;
}
return (vals==4);
}
/* ---------------- Primitive Handlers ---------------- */
SolidParseResult SolidParser::handle_ars(const std::string &name, const std::string &firstLine) {
std::string_view sv(firstLine);
int ncurves = ComGeomReader::fieldInt(sv, 10, 10);
int pts_per_curve = ComGeomReader::fieldInt(sv, 20, 10);
if (ncurves <= 0 || pts_per_curve <= 0)
return {SolidParseResult::Error, "ars invalid counts"};
std::vector<std::vector<fastf_t>> curves(ncurves);
for (int i = 0; i < ncurves; ++i) {
curves[i].resize(static_cast<size_t>(pts_per_curve) * 3);
if (!getXSolData(curves[i], pts_per_curve * 3, m_solWork))
return {SolidParseResult::Error, "ars curve data read failure"};
}
std::vector<fastf_t*> ptrs(ncurves);
for (int i = 0; i < ncurves; ++i) ptrs[i] = curves[i].data();
int ret = mk_ars(m_out, name.c_str(), ncurves, pts_per_curve, ptrs.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_ars failed" : ""};
}
SolidParseResult SolidParser::handle_rpp(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 6) return {SolidParseResult::Error, "rpp needs 6 values"};
fastf_t min[3], max[3];
vset_minmax(min, max, dd.data());
int ret = mk_rpp(m_out, name.c_str(), min, max);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rpp failed" : ""};
}
SolidParseResult SolidParser::handle_box(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "box needs 12 values"};
point_t v[8];
const fastf_t *D0 = &dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
VMOVE(v[0], D0);
VADD2(v[1], D0, D2);
VADD3(v[2], D0, D2, D1);
VADD2(v[3], D0, D1);
VADD2(v[4], D0, D3);
VADD3(v[5], D0, D3, D2);
VADD4(v[6], D0, D3, D2, D1);
VADD3(v[7], D0, D3, D1);
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(box) failed" : ""};
}
SolidParseResult SolidParser::handle_raw_wed(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "raw/wed needs 12 values"};
point_t v[8];
const fastf_t *D0=&dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
VMOVE(v[0], D0);
VADD2(v[1], D0, D2);
VMOVE(v[2], v[1]);
VADD2(v[3], D0, D1);
VADD2(v[4], D0, D3);
VADD3(v[5], D0, D3, D2);
VMOVE(v[6], v[5]);
VADD3(v[7], D0, D3, D1);
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(raw/wed) failed" : ""};
}
SolidParseResult SolidParser::handle_rvw(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 7) return {SolidParseResult::Error, "rvw needs 7 values"};
vect_t V; VMOVE(V, &dd[0]);
double a2=dd[3], theta=dd[4], phi=dd[5], h2=dd[6];
double angle1 = (phi + theta - 90.0) * DEG2RAD;
double angle2 = (phi + theta) * DEG2RAD;
double a2theta = a2 * std::tan(theta * DEG2RAD);
vect_t a,b,c;
VSET(a, a2theta * std::cos(angle1), a2theta * std::sin(angle1), 0.0);
VSET(b, -a2 * std::cos(angle2), -a2 * std::sin(angle2), 0.0);
VSET(c, 0.0, 0.0, h2);
point_t vert[8];
VSUB2(vert[0], V, b);
VMOVE(vert[1], V);
VMOVE(vert[2], vert[1]);
VADD2(vert[3], vert[0], a);
VADD2(vert[4], vert[0], c);
VADD2(vert[5], vert[1], c);
VMOVE(vert[6], vert[5]);
VADD2(vert[7], vert[3], c);
int ret = mk_arb8(m_out, name.c_str(), &vert[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(rvw) failed" : ""};
}
SolidParseResult SolidParser::handle_arw(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "arw needs 12 values"};
point_t v[8];
const fastf_t *D0=&dd[0], *D1=&dd[3], *D2=&dd[6], *D3=&dd[9];
VMOVE(v[0], D0);
VADD2(v[1], D0, D2);
VADD3(v[2], D0, D2, D3);
VADD2(v[3], D0, D3);
VADD2(v[4], D0, D1);
VMOVE(v[5], v[4]);
VADD3(v[6], D0, D1, D3);
VMOVE(v[7], v[6]);
int ret = mk_arb8(m_out, name.c_str(), &v[0][0]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arw) failed" : ""};
}
/* ---------- MAGIC v0 ARB generic handler ---------- */
SolidParseResult SolidParser::handle_arb_magic(const std::string &name, const std::string &firstLine) {
// Multi-card ARB parsing:
// Collect vertex triples until a face card is detected (integer ordinals 1..8).
std::vector<fastf_t> vertexDoubles;
std::string line = firstLine;
int maxVertexTriples = 8;
// Include data from first line if it contains numeric fields (after header)
auto consumeVertexCard = [&](const std::string &card) {
std::string_view sv(card);
for (int i=0;i<6;++i) { // up to 6 fields (i.e., 2 triples per card typical)
double val = ComGeomReader::fieldDouble(sv, 10 + i*10, 10);
// Heuristic: consider any numeric (including 0) as part of vertex stream until a face card appears
vertexDoubles.push_back(static_cast<fastf_t>(val));
if ((int)vertexDoubles.size()/3 >= maxVertexTriples) break;
}
};
// First card may have header only; attempt to parse numeric fields
consumeVertexCard(line);
// Read continuation cards until face card or limit reached
while ((int)vertexDoubles.size()/3 < maxVertexTriples) {
auto next = m_r.next("arb card");
if (!next) break;
if (isLikelyFaceCard(next->s)) {
line = next->s;
break;
}
consumeVertexCard(next->s);
}
int vertexCount = static_cast<int>(vertexDoubles.size()/3);
if (vertexCount < 4) return {SolidParseResult::Error, "arb v0 insufficient vertices"};
// Build point array
std::vector<fastf_t> verts(vertexCount*3);
std::copy(vertexDoubles.begin(), vertexDoubles.begin()+vertexCount*3, verts.begin());
// Read face cards (up to 6 faces)
std::vector<std::vector<int>> faces;
while (true) {
if (!isLikelyFaceCard(line)) {
auto peek = m_r.next("arb maybe face");
if (!peek) break;
line = peek->s;
if (!isLikelyFaceCard(line)) break;
}
std::string_view sv(line);
std::vector<int> f;
for (int i=0;i<4;++i) {
int v=0;
if (isIntegerField(sv, 10 + i*10, 10, v) && v != 0) {
f.push_back(v);
}
}
if (!f.empty()) faces.push_back(f);
auto next = m_r.next("arb face next");
if (!next) break;
if (!isLikelyFaceCard(next->s)) {
line = next->s; // pass to main dispatcher for next solid
break;
}
line = next->s;
if ((int)faces.size() >= 6) break;
}
// Validate face indices
for (const auto &f : faces) {
for (int idx : f) {
int absIdx = std::abs(idx);
if (absIdx < 1 || absIdx > vertexCount)
return {SolidParseResult::Error, "arb face index out of range"};
}
}
// Convert based on vertex count
SolidParseResult result;
if (vertexCount == 4) {
result = handle_arb4(name, verts);
} else if (vertexCount == 5) {
result = handle_arb5(name, verts);
} else if (vertexCount == 6) {
result = handle_arb6(name, verts);
} else if (vertexCount == 7) {
result = handle_arb7(name, verts);
} else if (vertexCount == 8) {
result = handle_arb8(name, verts);
} else {
return {SolidParseResult::Error, "arb v0 unsupported vertex count"};
}
if (result.status != SolidParseResult::Ok) {
// Fallback to ARBN if vertex-based conversion fails
std::fprintf(stderr, "arb v0 vertex conversion failed, attempting arbn fallback\n");
// Build plane equations from faces using indices (if enough faces)
if (faces.size() >= 4) {
// Simple plane extraction: pick triple for each face (first three indices)
std::vector<plane_t> eqn(faces.size());
for (size_t i=0;i<faces.size();++i) {
int a = std::abs(faces[i][0]) - 1;
int b = std::abs(faces[i][1]) - 1;
int c = std::abs(faces[i][2]) - 1;
if (a<0||b<0||c<0 || a>=vertexCount||b>=vertexCount||c>=vertexCount) continue;
plane_t pl;
point_t pa, pb, pc;
VMOVE(pa, &verts[a*3]);
VMOVE(pb, &verts[b*3]);
VMOVE(pc, &verts[c*3]);
struct bn_tol tol;
tol.magic = BN_TOL_MAGIC;
tol.dist = 0.0005;
tol.dist_sq = tol.dist * tol.dist;
tol.perp = 1e-6;
tol.para = 1 - tol.perp;
if (bg_make_plane_3pnts(pl, pa, pb, pc, &tol) == 0) {
VMOVE(eqn[i], pl);
}
}
int ret = mk_arbn(m_out, name.c_str(), (int)faces.size(), eqn.data());
if (ret < 0) return {SolidParseResult::Error, "arbn fallback failed"};
return {SolidParseResult::Ok, ""};
}
}
return result;
}
SolidParseResult SolidParser::handle_arb4(const std::string &name, const std::vector<fastf_t> &dd4) {
if (dd4.size() < 12) return {SolidParseResult::Error, "arb4 needs 12 values"};
int ret = mk_arb4(m_out, name.c_str(), dd4.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb4 failed" : ""};
}
SolidParseResult SolidParser::handle_arb5(const std::string &name, const std::vector<fastf_t> &dd5) {
if (dd5.size() < 15) return {SolidParseResult::Error, "arb5 needs 15 values"};
std::vector<fastf_t> dd_full(24);
std::copy(dd5.begin(), dd5.begin()+15, dd_full.begin());
const fastf_t *v4 = &dd5[12];
for (int rep=5; rep<=7; ++rep) std::copy(v4, v4+3, dd_full.begin()+rep*3);
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb5) failed" : ""};
}
SolidParseResult SolidParser::handle_arb6(const std::string &name, const std::vector<fastf_t> &dd6) {
if (dd6.size() < 18) return {SolidParseResult::Error, "arb6 needs 18 values"};
std::vector<fastf_t> dd_full(24);
std::copy(dd6.begin(), dd6.begin()+18, dd_full.begin());
const fastf_t *D4=&dd6[12];
const fastf_t *D5=&dd6[15];
std::copy(D4, D4+3, dd_full.begin()+5*3); // D(5)=D(4)
std::copy(D5, D5+3, dd_full.begin()+6*3); // D(6)=original D(5)
std::copy(D5, D5+3, dd_full.begin()+7*3); // D(7)=original D(5)
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb6) failed" : ""};
}
SolidParseResult SolidParser::handle_arb7(const std::string &name, const std::vector<fastf_t> &dd7) {
if (dd7.size() < 21) return {SolidParseResult::Error, "arb7 needs 21 values"};
std::vector<fastf_t> dd_full(24);
std::copy(dd7.begin(), dd7.begin()+21, dd_full.begin());
const fastf_t *D4=&dd7[12];
std::copy(D4, D4+3, dd_full.begin()+7*3); // D(7)=D(4)
int ret = mk_arb8(m_out, name.c_str(), dd_full.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8(arb7) failed" : ""};
}
SolidParseResult SolidParser::handle_arb8(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 24) return {SolidParseResult::Error, "arb8 needs 24 values"};
int ret = mk_arb8(m_out, name.c_str(), dd.data());
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_arb8 failed" : ""};
}
SolidParseResult SolidParser::handle_rcc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 7) return {SolidParseResult::Error, "rcc needs 7 values"};
int ret = mk_rcc(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rcc failed" : ""};
}
SolidParseResult SolidParser::handle_rec(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "rec needs 12 values"};
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, dd.data()+6, dd.data()+9);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc(rec) failed" : ""};
}
SolidParseResult SolidParser::handle_trc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 8) return {SolidParseResult::Error, "trc needs 8 values"};
int ret = mk_trc_h(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6], dd[7]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_trc_h failed" : ""};
}
SolidParseResult SolidParser::handle_tec(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 13) return {SolidParseResult::Error, "tec needs 13 values"};
double p = dd[12];
if (NEAR_ZERO(p, SMALL)) return {SolidParseResult::Error, "tec p cannot be zero"};
vect_t topA, topB;
double invp = 1.0/p;
VSCALE(topA, dd.data()+6, invp);
VSCALE(topB, dd.data()+9, invp);
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, topA, topB);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc(tec) failed" : ""};
}
SolidParseResult SolidParser::handle_tgc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 14) return {SolidParseResult::Error, "tgc needs 14 values"};
vect_t topA, topB;
double magBaseA = MAGNITUDE(dd.data()+6);
double magBaseB = MAGNITUDE(dd.data()+9);
if (magBaseA < SMALL || magBaseB < SMALL) return {SolidParseResult::Error,"tgc zero-length A or B"};
VSCALE(topA, dd.data()+6, dd[12]/magBaseA);
VSCALE(topB, dd.data()+9, dd[13]/magBaseB);
int ret = mk_tgc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9, topA, topB);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_tgc failed" : ""};
}
SolidParseResult SolidParser::handle_sph(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 4) return {SolidParseResult::Error, "sph needs 4 values"};
int ret = mk_sph(m_out, name.c_str(), dd.data(), dd[3]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_sph failed" : ""};
}
SolidParseResult SolidParser::handle_wir(const std::string &name, const std::string &firstLine) {
int numpts = ComGeomReader::fieldInt(firstLine, 8, 2);
if (numpts <= 0) return {SolidParseResult::Error, "wir invalid point count"};
int total = numpts*3 + 1;
std::vector<fastf_t> raw;
if (!getSolData(raw, total, m_solWork, true, firstLine))
return {SolidParseResult::Error, "wir data read failure"};
if ((int)raw.size() < total) return {SolidParseResult::Error, "wir insufficient data"};
double radius = raw.back();
if (radius <= 0.0) return {SolidParseResult::Error, "wir invalid radius"};
double diameter = radius * 2.0;
struct bu_list head;
BU_LIST_INIT(&head);
for (int i=0;i<numpts;++i) {
struct wdb_pipe_pnt *ps;
BU_ALLOC(ps, struct wdb_pipe_pnt);
ps->l.magic = WDB_PIPESEG_MAGIC;
VMOVE(ps->pp_coord, &raw[i*3]);
ps->pp_id = 0;
ps->pp_od = diameter;
ps->pp_bendradius = diameter;
BU_LIST_INSERT(&head, &ps->l);
}
int ret = mk_pipe(m_out, name.c_str(), &head);
mk_pipe_free(&head);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_pipe failed" : ""};
}
SolidParseResult SolidParser::handle_rpc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 10) return {SolidParseResult::Error, "rpc needs 10 values"};
int ret = mk_rpc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rpc failed" : ""};
}
SolidParseResult SolidParser::handle_rhc(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 11) return {SolidParseResult::Error, "rhc needs 11 values"};
int ret = mk_rhc(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_rhc failed" : ""};
}
SolidParseResult SolidParser::handle_epa(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 11) return {SolidParseResult::Error, "epa needs 11 values"};
int ret = mk_epa(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_epa failed" : ""};
}
SolidParseResult SolidParser::handle_ehy(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "ehy needs 12 values"};
int ret = mk_ehy(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10], dd[11]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_ehy failed" : ""};
}
SolidParseResult SolidParser::handle_eto(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 11) return {SolidParseResult::Error, "eto needs 11 values"};
int ret = mk_eto(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd[9], dd[10]);
return {ret < 0 ? SolidParseResult::Error : SolidParseResult::Ok, ret < 0 ? "mk_eto failed" : ""};
}
SolidParseResult SolidParser::handle_v4_ell_transform(const std::string &name, const std::vector<fastf_t> &foci) {
if (foci.size() < 7) return {SolidParseResult::Error, "v4 ell needs 7 values"};
vect_t F1,F2; VMOVE(F1,&foci[0]); VMOVE(F2,&foci[3]);
double L = foci[6];
vect_t V; VADD2SCALE(V,F1,F2,0.5);
vect_t work; VSUB2(work,F2,F1);
double m1 = MAGNITUDE(work);
if (m1 < SMALL) return {SolidParseResult::Error,"degenerate ell"};
double a_scale = (L*0.5)/m1;
vect_t baseA; VSCALE(baseA, work, a_scale);
double r = std::sqrt(MAGSQ(baseA) - (m1*0.5)*(m1*0.5));
if (!(r>0.0)) return {SolidParseResult::Error,"invalid ell radius"};
vect_t Vp{V[0]+M_PI, V[1]+M_PI, V[2]+M_PI};
vect_t B; VCROSS(B,Vp,baseA); double mb=MAGNITUDE(B); if (mb<SMALL) return {SolidParseResult::Error,"degenerate B"};
VSCALE(B,B,r/mb);
vect_t C; VCROSS(C,baseA,B); double mc=MAGNITUDE(C); if (mc<SMALL) return {SolidParseResult::Error,"degenerate C"};
VSCALE(C,C,r/mc);
int ret = mk_ell(m_out, name.c_str(), V, baseA, B, C);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
}
SolidParseResult SolidParser::handle_ell(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 7) return {SolidParseResult::Error, "ell needs 7 values"};
vect_t V, A; VMOVE(V,&dd[0]); VMOVE(A,&dd[3]);
double r = dd[6]; if (r <= 0.0) return {SolidParseResult::Error,"invalid ell radius"};
vect_t Vp{V[0]+M_PI,V[1]+M_PI,V[2]+M_PI};
vect_t B; VCROSS(B,Vp,A); double mb=MAGNITUDE(B); if (mb<SMALL) return {SolidParseResult::Error,"degenerate B"};
VSCALE(B,B,r/mb);
vect_t C; VCROSS(C,A,B); double mc=MAGNITUDE(C); if (mc<SMALL) return {SolidParseResult::Error,"degenerate C"};
VSCALE(C,C,r/mc);
int ret = mk_ell(m_out, name.c_str(), V, A, B, C);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
}
SolidParseResult SolidParser::handle_ellg(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 12) return {SolidParseResult::Error, "ellg needs 12 values"};
int ret = mk_ell(m_out, name.c_str(), dd.data(), dd.data()+3, dd.data()+6, dd.data()+9);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_ell failed":""};
}
SolidParseResult SolidParser::handle_tor(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 8) return {SolidParseResult::Error, "tor needs 8 values"};
int ret = mk_tor(m_out, name.c_str(), dd.data(), dd.data()+3, dd[6], dd[7]);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_tor failed":""};
}
SolidParseResult SolidParser::handle_haf(const std::string &name, const std::vector<fastf_t> &dd) {
if (dd.size() < 4) return {SolidParseResult::Error, "haf needs 4 values"};
vect_t N; VMOVE(N,&dd[0]);
double magN = MAGNITUDE(N);
if (magN < SMALL) return {SolidParseResult::Error,"haf normal too small"};
int ret = mk_half(m_out, name.c_str(), N, -dd[3]);
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_half failed":""};
}
SolidParseResult SolidParser::handle_arbn(const std::string &name, const std::string &firstLine) {
std::string_view sv(firstLine);
int npt = ComGeomReader::fieldInt(sv, 10 + 0*10, 10);
int npe = ComGeomReader::fieldInt(sv, 10 + 1*10, 10);
int neq = ComGeomReader::fieldInt(sv, 10 + 2*10, 10);
int nae = ComGeomReader::fieldInt(sv, 10 + 3*10, 10);
int nface = npe + neq + nae;
if (npt < 1) return {SolidParseResult::Error, "arbn requires >=1 point"};
if (nface < 1) return {SolidParseResult::Error, "arbn requires >=1 face"};
std::vector<plane_t> eqn(static_cast<size_t>(nface));
std::vector<int> used(static_cast<size_t>(nface), 0);
std::vector<fastf_t> input_points;
input_points.reserve(npt*3);
struct bn_tol tol;
tol.magic = BN_TOL_MAGIC;
tol.dist = 0.0005;
tol.dist_sq = tol.dist * tol.dist;
tol.perp = 1e-6;
tol.para = 1 - tol.perp;
{ // vertex points
std::vector<fastf_t> pts;
if (!getXSolData(pts, npt*3, m_solWork))
return {SolidParseResult::Error, "arbn vertex read failure"};
input_points = std::move(pts);
}
int cur_eq = 0;
int symm = 0;
/* Planes from triples */
for (int i=0;i<npe;i+=6) {
auto l = m_r.next("arbn vertex point indices");
if (!l) return {SolidParseResult::Error,"arbn EOF (point indices)"};
std::string_view lv(l->s);
for (int j=0;j<6 && (i+j)<npe;++j) {
int q = ComGeomReader::fieldInt(lv, 10 + j*10 + 0, 4);
int r = ComGeomReader::fieldInt(lv, 10 + j*10 + 4, 3);
int s = ComGeomReader::fieldInt(lv, 10 + j*10 + 7, 3);
if (q==0 || r==0 || s==0) continue;
point_t a,b,c;
auto fetch = [&](int idx, point_t &out){
int abs_idx = std::abs(idx)-1;
VMOVE(out, &input_points[abs_idx*3]);
if (idx < 0) { out[Y] = -out[Y]; symm = 1; }
};
fetch(q,a); fetch(r,b); fetch(s,c);
if (bg_make_plane_3pnts(eqn[cur_eq], a, b, c, &tol) < 0) {
if (m_opts.verbose) std::fprintf(stderr,"arbn degenerate 3p plane ignored\n");
continue;
}
++cur_eq;
}
}
/* Planes from equations */
for (int i=0;i<neq;++i) {
auto l = m_r.next("arbn plane equation card");
if (!l) return {SolidParseResult::Error,"arbn EOF (equation)"};
std::string_view lv(l->s);
eqn[cur_eq][X] = ComGeomReader::fieldDouble(lv, 10, 10);
eqn[cur_eq][Y] = ComGeomReader::fieldDouble(lv, 20, 10);
eqn[cur_eq][Z] = ComGeomReader::fieldDouble(lv, 30, 10);
eqn[cur_eq][W] = ComGeomReader::fieldDouble(lv, 40, 10);
double scale = MAGNITUDE(eqn[cur_eq]);
if (scale < SMALL) {
if (m_opts.verbose) std::fprintf(stderr,"arbn plane normal too small (eqn)\n");
continue;
}
scale = 1.0/scale;
VSCALE(eqn[cur_eq], eqn[cur_eq], scale);
eqn[cur_eq][W] *= scale;
++cur_eq;
}
/* Planes from az/el */
for (int i=0;i<nae;i+=2) {
auto l = m_r.next("arbn az/el card");
if (!l) return {SolidParseResult::Error,"arbn EOF (az/el)"};
std::string_view lv(l->s);
for (int j=0;j<2 && (i+j)<nae;++j) {
double az = ComGeomReader::fieldDouble(lv, 10 + j*30 + 0*10, 10) * DEG2RAD;
double el = ComGeomReader::fieldDouble(lv, 10 + j*30 + 1*10, 10) * DEG2RAD;
int vert_no = ComGeomReader::fieldInt(lv, 10 + j*30 + 2*10, 10);
if (vert_no == 0) break;
double cos_el = std::cos(el);
eqn[cur_eq][X] = std::cos(az) * cos_el;
eqn[cur_eq][Y] = std::sin(az) * cos_el;
eqn[cur_eq][Z] = std::sin(el);
point_t pt;
int abs_idx = std::abs(vert_no)-1;
VMOVE(pt, &input_points[abs_idx*3]);
if (vert_no < 0) pt[Y] = -pt[Y];
eqn[cur_eq][W] = VDOT(pt, eqn[cur_eq]);
++cur_eq;
}
}
if (cur_eq != nface)
return {SolidParseResult::Error, "arbn face count mismatch"};
point_t cent; VSETALL(cent, 0);
for (int i=0;i<npt;++i) VADD2(cent, cent, &input_points[i*3]);
VSCALE(cent, cent, 1.0/static_cast<double>(npt));
if (symm) cent[Y] = 0.0;
constexpr double DIST_TOL = 1.0e-8;
for (int i=0;i<nface;++i) {
double dist = VDOT(eqn[i], cent) - eqn[i][W];
if (dist < -DIST_TOL) continue;
if (dist > DIST_TOL) {
VREVERSE(eqn[i], eqn[i]);
eqn[i][W] = -eqn[i][W];
} else {
return {SolidParseResult::Error,"arbn centroid lies on face"};
}
}
std::vector<double> vertex;
int last_vertex = 0;
for (int i=0;i<nface-2;++i) {
for (int j=i+1;j<nface-1;++j) {
double dot = VDOT(eqn[i], eqn[j]);
if (!NEAR_ZERO(dot, 0.999999)) continue;
for (int k=j+1;k<nface;++k) {
point_t pt;
if (bg_make_pnt_3planes(pt, eqn[i], eqn[j], eqn[k]) < 0) continue;
bool outside=false;
for (int m=0;m<nface;++m) {
if (m==i||m==j||m==k) continue;
if (VDOT(pt, eqn[m]) - eqn[m][W] > DIST_TOL) { outside=true; break; }
}
if (outside) continue;
bool duplicate=false;
for (int m=0;m<last_vertex;++m) {
vect_t dv; VSUB2(dv, pt, &vertex[m*3]);
if (MAGSQ(dv) < 1.0e-10) { duplicate=true; break; }
}
if (duplicate) continue;
vertex.resize((last_vertex+1)*3);
VMOVE(&vertex[last_vertex*3], pt);
++last_vertex;
used[i]++; used[j]++; used[k]++;
}
}
}
for (int i=0;i<nface;++i)
if (used[i]==0)
return {SolidParseResult::Error,"arbn face unused (not convex)"};
int ret = mk_arbn(m_out, name.c_str(), nface, eqn.data());
return {ret<0?SolidParseResult::Error:SolidParseResult::Ok, ret<0?"mk_arbn failed":""};
}
/* ---------------- Dispatcher ---------------- */
SolidParseResult SolidParser::parseNext() {
if (m_solWork >= m_solTotal && m_solTotal > 0) {
return {SolidParseResult::Eof, ""};
}
auto l = m_r.next("solid card");
if (!l) return {SolidParseResult::Eof, ""};
int solidNum = 0;
std::string type, firstLine = l->s;
if (!parseSolidHeader(l->s, solidNum, type, firstLine)) {
if (m_solWork >= m_solTotal) return {SolidParseResult::Eof, ""};
return {SolidParseResult::Error, "invalid solid header"};
}
// v1 end sentinel handled in region code; in solids, "end" terminates
if (type == "end") {
return {SolidParseResult::Eof, ""};
}
if (m_opts.version == 5) {
int expectedNext = m_solWork + 1;
if (solidNum != expectedNext) {
// proceed but warn
std::fprintf(stderr,
"warning: solid numbering mismatch: expected %d, got %d - consuming this solid to maintain alignment\n",
expectedNext, solidNum);
}
}
// Always advance processed count; names use sequential counter
m_solWork++;
std::string name = makeSolidName(m_solWork);
colPrint(name);
std::vector<fastf_t> dd;
if (type == "ars") return handle_ars(name, firstLine);
if (type == "rpp") { if(!getSolData(dd,6,solidNum,true,firstLine)) return {SolidParseResult::Error,"rpp data read"}; return handle_rpp(name,dd); }
if (type == "box") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"box data read"}; return handle_box(name,dd); }
if (type == "raw" || type == "wed") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"raw/wed data read"}; return handle_raw_wed(name,dd); }
if (type == "rvw") { if(!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"rvw data read"}; return handle_rvw(name,dd); }
if (type == "arw") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"arw data read"}; return handle_arw(name,dd); }
if (m_opts.version == 0 && type == "arb") return handle_arb_magic(name, firstLine);
if (type == "arb4") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb4 data read"}; return handle_arb4(name,dd); }
if (type == "arb5") { if(!getSolData(dd,15,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb5 data read"}; return handle_arb5(name,dd); }
if (type == "arb6") { if(!getSolData(dd,18,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb6 data read"}; return handle_arb6(name,dd); }
if (type == "arb7") { if(!getSolData(dd,21,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb7 data read"}; return handle_arb7(name,dd); }
if (type == "arb8") { if(!getSolData(dd,24,solidNum,true,firstLine)) return {SolidParseResult::Error,"arb8 data read"}; return handle_arb8(name,dd); }
if (type == "rcc") { if(!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"rcc data read"}; return handle_rcc(name,dd); }
if (type == "rec") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"rec data read"}; return handle_rec(name,dd); }
if (type == "trc") { if(!getSolData(dd,8,solidNum,true,firstLine)) return {SolidParseResult::Error,"trc data read"}; return handle_trc(name,dd); }
if (type == "tec") { if(!getSolData(dd,13,solidNum,true,firstLine)) return {SolidParseResult::Error,"tec data read"}; return handle_tec(name,dd); }
if (type == "tgc") { if(!getSolData(dd,14,solidNum,true,firstLine)) return {SolidParseResult::Error,"tgc data read"}; return handle_tgc(name,dd); }
if (type == "sph") { if(!getSolData(dd,4,solidNum,true,firstLine)) return {SolidParseResult::Error,"sph data read"}; return handle_sph(name,dd); }
if (type.rfind("wir",0)==0) return handle_wir(name, firstLine);
if (type == "rpc") { if(!getSolData(dd,10,solidNum,true,firstLine)) return {SolidParseResult::Error,"rpc data read"}; return handle_rpc(name,dd); }
if (type == "rhc") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"rhc data read"}; return handle_rhc(name,dd); }
if (type == "epa") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"epa data read"}; return handle_epa(name,dd); }
if (type == "ehy") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"ehy data read"}; return handle_ehy(name,dd); }
if (type == "eto") { if(!getSolData(dd,11,solidNum,true,firstLine)) return {SolidParseResult::Error,"eto data read"}; return handle_eto(name,dd); }
if (type == "ellg") { if(!getSolData(dd,12,solidNum,true,firstLine)) return {SolidParseResult::Error,"ellg data read"}; return handle_ellg(name,dd); }
if (type == "ell" || type == "ell1") {
if (m_opts.version <=4 && type=="ell") {
if (!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"v4 ell data read"};
return handle_v4_ell_transform(name, dd);
} else {
if (!getSolData(dd,7,solidNum,true,firstLine)) return {SolidParseResult::Error,"ell data read"};
return handle_ell(name, dd);
}
}
if (type == "tor") { if(!getSolData(dd,8,solidNum,true,firstLine)) return {SolidParseResult::Error,"tor data read"}; return handle_tor(name,dd); }
if (type == "haf") { if(!getSolData(dd,4,solidNum,true,firstLine)) return {SolidParseResult::Error,"haf data read"}; return handle_haf(name,dd); }
if (type == "arbn") return handle_arbn(name, firstLine);
std::fprintf(stderr, "getsolid: unsupported type '%s'\n", type.c_str());
return {SolidParseResult::Error, "unsupported solid"};
}
+93
View File
@@ -0,0 +1,93 @@
#ifndef BRLCAD_SOLID_PARSER_H
#define BRLCAD_SOLID_PARSER_H
#include "common.h"
#include <string>
#include <vector>
#include <optional>
extern "C" {
#include "vmath.h"
#include "raytrace.h"
#include "wdb.h"
#include "bg.h"
}
#include "ComGeomReader.h"
struct SolidParseResult {
enum Status { Ok, Eof, Error } status = Error;
std::string message;
};
struct SolidParserOptions {
int version = 5;
int verbose = 0;
std::string suffix;
bool legacy_trunc = false;
};
class SolidParser {
public:
SolidParser(ComGeomReader &reader, rt_wdb *outfp, const SolidParserOptions &opts);
SolidParseResult parseNext();
int solidsExpected() const { return m_solTotal; }
int solidsProcessed() const { return m_solWork; }
void setSolidsExpected(int c) { m_solTotal = c; }
private:
std::string makeSolidName(int n) const;
void colPrint(const std::string &s);
bool parseSolidHeader(const std::string &line, int &solidNum,
std::string &typeLower, std::string &lineOutForData);
bool getSolData(std::vector<fastf_t> &data, int num, int solidNum,
bool firstCardAlreadyRead, std::string lineBuf);
bool getXSolData(std::vector<fastf_t> &data, int num, int solidNum);
/* Helpers */
bool isIntegerField(std::string_view sv, size_t start, size_t len, int &value) const;
bool isLikelyFaceCard(const std::string &line) const;
/* Primitive handlers */
SolidParseResult handle_ars(const std::string &name, const std::string &firstLine);
SolidParseResult handle_rpp(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_box(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_raw_wed(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rvw(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_arw(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_arb_magic(const std::string &name, const std::string &firstLine);
SolidParseResult handle_arb4(const std::string &name, const std::vector<fastf_t> &dd4);
SolidParseResult handle_arb5(const std::string &name, const std::vector<fastf_t> &dd5);
SolidParseResult handle_arb6(const std::string &name, const std::vector<fastf_t> &dd6);
SolidParseResult handle_arb7(const std::string &name, const std::vector<fastf_t> &dd7);
SolidParseResult handle_arb8(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rcc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rec(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_trc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_tec(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_tgc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_sph(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_wir(const std::string &name, const std::string &firstLine);
SolidParseResult handle_rpc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_rhc(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_epa(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_ehy(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_eto(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_v4_ell_transform(const std::string &name, const std::vector<fastf_t> &foci);
SolidParseResult handle_ell(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_ellg(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_tor(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_haf(const std::string &name, const std::vector<fastf_t> &dd);
SolidParseResult handle_arbn(const std::string &name, const std::string &firstLine);
private:
ComGeomReader &m_r;
rt_wdb *m_out = nullptr;
SolidParserOptions m_opts;
int m_solTotal = 0;
int m_solWork = 0;
size_t m_colCur = 0;
};
#endif
@@ -0,0 +1,376 @@
================================================================================
BRL-CAD BoT → COMGEOM ARBN Export Pipeline
RLM (Robust Low-Poly Meshing Inspired) + HACD/V-HACD Convex Decomposition
================================================================================
Purpose:
--------
Provide a clear, implementation-oriented blueprint for converting arbitrary
triangle meshes (BOTs) into a set of convex COMGEOM ARBN solids suitable for
region assembly. This document is intended both for human developers and as
context for future GPT-assisted sessions.
Context:
--------
COMGEOM does not have a native arbitrary triangle mesh primitive. ARBN
(primitives defined by intersection of half-spaces / planes) can represent
convex polyhedra only. Arbitrary non-convex BOT geometry must be decomposed
into convex parts, each described as an ARBN. To reduce complexity and noise,
we first simplify and planarize the mesh with an RLM-inspired stage, then
decompose using HACD/V-HACD, and finally produce ARBNs.
High-Level Pipeline Stages:
---------------------------
1. Mesh Acquisition & Validation
2. RLM-Inspired Simplification (Planar Clustering & Polygon Extraction)
3. Optional Primitive Recognition (Planes, Cylinders, Cones) (future enhancement)
4. Convex Decomposition (HACD/V-HACD or fallback hull partitioning)
5. Convex Part → Plane Set Conversion
6. ARBN Construction via Incremental Half-Space Clipping
7. Region Assembly (OR union of convex ARBN solids)
8. Quality Assurance (QA) & Reporting
9. Output COMGEOM Deck (version selectable: v1 / v4 / v5)
10. Optional JSON summary and statistics
Key Design Goals:
-----------------
- Produce clean, planar-faced convex solids minimizing plane count.
- Heal small cracks, near-coplanar face fragmentation, and sliver triangles.
- Provide tunable tolerances for clustering, plane fitting, snapping, and decomposition.
- Maintain sharp features (edges with large dihedral angles).
- Scale to large meshes (hundreds of thousands of triangles) via segmentation.
Terminology:
------------
BOT : BRL-CAD triangle mesh primitive.
RLM-inspired : Robust Low-Poly Meshing style operations (plane clustering, feature retention).
HACD/V-HACD : Hierarchical Approximate Convex Decomposition algorithms.
ARBN : COMGEOM arbitrary N-plane convex solid.
Plane cluster : Group of triangles forming an approximate planar patch.
Convex part : Output of HACD or grouping guaranteed to be convex.
Clipper : Incremental half-space intersection algorithm producing ARBN vertices.
Region : COMGEOM Boolean combination (union/intersection/subtraction) of solids.
Assumptions:
------------
- BOT input is either closed and manifold or can be repaired (hole fill / thickening).
- Decomposition tools are available (HACD or V-HACD library; otherwise fallback convex hull).
- Numeric tolerances are chosen relative to model scale (bbox diagonal).
- Complexity controls (max planes per ARBN, max clusters, max convex parts) prevent runaway resource usage.
Environment / Flags (Proposed):
-------------------------------
--simplify=none|rlm : Enable RLM-stage planar clustering.
--rlm-angle=deg : Angle threshold for normal clustering (default 2.0).
--rlm-dist=scale : Distance tolerance for plane residual (default 1e-3 * bbox_diag).
--rlm-snap=scale : Vertex snap tolerance (default 5e-4 * bbox_diag).
--rlm-sharp=deg : Dihedral angle above which edges are preserved (default 30).
--rlm-min-area=scale^2 : Minimum polygon area; discard degenerates.
--hacd=on|off : Enable HACD decomposition stage.
--hacd-concavity=value : HACD concavity parameter.
--hacd-max-parts=N : Max number of convex parts.
--hacd-min-volume=scale^3 : Minimum part volume threshold.
--decomp-order=pre|post : RLM before or after HACD (default pre).
--max-planes-per-arbn=N : Safety cap (default 512).
--arbn-hash=on|off : Spatial hash for vertex deduplication (default on).
--arbn-stats=path : Stats dump file.
--export-version=1|4|5 : COMGEOM dialect (default 5).
--json=path : Emit JSON export report.
--material-map=file.json : Attribute enrichment mapping.
--color-map=file.json : Region color mapping.
Stage 1: Mesh Acquisition & Validation
--------------------------------------
Inputs: BOT primitive (triangle list).
Steps:
1. Load triangles; compute bounding box.
2. Verify manifold conditions:
- Each edge referenced by 1 or 2 faces.
- No zero-area triangles.
3. If open surface and user requests solid export:
- Either abort or apply thickening (offset + stitch) – optional future feature.
4. Deduplicate vertices (hash by quantized position).
5. Compute per-face normals and edge dihedral angles.
Outputs:
- Clean triangle list, adjacency (half-edge or edge-to-face map).
- BBox, scale metrics.
Stage 2: RLM-Inspired Simplification
------------------------------------
Goal: Merge nearly coplanar triangles into planar clusters.
Algorithm Sketch:
1. Seed clusters: each triangle forms initial cluster with its plane.
2. Normal clustering: merge cluster pairs if angle between planes < rlm-angle AND distance between centroids projects within rlm-dist residual.
3. Plane fitting: For each cluster, fit plane via PCA/least squares to vertex set. Recompute residuals.
4. Split clusters where residual > rlm-dist or non-manifold boundaries arise.
5. Extract boundary polygon:
- Collect boundary edges (adjacent triangle only once).
- Project boundary vertices to plane.
- Compute 2D convex (or general boundary) hull; remove interior points.
6. Vertex snapping:
- Project vertices onto fitted plane.
- Merge vertices within rlm-snap tolerance.
7. Sharp edges retention:
- Do not merge clusters across edges with dihedral > rlm-sharp.
8. Degeneracy filtering:
- Discard polygons with area < rlm-min-area or internal angle < min_angle (~5°).
9. Optional: Triangulate polygon if required by downstream (for HACD only if non-convex planar patch).
Outputs:
- Reduced set of planar patches (clusters) with cleaned boundary polygons.
- Mapping from original faces to cluster.
Stage 3: Optional Primitive Recognition (Future Enhancement)
------------------------------------------------------------
Idea: Detect canonical primitives (planes, cylinders, spheres, cones) before clustering to reduce complexity:
- RANSAC or multi-pass fitting.
- Replace large planar clusters with a BOX or RPP if axis-aligned.
- Defer for initial implementation.
Stage 4: Convex Decomposition (HACD/V-HACD)
-------------------------------------------
If mesh (post-RLM) remains non-convex:
1. Run HACD on simplified mesh.
- Inputs: tolerance (concavity), max parts, min volume.
2. Obtain convex parts (triangle subsets).
3. For each convex part:
- Optionally re-run a localized RLM stage again (Mode: post-hacd).
4. If HACD disabled:
- Option hull-only: compute global convex hull (loss of concavity details).
- Reject or warn if non-convex and no decomposition path chosen.
Outputs:
- Collection of convex parts (each either a polygonal shell).
Stage 5: Convex Part → Plane Set Conversion
-------------------------------------------
For each convex part:
1. Compute convex hull of part (even if already convex; double-check).
2. Extract hull faces; each face yields a plane (normal outward, distance).
3. Normalize planes; cluster near-duplicates (angle & offset tolerance).
4. Verify face consistency (no inverted normals; positive volume).
5. Cap plane count; if > max-planes-per-arbn → either split, relax tolerance, or fallback to legacy enumeration (warning).
Outputs:
- Plane list per convex part.
Stage 6: ARBN Construction
--------------------------
Use incremental half-space clipping (implemented arbn_clip):
1. Seed bounding polyhedron from plane offsets (derived radius).
2. For each plane in part:
- Clip polyhedron (spatial hash for vertex reuse).
- Abort if volume becomes zero (log error; fallback to hull triangulation?).
3. Gather final vertex set and face loops.
4. Record stats (input planes, duplicates removed, final face/vertex count).
Outputs:
- ARBN solid definition: plane equations only (COMGEOM stores planes; vertex enumeration implicit).
- Optional internal triangulation only for QA (not stored in deck).
Stage 7: Region Assembly
------------------------
Single region for entire original BOT:
- Boolean semantics: Region = OR of all ARBN solids derived from decomposition.
- If decomposition yields components with distinct attributes (e.g., materials), create multiple regions (each corresponding to an attribute class).
- For COMGEOM v1: multiple cards with OR semantics; for v4/v5: parenthesized expression: ( +S1 ) OR ( +S2 ) OR ...
Stage 8: QA & Validation
------------------------
Checks:
1. Volume > 0 and matches (within tolerance) original mesh volume (approx. via voxel sampling or Monte Carlo).
2. Surface area near original (after simplification) minus sliver removal.
3. Watertightness: ray shoot tests (random directions) see consistent in/out transitions.
4. Plane count sanity (no explosion).
5. Overlaps: sampled points belong to exactly one convex part unless intended union.
Stage 9: Output COMGEOM Deck
----------------------------
For each ARBN:
- Solid table entries listing plane equations (v1 fixed columns or v5 token).
Region table:
- Union of solids, optional attributes.
Ident table:
- Region codes, space codes, materials, LOS (v5 attribute lines or numeric fields).
Attribute enhancement (v5):
- material=, color=R/G/B, aircode=, density= if available.
Stage 10: Reporting
-------------------
JSON report keys:
{
"input": { "triangles": N, "bbox": [...], "scale": S },
"rlm": { "clusters": C, "merged_faces": F, "avg_residual": R },
"hacd": { "enabled": true, "parts": P, "concavity": X },
"arbn": { "solids": S, "avg_planes": A, "max_planes": M },
"region": { "count": Rg, "boolean": "OR" },
"stats": { "warnings": W, "errors": E },
"timing": { "rlm_ms": t1, "hacd_ms": t2, "arbn_ms": t3 }
}
Parameter Tuning Guidelines:
----------------------------
angle_tol (cluster) : 1–2 degrees (start small; larger increases merging).
distance_tol (plane fit) : 1e-3 × bbox_diag (reduce for high precision parts).
snap_tol (vertex merge) : 5e-4 × bbox_diag (balance healing vs distortion).
sharp_threshold : 30 degrees (increase to preserve more edges).
hacd_concavity : 0.002–0.01 typical; lower → more parts, higher → fewer.
max_planes_per_arbn : 256–512 recommended safety.
min_face_area : 1e-10 × bbox_diag^2 (filter numerical artifacts).
min_part_volume : 1e-9 × bbox_diag^3 (discard tiny fragments).
Error Handling Strategy:
------------------------
- Non-manifold input → attempt repair; if fails, abort with message.
- Excessive plane count → split cluster or relax clustering thresholds.
- Zero-volume ARBN creation → fallback to convex hull or mark as skipped.
- HACD failure → fallback to hull-only mode with warning.
- Tolerance misconfiguration → auto-suggest adjustments in JSON report.
Performance Considerations:
---------------------------
- Spatial hashing for vertex dedup in ARBN clipper (O(1) average insert/find).
- Parallelization possibilities:
- RLM clustering per spatial block / octree node.
- HACD usually internal multi-threading (depending on library).
- ARBN construction per convex part in parallel threads.
- Memory caps: early discard of clusters under min_face_area prevents growth.
- Streaming: flush solids incrementally to output files.
Security / Robustness Notes:
----------------------------
- Validate all user-supplied tolerances; clamp unreasonable values.
- Protect against extremely large mesh inputs by early bounding checks.
- Consistent use of relative tolerances tied to model scale to avoid numeric collapse.
Interfaces & Extensibility:
---------------------------
Future flags:
--primitive-detect=on/off : Pre-pass for cylinders/cones/spheres.
--thicken-open-mesh=scale : Convert open surfaces to solids.
--exact-predicates=on/off : Introduce robust orientation tests.
--parallel=N : Explicit thread count.
Data Structures (Minimal Outline):
----------------------------------
struct RLMCluster {
vect_t plane_normal;
double plane_offset;
double rms_error;
size_t *face_indices;
size_t face_count;
int sharp_boundary; /* flag */
point_t *boundary_vertices;
size_t boundary_count;
};
struct ConvexPart {
size_t *triangle_indices;
size_t tri_count;
plane_t *planes;
size_t plane_count;
};
struct ARBNSolid {
plane_t *planes;
size_t plane_count;
/* optional stats */
};
Pseudo-Code Summary:
--------------------
function export_bot_to_comgeom(bot, params):
triangles = load(bot)
validate(triangles)
if params.simplify == RLM:
clusters = rlm_cluster(triangles, params)
triangles = rebuild_from_clusters(clusters) // optional
parts = (params.hacd ? hacd_decompose(triangles, params) : [triangles])
solids = []
for part in parts:
planes = extract_planes(part, params)
arbn = build_arbn(planes, params)
if arbn.valid: solids.append(arbn)
region = build_region_union(solids)
write_comgeom(solids, region, params)
write_report(params, stats)
Potential Failure Scenarios & Responses:
----------------------------------------
1. Input mesh massively non-manifold:
- Response: abort early; suggest repair tool invocation.
2. RLM merges too aggressively (loss of moderate detail):
- Response: output warning; advise lower angle_tol.
3. HACD produces thousands of tiny parts:
- Response: raise concavity tolerance; filter by min_part_volume.
4. ARBN plane overflow:
- Response: split part along largest variance axis; recursively process.
5. Output region expression too large:
- Response: chunk region definitions or group solids logically by type.
Testing Strategy:
-----------------
Unit:
- Plane fitting accuracy (synthetic coplanar sets).
- Clustering stability under noise.
- ARBN clipper invariants (volume positive, faces planar).
Integration:
- BOT sample models (simple, moderate, complex).
- Comparison of original vs simplified volumes (Monte Carlo sampling).
- Performance benchmarking (timings for each stage).
Regression:
- Fuzz small random perturbations in vertex positions; ensure pipeline stable.
- Large mesh stress (e.g., 250k triangles) within acceptable memory/time.
JSON Report Example:
--------------------
{
"input": { "triangles": 50234, "bbox": [ [0,0,0], [10,5,7] ], "scale": 12.247 },
"rlm": { "clusters": 340, "merged_faces": 48000, "avg_residual": 0.00072 },
"hacd": { "enabled": true, "parts": 128, "concavity": 0.005 },
"arbn": { "solids": 128, "avg_planes": 18, "max_planes": 42 },
"region": { "count": 1, "boolean": "OR" },
"stats": { "warnings": 3, "errors": 0 },
"timing": { "rlm_ms": 1834, "hacd_ms": 2975, "arbn_ms": 622 }
}
Open Questions (For Future Iteration):
--------------------------------------
- Should we incorporate curved primitive detection before RLM to reduce plane counts further?
- Acceptable loss thresholds for feature simplification (volume delta %, area delta %)?
- Deterministic mode (fixed random seeds) for reproducible CI outputs?
- Support for per-material segmentation feeding separate region definitions?
Recommended Initial Implementation Order:
----------------------------------------
Phase 1: BOT validation + adjacency + dihedral computation
Phase 2: Basic RLM clustering (angle + residual) + boundary extraction
Phase 3: HACD integration (API wrapper) + parameter mapping
Phase 4: Plane extraction + ARBN clipper integration
Phase 5: Region assembly + COMGEOM writing (v5 first)
Phase 6: JSON reporting + stats instrumentation
Phase 7: Tuning + advanced cluster splitting + QA benchmarks
Phase 8: Optional primitive recognition and improved heuristics
Risk Summary:
-------------
- Over-simplification: mitigated by exposing user parameters and reporting cluster stats.
- Performance bottlenecks: spatial hashing, early culling, parallel cluster operations.
- Numerical instability: per-stage tolerance scaling; failover to hull approximations.
- Large memory footprints: streaming cluster processing and releasing intermediate buffers.
References (Conceptual Inspirations):
-------------------------------------
- Robust Low-Poly Meshing (RLM) paper (site referenced).
- HACD / V-HACD algorithms (hierarchical approximate convex decomposition).
- Plane clustering & PCA fitting (standard computational geometry).
- Incremental half-space intersection for convex polyhedra.
- Shewchuk robust predicates (optional future exact arithmetic upgrade).
- RANSAC-based primitive detection (future enhancements).
End of Document
================================================================================
+122
View File
@@ -0,0 +1,122 @@
#include "common.h"
#include <string>
#include <vector>
#include <cstdio>
#include <cstdlib>
extern "C" {
#include "bu/app.h"
#include "bu/opt.h"
#include "bu/vls.h"
}
#include "ComGeomConverter.h"
/* Custom arg processor to copy a string argument into a std::string */
static int
opt_set_string(struct bu_vls *msg, size_t argc, const char **argv, void *set_var)
{
BU_OPT_CHECK_ARGV0(msg, argc, argv, "string");
if (!set_var) return -1;
std::string *sp = static_cast<std::string *>(set_var);
*sp = argv[0];
return 1;
}
struct CliOptions {
ConverterOptions conv;
int show_help = 0;
int legacy_truncate_names = 0;
std::vector<std::string> positionals;
};
static void usage(const char *argv0, const struct bu_opt_desc *opt_defs)
{
std::fprintf(stderr,
"Usage: %s [options] input_file output_file\n\n", argv0);
char *help = bu_opt_describe(opt_defs, NULL);
if (help) {
std::fprintf(stderr, "%s\n", help);
bu_free(help, "help string");
}
std::fprintf(stderr,
"\nExamples:\n"
" %s model.cg model.g # convert using defaults (version 5)\n"
" %s -v 0 in_magic.cg out.g # convert MAGIC-era v0 deck\n"
" %s -v 4 -s _cg4 in.cg out.g # convert v4, append suffix to names\n"
" %s -t -s _longsuffix in.cg out.g # legacy suffix truncation (13 chars)\n"
" %s -d 2 input.cg out.g # verbose debug level 2\n",
argv0, argv0, argv0, argv0, argv0);
}
int main(int argc, const char **argv)
{
bu_setprogname(argv[0]);
CliOptions co;
struct bu_opt_desc opt_defs[7];
BU_OPT(opt_defs[0], "h", "help", "", NULL, &co.show_help, "Show help and exit.");
BU_OPT(opt_defs[1], "v", "version", "N", &bu_opt_int, &co.conv.version, "Set COMGEOM input version (0,1,4,5; default 5).");
BU_OPT(opt_defs[2], "d", "debug", "N", &bu_opt_int, &co.conv.verbose, "Set verbosity/debug level (integer).");
BU_OPT(opt_defs[3], "s", "suffix", "STR", opt_set_string, &co.conv.suffix, "Append name suffix to all generated objects.");
BU_OPT(opt_defs[4], "V", "verbose", "", &bu_opt_incr_long,&co.conv.verbose, "Increment verbosity (may be repeated).");
BU_OPT(opt_defs[5], "t", "legacy-truncate-names", "", NULL, &co.legacy_truncate_names, "Enable legacy name truncation (suffix max 13 chars).");
BU_OPT_NULL(opt_defs[6]);
std::vector<const char *> argvec;
argvec.reserve(static_cast<size_t>(argc - 1));
for (int i = 1; i < argc; ++i) argvec.push_back(argv[i]);
struct bu_vls msgs = BU_VLS_INIT_ZERO;
int ret = bu_opt_parse(&msgs, argvec.size(), argvec.data(), opt_defs);
if (ret < 0) {
if (BU_VLS_IS_INITIALIZED(&msgs) && bu_vls_strlen(&msgs) > 0)
std::fprintf(stderr, "%s", bu_vls_addr(&msgs));
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 1;
}
size_t unused = static_cast<size_t>(ret);
for (size_t i = 0; i < unused; ++i)
co.positionals.emplace_back(argvec[i]);
if (co.show_help) {
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 0;
}
if (co.positionals.size() != 2) {
std::fprintf(stderr,
"Error: expected 2 positional arguments (input_file output_file), got %zu.\n",
co.positionals.size());
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 1;
}
if (co.conv.version != 0 && co.conv.version != 1 && co.conv.version != 4 && co.conv.version != 5) {
std::fprintf(stderr, "Error: unsupported COMGEOM version %d (valid: 0,1,4,5).\n",
co.conv.version);
usage(argv[0], opt_defs);
bu_vls_free(&msgs);
return 1;
}
if (BU_VLS_IS_INITIALIZED(&msgs) && bu_vls_strlen(&msgs) > 0 && co.conv.verbose)
std::fprintf(stderr, "%s", bu_vls_addr(&msgs));
bu_vls_free(&msgs);
const std::string inputPath = co.positionals[0];
const std::string outputPath = co.positionals[1];
co.conv.legacy_truncate_names = (co.legacy_truncate_names != 0);
ComGeomConverter converter(inputPath, outputPath, co.conv);
return converter.run();
}
+1
View File
@@ -42,6 +42,7 @@ if(BRLCAD_ENABLE_QT AND OPENGL_TARGETS)
Qt6::Widgets
Qt6::OpenGL
Qt6::OpenGLWidgets
${OPENGL_TARGETS}
)
else(Qt6Widgets_FOUND)
qt5_wrap_cpp(moc_srcs ${isst_headers})
+15 -52
View File
@@ -26,7 +26,6 @@
#include <QFile>
#include <QPlainTextEdit>
#include <QTextStream>
#include <vector>
#include "gfile.h"
#include "bu/malloc.h"
@@ -34,7 +33,6 @@
#include "gcv.h"
#include "nmg.h"
#include "rt/geom.h"
#include "load_g.h"
/* Replace the load_g globals from adrt with a struct that is
* passed through the callers. */
@@ -104,8 +102,7 @@ nmg_to_adrt_internal(TIE_3 **tribuf, struct tie_s *cur_tie, struct adrt_mesh_s *
NMG_CK_VERTEX(v);
/* convert mm to m */
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord,
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*tribuf[vert_count]).v, v->vg_p->coord, 1.0/1000.0);
vert_count++;
}
if (vert_count > 3)
@@ -188,12 +185,9 @@ nmg_to_adrt_regstart(struct db_tree_state *ts, const struct db_full_path *path,
for (i=0;i<bot->num_faces;i++)
{
VSCALE((*d->tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]),
1.0 / ADRT_MODEL_UNITS_PER_TIE_UNIT);
VSCALE((*d->tribuf[0]).v, (bot->vertices+3*bot->faces[3*i+0]), 1.0/1000.0);
VSCALE((*d->tribuf[1]).v, (bot->vertices+3*bot->faces[3*i+1]), 1.0/1000.0);
VSCALE((*d->tribuf[2]).v, (bot->vertices+3*bot->faces[3*i+2]), 1.0/1000.0);
TIE_VAL(tie_push)(d->cur_tie, d->tribuf, 1, mesh, 0);
}
@@ -239,13 +233,6 @@ nmg_to_adrt_gcvwrite(struct nmgregion *r, const struct db_full_path *pathp, stru
nmg_to_adrt_internal(d->tribuf, d->cur_tie, mesh, r);
}
GFile::~GFile()
{
closedb();
}
int
GFile::load_g(const char *filename, int argc, const char *argv[])
{
@@ -254,9 +241,6 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
struct bg_tess_tol ttol; /* tessellation tolerance in mm */
struct db_tree_state tree_state; /* includes tol & model */
struct isst_nmg_data d;
std::vector<const char *> geometry_paths;
closedb();
RT_DBTS_INIT(&tree_state);
tree_state.ts_tol = &tol;
@@ -294,8 +278,6 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
}
if (db_dirbuild(dbip)) {
bu_log("ERROR: db_dirbuild failed\n");
db_close(dbip);
dbip = NULL;
return -1;
}
d.dbip = dbip;
@@ -322,30 +304,20 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
tribuf[2] = (TIE_3 *)bu_malloc(sizeof(TIE_3) * 3, "triangle tribuffer");
d.tribuf = this->tribuf;
annotations = rt_annot_scene_create(dbip, argc, argv, &ttol, &tol);
for (int i = 0; i < argc; ++i) {
if (adrt_path_has_geometry(dbip, argv[i]))
geometry_paths.push_back(argv[i]);
}
if (!geometry_paths.empty()) {
(void) db_walk_tree(dbip,
(int)geometry_paths.size(), /* number of toplevel regions */
geometry_paths.data(), /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
adrt_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
}
(void) db_walk_tree(dbip,
argc, /* number of toplevel regions */
argv, /* region names */
1, /* ncpu */
&tree_state, /* initial tree state */
nmg_to_adrt_regstart, /* region start function */
gcv_region_end, /* region end function */
rt_booltree_leaf_tess, /* leaf func */
(void *)&gcvwriter); /* client data */
/* Release dynamic storage */
nmg_km(the_model);
rt_vlist_cleanup();
db_close(dbip);
dbip = NULL;
bu_free(tribuf[0], "vert");
bu_free(tribuf[1], "vert");
bu_free(tribuf[2], "vert");
@@ -359,17 +331,7 @@ GFile::load_g(const char *filename, int argc, const char *argv[])
void
GFile::closedb()
{
rt_annot_scene_destroy(annotations);
annotations = NULL;
if (tie) {
TIE_FREE(tie);
bu_free(tie, "free tie");
tie = NULL;
}
if (dbip) {
db_close(dbip);
dbip = NULL;
}
bu_free(tie, "free tie");
current_file.clear();
}
@@ -383,3 +345,4 @@ GFile::closedb()
* End:
* ex: shiftwidth=4 tabstop=8
*/
+5 -8
View File
@@ -32,7 +32,6 @@
extern "C" {
#include "bn/tol.h"
#include "raytrace.h"
#include "rt/primitives/annot.h"
#include "rt/tie.h"
#include "adrt.h"
@@ -44,16 +43,13 @@ class GFile : public QObject
Q_OBJECT
public:
~GFile() override;
int load_g(const char *filename, int argc, const char **argv);
void closedb();
struct tie_s *tie = NULL;
struct adrt_mesh_s *meshes = NULL;
struct db_i *dbip = NULL;
struct rt_annot_scene *annotations = NULL;
TIE_3 **tribuf = NULL;
struct tie_s *tie;
struct adrt_mesh_s *meshes;
struct db_i *dbip;
TIE_3 **tribuf;
QString current_file;
};
@@ -70,3 +66,4 @@ class GFile : public QObject
* End:
* ex: shiftwidth=4 tabstop=8
*/
+2 -4
View File
@@ -28,9 +28,6 @@
int
ISSTApp::load_g(const char *filename, int argc, const char *argv[])
{
/* Synchronize with the render thread before GFile releases the old
* annotation scene and TIE data. */
w.canvas->clear_tie();
if (g.load_g(filename, argc, argv)) {
w.statusBar()->showMessage("open failed");
return -1;
@@ -38,7 +35,7 @@ ISSTApp::load_g(const char *filename, int argc, const char *argv[])
// The OpenGL widget manages the rendering, so let it know about the
// TIE data structure associated with the current model
w.canvas->set_tie(g.tie, g.annotations);
w.canvas->set_tie(g.tie);
return 0;
}
@@ -54,3 +51,4 @@ ISSTApp::load_g(const char *filename, int argc, const char *argv[])
* End:
* ex: shiftwidth=4 tabstop=8
*/
+4 -1
View File
@@ -36,14 +36,16 @@
class ISSTApp : public QApplication
{
Q_OBJECT
public:
ISSTApp(int &argc, char *argv[]) :QApplication(argc, argv) {};
~ISSTApp() {};
int load_g(const char *filename, int argc, const char **argv);
GFile g;
ISST_MainWindow w;
GFile g;
};
#endif // ISSTAPP_H
@@ -58,3 +60,4 @@ class ISSTApp : public QApplication
* End:
* ex: shiftwidth=4 tabstop=8
*/
+9 -28
View File
@@ -159,13 +159,8 @@ void TIERenderer::render()
m_grabCond.wait(&m_grabMutex);
QMutexLocker lock(&m_renderMutex);
m_grabMutex.unlock();
/* The GUI may clear a scene while a queued render is waiting for the
* context. Return the context before leaving that stale request. */
if (m_exiting || !tie) {
ctx->moveToThread(qGuiApp->thread());
if (m_exiting)
return;
}
Q_ASSERT(ctx->thread() == QThread::currentThread());
@@ -188,8 +183,7 @@ void TIERenderer::render()
// Core TIE render
render_camera_prep(&camera);
render_camera_render_annotations(&camera, tie, &tile, &buffer_image,
annotations, ADRT_MODEL_UNITS_PER_TIE_UNIT);
render_camera_render(&camera, tie, &tile, &buffer_image);
glDisable(GL_LIGHTING);
@@ -303,31 +297,17 @@ void isstGL::grabContext()
}
void
isstGL::clear_tie()
isstGL::set_tie(struct tie_s *in_tie)
{
m_renderer->lockRenderer();
m_renderer->tie = NULL;
m_renderer->annotations = NULL;
m_renderer->changed = false;
m_renderer->unlockRenderer();
}
void
isstGL::set_tie(struct tie_s *in_tie, struct rt_annot_scene *annotations)
{
m_renderer->lockRenderer();
m_renderer->tie = in_tie;
m_renderer->annotations = annotations;
render_camera_fit_scene(&m_renderer->camera, m_renderer->tie, annotations,
ADRT_MODEL_UNITS_PER_TIE_UNIT);
// Initialize the camera position
VSETALL(m_renderer->camera.pos, m_renderer->tie->radius);
VMOVE(m_renderer->camera.focus, m_renderer->tie->mid);
// Record the initial settings for use in subsequent calculations
VMOVE(m_renderer->camera_pos_init, m_renderer->camera.pos);
VMOVE(m_renderer->camera_focus_init, m_renderer->camera.focus);
m_renderer->changed = true;
m_renderer->unlockRenderer();
VSETALL(m_renderer->camera_pos_init, m_renderer->tie->radius);
VMOVE(m_renderer->camera_focus_init, m_renderer->tie->mid);
// Having just loaded a new TIE scene,
// we need a new image
@@ -389,3 +369,4 @@ void isstGL::save_image() {
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+2 -3
View File
@@ -77,7 +77,6 @@ class TIERenderer : public QObject, protected QOpenGLFunctions
public:
struct tie_s *tie = NULL; // From parent app
struct rt_annot_scene *annotations = NULL; // Owned by the parent app
struct render_camera_s camera;
vect_t camera_pos_init;
vect_t camera_focus_init;
@@ -114,8 +113,7 @@ class isstGL : public QOpenGLWidget
explicit isstGL(QWidget *parent = nullptr);
~isstGL();
void clear_tie();
void set_tie(struct tie_s *in_tie, struct rt_annot_scene *annotations);
void set_tie(struct tie_s *in_tie);
void save_image();
@@ -155,3 +153,4 @@ class isstGL : public QOpenGLWidget
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
+3 -196
View File
@@ -480,22 +480,6 @@ _nirt_get_resource(struct nirt_state *nss)
}
static void
_nirt_annotation_result_clear(struct nirt_state *nss)
{
delete nss->i->vals->annotation;
nss->i->vals->annotation = NULL;
}
static void
_nirt_annotation_scene_clear(struct nirt_state *nss)
{
rt_annot_scene_destroy(nss->i->annotation_scene);
nss->i->annotation_scene = NULL;
}
int
_nirt_raytrace_prep(struct nirt_state *nss)
{
@@ -533,17 +517,11 @@ _nirt_raytrace_prep(struct nirt_state *nss)
bu_free(attrs, "objs");
return -1;
}
bu_free(objs, "objs");
bu_free(attrs, "objs");
nmsg(nss, "Prepping the geometry...\n");
rt_prep(nss->i->ap->a_rt_i);
_nirt_annotation_scene_clear(nss);
if (nss->i->use_annotations) {
nss->i->annotation_scene = rt_annot_scene_create(nss->i->dbip,
ocnt, objs, &nss->i->ap->a_rt_i->rti_ttol,
&nss->i->ap->a_rt_i->rti_tol);
}
bu_free(objs, "objs");
nmsg(nss, "%s ", (nss->i->active_paths.size() == 1) ? "Object" : "Objects");
for (size_t i = 0; i < nss->i->active_paths.size(); i++) {
if (i == 0) {
@@ -982,17 +960,6 @@ _nirt_print_fmt_substr(struct nirt_state *nss, struct bu_vls *ostr, const char *
nirt_print_key("a", r->a);
nirt_print_key("e", r->e);
if (r->annotation) {
nirt_print_key("annot_path", r->annotation->path.c_str());
nirt_print_key("annot_segment", r->annotation->segment);
nirt_print_key("annot_role", r->annotation->role.c_str());
nirt_print_key("annot_x", r->annotation->point[X] * base2local);
nirt_print_key("annot_y", r->annotation->point[Y] * base2local);
nirt_print_key("annot_z", r->annotation->point[Z] * base2local);
nirt_print_key("annot_d", r->annotation->d * base2local);
nirt_print_key("annot_visible", r->annotation->visible);
}
if (!r->seg || r->seg->type == 0) return;
if (r->seg->type == NIRT_PARTITION_SEG || r->seg->type == NIRT_ALL_SEG) {
@@ -1089,9 +1056,6 @@ _nirt_report(struct nirt_state *nss, char type, struct nirt_output_record *r)
std::vector<std::pair<std::string,std::string> >::iterator f_it;
switch (type) {
case 'a':
fmt_vect = &nss->i->fmt.annotation;
break;
case 'r':
fmt_vect = &nss->i->fmt.ray;
break;
@@ -1127,75 +1091,6 @@ _nirt_report(struct nirt_state *nss, char type, struct nirt_output_record *r)
}
static const char *
_nirt_annotation_role(uint32_t role)
{
switch (role) {
case RT_ANNOT_ROLE_UNSPECIFIED:
return "unspecified";
case RT_ANNOT_ROLE_GEOMETRY:
return "geometry";
case RT_ANNOT_ROLE_TEXT:
return "text";
case RT_ANNOT_ROLE_LEADER:
return "leader";
case RT_ANNOT_ROLE_EXTENSION:
return "extension";
case RT_ANNOT_ROLE_DIMENSION:
return "dimension";
case RT_ANNOT_ROLE_ARROWHEAD:
return "arrowhead";
case RT_ANNOT_ROLE_SYMBOL:
return "symbol";
case RT_ANNOT_ROLE_MASK:
return "mask";
case RT_ANNOT_ROLE_CENTERMARK:
return "centermark";
case RT_ANNOT_ROLE_PLACEHOLDER:
return "placeholder";
case RT_ANNOT_ROLE_TEXT_DECORATION:
return "text-decoration";
default:
return "unknown";
}
}
static void
_nirt_report_annotations(struct nirt_state *nss)
{
const size_t count = rt_annot_scene_query_model(nss->i->annotation_scene,
&nss->i->ap->a_ray, nss->i->first_solid_distance, NULL, 0);
if (!count)
return;
std::vector<struct rt_annot_hit> hits(count);
const size_t found = rt_annot_scene_query_model(nss->i->annotation_scene,
&nss->i->ap->a_ray, nss->i->first_solid_distance, hits.data(),
hits.size());
nirt_seg *saved_segment = nss->i->vals->seg;
nss->i->vals->seg = NULL;
for (size_t i = 0; i < std::min(found, hits.size()); ++i) {
const struct rt_annot_hit &hit = hits[i];
if (hit.segment > (size_t)std::numeric_limits<int>::max()) {
nerr(nss, "Annotation segment index is too large for NIRT reporting\n");
continue;
}
if (!nss->i->vals->annotation)
nss->i->vals->annotation = new nirt_annotation_record;
nirt_annotation_record *record = nss->i->vals->annotation;
record->path = hit.path ? hit.path : "";
record->segment = (int)hit.segment;
record->role = _nirt_annotation_role(hit.role);
VMOVE(record->point, hit.point);
record->d = d_calc(nss, record->point);
record->visible = hit.visible;
_nirt_report(nss, 'a', nss->i->vals);
}
nss->i->vals->seg = saved_segment;
}
/************************
* Raytracing Callbacks *
************************/
@@ -1318,21 +1213,6 @@ _nirt_if_hit(struct application *ap, struct partition *part_head, struct seg *UN
int ev_odd = 1; /* first partition is colored as "odd" */
point_t out_old = VINIT_ZERO;
double d_out_old = 0.0;
nss->i->first_solid_distance = INFINITY;
for (part = part_head->pt_forw; part != part_head; part = part->pt_forw) {
if (part->pt_regionp->reg_aircode)
continue;
if (part->pt_inhit->hit_dist >= 0.0) {
nss->i->first_solid_distance = part->pt_inhit->hit_dist;
break;
}
if (part->pt_outhit->hit_dist >= 0.0) {
nss->i->first_solid_distance = part->pt_outhit->hit_dist;
break;
}
}
nirt_seg *s = new nirt_seg;
if (vals->seg) {
delete vals->seg;
@@ -1669,10 +1549,9 @@ static const struct nirt_cmd_desc nirt_descs[] = {
{ "s", "shoot a ray at the target", NULL },
{ "backout", "back out of model", NULL },
{ "useair", "set/query use of air", "<0|1|2|...>" },
{ "useannot", "set/query annotation reporting", "<0|1>" },
{ "units", "set/query local units", "<mm|cm|m|in|ft>" },
{ "overlap_claims", "set/query overlap rebuilding/retention", "<0|1|2|3>" },
{ "fmt", "set/query output formats", "{arhpfmog} format item item ..." },
{ "fmt", "set/query output formats", "{rhpfmog} format item item ..." },
{ "plotfile", "designate an output file to hold plot data", "file" },
{ "print", "query an output item", "item" },
{ "debug", "set/query nirt debug flags", "[-h] [-l [lib]] [-C [lib]] [-V [lib] [val]] [lib [flag]]" },
@@ -1949,9 +1828,6 @@ _nirt_cmd_shoot(void *ns, int argc, const char **UNUSED(argv))
return -1;
}
_nirt_annotation_result_clear(nss);
nss->i->first_solid_distance = INFINITY;
/* If we have no active rtip, we don't need to prep or shoot */
if (!_nirt_get_rtip(nss)) {
return 0;
@@ -1984,10 +1860,9 @@ _nirt_cmd_shoot(void *ns, int argc, const char **UNUSED(argv))
nss->i->ap->a_ray.r_pt[2] * nss->i->base2local,
V3ARGS(nss->i->ap->a_ray.r_dir));
// TODO - any necessary initialization for data collection by callbacks
_nirt_init_ovlp(nss);
(void)rt_shootray(nss->i->ap);
if (nss->i->use_annotations)
_nirt_report_annotations(nss);
// Undo backout
for (i = 0; i < 3; ++i) {
@@ -2090,45 +1965,6 @@ _nirt_cmd_use_air(void *ns, int argc, const char *argv[])
}
extern "C" int
_nirt_cmd_use_annotations(void *ns, int argc, const char *argv[])
{
struct nirt_state *nss = (struct nirt_state *)ns;
struct bu_vls optparse_msg = BU_VLS_INIT_ZERO;
int enabled = 0;
if (!ns) return -1;
if (argc == 1) {
nout(nss, "use_annotations = %d\n", nss->i->use_annotations);
return 0;
}
if (argc > 2) {
nerr(nss, "Usage: useannot %s\n", _nirt_get_desc_args("useannot"));
return -1;
}
if (bu_opt_int(&optparse_msg, 1, (const char **)&argv[1],
(void *)&enabled) == -1) {
nerr(nss, "Error: bu_opt value read failure: %s\n\nUsage: useannot %s\n",
bu_vls_cstr(&optparse_msg), _nirt_get_desc_args("useannot"));
bu_vls_free(&optparse_msg);
return -1;
}
bu_vls_free(&optparse_msg);
if (enabled != 0 && enabled != 1) {
nerr(nss, "useannot must be set to 0 (off) or 1 (on)\n");
return -1;
}
if (enabled != nss->i->use_annotations) {
nss->i->use_annotations = enabled;
_nirt_annotation_scene_clear(nss);
_nirt_annotation_result_clear(nss);
if (enabled)
nss->i->need_reprep = 1;
}
return 0;
}
extern "C" int
_nirt_cmd_units(void *ns, int argc, const char *argv[])
{
@@ -2249,9 +2085,6 @@ _nirt_cmd_format_output(void *ns, int argc, const char **argv)
// print out current fmt str for the specified type
struct bu_vls ostr = BU_VLS_INIT_ZERO;
switch (type) {
case 'a':
_nirt_print_fmt_str(&ostr, nss->i->fmt.annotation);
break;
case 'r':
_nirt_print_fmt_str(&ostr, nss->i->fmt.ray);
break;
@@ -2358,10 +2191,6 @@ _nirt_cmd_format_output(void *ns, int argc, const char **argv)
* formatting instructions with the new. */
set_fmt:
switch (type) {
case 'a':
nss->i->fmt.annotation.clear();
nss->i->fmt.annotation = fmt_tmp;
break;
case 'r':
nss->i->fmt.ray.clear();
nss->i->fmt.ray = fmt_tmp;
@@ -2714,7 +2543,6 @@ _nirt_cmd_state(void *ns, int argc, const char *argv[])
bu_vls_printf(&dumpstr, "xyz %g %g %g\n", V3ARGS(nss->i->vals->orig));
bu_vls_printf(&dumpstr, "dir %g %g %g\n", V3ARGS(nss->i->vals->dir));
bu_vls_printf(&dumpstr, "useair %d\n", nss->i->use_air);
bu_vls_printf(&dumpstr, "useannot %d\n", nss->i->use_annotations);
bu_vls_printf(&dumpstr, "units %s\n", bu_units_string(nss->i->local2base));
bu_vls_printf(&dumpstr, "overlap_claims ");
switch (nss->i->overlap_claims) {
@@ -2735,8 +2563,6 @@ _nirt_cmd_state(void *ns, int argc, const char *argv[])
break;
}
struct bu_vls ostr = BU_VLS_INIT_ZERO;
_nirt_print_fmt_cmd(&ostr, 'a', nss->i->fmt.annotation);
bu_vls_printf(&dumpstr, "%s", bu_vls_cstr(&ostr));
_nirt_print_fmt_cmd(&ostr, 'r', nss->i->fmt.ray);
bu_vls_printf(&dumpstr, "%s", bu_vls_cstr(&ostr));
_nirt_print_fmt_cmd(&ostr, 'h', nss->i->fmt.head);
@@ -2791,7 +2617,6 @@ const struct bu_cmdtab _libanalyze_nirt_cmds[] = {
{ "state", _nirt_cmd_state},
{ "units", _nirt_cmd_units},
{ "useair", _nirt_cmd_use_air},
{ "useannot", _nirt_cmd_use_annotations},
{ "xyz", _nirt_cmd_target_coor},
{ (char *)NULL, NULL}
};
@@ -2958,14 +2783,6 @@ static const char *nirt_cmd_tbl_defs =
"z_dir, FNOUNIT, Ray direction unit vector z component,"
"a, FNOUNIT, Azimuth,"
"e, FNOUNIT, Elevation,"
"annot_path, STRING, Annotation database path,"
"annot_segment, INT, Annotation segment index,"
"annot_role, STRING, Annotation segment role,"
"annot_x, FLOAT, Annotation hit X coordinate,"
"annot_y, FLOAT, Annotation hit Y coordinate,"
"annot_z, FLOAT, Annotation hit Z coordinate,"
"annot_d, FLOAT, Annotation hit gridplane distance,"
"annot_visible, INT, Annotation hit visibility,"
"x_in, FLOAT, ,"
"y_in, FLOAT, ,"
"z_in, FLOAT, ,"
@@ -3078,7 +2895,6 @@ nirt_init(struct nirt_state *ns)
n->base2local = 0.0;
n->local2base = 0.0;
n->use_air = 0;
n->use_annotations = 0;
n->backout = 1;
n->b_state = false;
@@ -3093,8 +2909,6 @@ nirt_init(struct nirt_state *ns)
BU_GET(n->res_air, struct resource);
n->rtip = RTI_NULL;
n->rtip_air = RTI_NULL;
n->annotation_scene = NULL;
n->first_solid_distance = INFINITY;
n->need_reprep = 1;
BU_GET(n->val_types, struct bu_attribute_value_set);
@@ -3108,7 +2922,6 @@ nirt_init(struct nirt_state *ns)
n->vals->h = 0.0;
n->vals->v = 0.0;
n->vals->d_orig = 0.0;
n->vals->annotation = NULL;
n->vals->seg = NULL;
_nirt_diff_create(ns);
@@ -3212,8 +3025,6 @@ nirt_clear_dbip(struct nirt_state *ns)
{
if (!ns) return -1;
_nirt_annotation_scene_clear(ns);
_nirt_annotation_result_clear(ns);
db_close(ns->i->dbip);
ns->i->dbip = NULL;
@@ -3234,9 +3045,6 @@ void
nirt_destroy(struct nirt_state *ns)
{
if (!ns) return;
_nirt_annotation_scene_clear(ns);
_nirt_annotation_result_clear(ns);
bu_vls_free(&ns->nirt_cmd);
bu_vls_free(ns->i->err);
bu_vls_free(ns->i->msg);
@@ -3249,7 +3057,6 @@ nirt_destroy(struct nirt_state *ns)
db_close(ns->i->dbip);
delete ns->i->vals->seg;
BU_PUT(ns->i->vals, struct nirt_output_record);
BU_PUT(ns->i->res, struct resource);
+1 -17
View File
@@ -83,7 +83,7 @@ extern "C" b_off_t ftello(FILE *);
#define NIRT_OVLP_RETAIN 3
#define NIRT_PRINTF_SPECIFIERS "difeEgGs"
#define NIRT_OUTPUT_TYPE_SPECIFIERS "arhpfmog"
#define NIRT_OUTPUT_TYPE_SPECIFIERS "rhpfmog"
#define HELPFLAG "--print-help"
#define PURPOSEFLAG "--print-purpose"
@@ -274,16 +274,6 @@ class nirt_seg {
}
};
class nirt_annotation_record {
public:
std::string path;
int segment;
std::string role;
point_t point;
fastf_t d;
int visible;
};
/* This record structure doesn't have to correspond exactly to the above list
* of available values, but it needs to retain sufficient information to
* support the ability to generate all of them upon request. */
@@ -296,7 +286,6 @@ struct nirt_output_record {
fastf_t a;
fastf_t e;
struct nirt_overlap ovlp_list;
nirt_annotation_record *annotation;
nirt_seg *seg;
};
@@ -305,7 +294,6 @@ struct nirt_diff_state;
class nirt_fmt_state {
public:
std::vector<std::pair<std::string,std::string> > annotation;
std::vector<std::pair<std::string,std::string> > ray;
std::vector<std::pair<std::string,std::string> > head;
std::vector<std::pair<std::string,std::string> > part;
@@ -315,7 +303,6 @@ class nirt_fmt_state {
std::vector<std::pair<std::string,std::string> > gap;
void clear() {
annotation.clear();
ray.clear();
head.clear();
part.clear();
@@ -360,7 +347,6 @@ struct nirt_state_impl {
int backout;
int overlap_claims;
int use_air;
int use_annotations;
std::set<std::string> attrs; // active attributes
std::vector<std::string> active_paths; // active paths for raytracer
struct nirt_output_record *vals;
@@ -382,8 +368,6 @@ struct nirt_state_impl {
struct resource *res;
struct rt_i *rtip_air;
struct resource *res_air;
struct rt_annot_scene *annotation_scene;
fastf_t first_solid_distance;
int need_reprep;
/* internal format specifier arrays */
-3
View File
@@ -1,9 +1,6 @@
brlcad_addexec(analyze_raydiff raydiff.c "libanalyze;librt;libbu" TEST)
brlcad_addexec(analyze_sp solid_partitions.c "libanalyze;librt;libbu" TEST)
brlcad_addexec(analyze_nhit nhit.cpp "libanalyze;librt;libbu" TEST_USESDATA)
brlcad_addexec(analyze_nirt_annotations nirt_annotations.cpp
"libanalyze;libwdb;librt;libbu" TEST_USESDATA)
brlcad_add_test(NAME analyze_nirt_annotations COMMAND analyze_nirt_annotations)
#####################################
# analyze_densities testing #
-191
View File
@@ -1,191 +0,0 @@
/* N I R T _ A N N O T A T I O N S . C P P
* BRL-CAD
*
* Copyright (c) 2026 United States Government as represented by the
* U.S. Army Research Laboratory.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "common.h"
#include <cstdio>
#include <cstdlib>
#include "analyze.h"
#include "bu/app.h"
#include "bu/file.h"
#include "bu/malloc.h"
#include "raytrace.h"
#include "wdb.h"
static constexpr fastf_t FRONT_ANNOTATION_X = 18.0;
static constexpr fastf_t REAR_ANNOTATION_X = 10.0;
static constexpr fastf_t SCREEN_ANNOTATION_X = 19.0;
static constexpr fastf_t OCCLUDER_MINIMUM_X = 15.0;
static constexpr fastf_t OCCLUDER_MAXIMUM_X = 16.0;
static constexpr fastf_t RAY_ORIGIN_X = 20.0;
static constexpr fastf_t TEST_HALF_EXTENT = 1.0;
static int
capture_output(struct nirt_state *state, void *data)
{
struct bu_vls current = BU_VLS_INIT_ZERO;
struct bu_vls *captured = (struct bu_vls *)data;
nirt_log(&current, state, NIRT_OUT);
bu_vls_vlscat(captured, &current);
bu_vls_free(&current);
return 0;
}
static void
write_annotation(struct rt_wdb *wdbp, const char *name, fastf_t x,
bool screen_space)
{
struct rt_annot_internal annotation = {};
struct line_seg line = {};
struct rt_annot_seg_style style = {};
int reverse = 0;
void *segment = &line;
point2d_t vertices[2] = {{0.0, 0.0}, {TEST_HALF_EXTENT, 0.0}};
annotation.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(annotation.V, x, 0.0, 0.0);
annotation.flags = screen_space ? RT_ANNOT_SCREEN_SPACE :
RT_ANNOT_MODEL_SPACE;
VSET(annotation.u_vec, 0.0, 1.0, 0.0);
VSET(annotation.v_vec, 0.0, 0.0, 1.0);
annotation.vert_count = 2;
annotation.verts = vertices;
annotation.ant.count = 1;
annotation.ant.reverse = &reverse;
annotation.ant.segments = &segment;
annotation.styles = &style;
line.magic = CURVE_LSEG_MAGIC;
line.start = 0;
line.end = 1;
style.role = RT_ANNOT_ROLE_LEADER;
style.line_pattern = RT_ANNOT_LINE_CONTINUOUS;
if (rt_annot_validate(&annotation, NULL) ||
mk_annot(wdbp, name, &annotation) < 0)
bu_exit(EXIT_FAILURE, "unable to write %s\n", name);
}
static void
clear_standard_formats(struct nirt_state *state)
{
const char *commands[] = {
"fmt r \"\"", "fmt h \"\"", "fmt p \"\"", "fmt f \"\"",
"fmt m \"\"", "fmt o \"\"", "fmt g \"\""
};
for (size_t i = 0; i < sizeof(commands) / sizeof(commands[0]); ++i)
if (nirt_exec(state, commands[i]))
bu_exit(EXIT_FAILURE, "NIRT rejected command: %s\n", commands[i]);
}
static void
get_output(struct bu_vls *output, struct bu_vls *captured)
{
bu_vls_sprintf(output, "%s", bu_vls_cstr(captured));
bu_vls_trunc(captured, 0);
}
int
main(int argc, char **argv)
{
char path[MAXPATHLEN];
struct db_i *dbip;
struct rt_wdb *wdbp;
struct nirt_state *state;
struct bu_vls captured = BU_VLS_INIT_ZERO;
struct bu_vls output = BU_VLS_INIT_ZERO;
struct wmember members;
point_t minimum, maximum;
bu_setprogname(argv[0]);
if (argc != 1)
return EXIT_FAILURE;
{
FILE *fp = bu_temp_file(path, sizeof(path));
if (!fp)
bu_exit(EXIT_FAILURE, "unable to create temporary database path\n");
fclose(fp);
}
dbip = db_create(path, 5);
if (!dbip)
bu_exit(EXIT_FAILURE, "unable to create temporary database\n");
wdbp = wdb_dbopen(dbip, RT_WDB_TYPE_DB_DISK);
if (!wdbp)
bu_exit(EXIT_FAILURE, "unable to open temporary database for writing\n");
write_annotation(wdbp, "front.annot", FRONT_ANNOTATION_X, false);
write_annotation(wdbp, "rear.annot", REAR_ANNOTATION_X, false);
write_annotation(wdbp, "screen.annot", SCREEN_ANNOTATION_X, true);
VSET(minimum, OCCLUDER_MINIMUM_X, -TEST_HALF_EXTENT,
-TEST_HALF_EXTENT);
VSET(maximum, OCCLUDER_MAXIMUM_X, TEST_HALF_EXTENT,
TEST_HALF_EXTENT);
if (mk_rpp(wdbp, "occluder.s", minimum, maximum) < 0)
bu_exit(EXIT_FAILURE, "unable to write occluder\n");
BU_LIST_INIT(&members.l);
if (!mk_addmember("occluder.s", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "occluder.r", &members, 1, NULL, NULL, NULL, 0))
bu_exit(EXIT_FAILURE, "unable to write occluding region\n");
BU_GET(state, struct nirt_state);
if (nirt_init(state))
bu_exit(EXIT_FAILURE, "unable to initialize NIRT\n");
nirt_udata(state, &captured);
nirt_hook(state, capture_output, NIRT_OUT);
if (nirt_exec(state,
"draw front.annot rear.annot screen.annot occluder.r") ||
nirt_init_dbip(state, dbip))
bu_exit(EXIT_FAILURE, "unable to prepare NIRT test database\n");
clear_standard_formats(state);
if (nirt_exec(state,
"fmt a \"%s|%d|%s|%.1f|%.1f|%d\\n\" annot_path annot_segment annot_role annot_x annot_d annot_visible"))
bu_exit(EXIT_FAILURE, "unable to set annotation report format\n");
struct bu_vls shot = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&shot, "backout 0;xyz %g 0 0;dir -1 0 0;s",
RAY_ORIGIN_X);
if (nirt_exec(state, bu_vls_cstr(&shot)))
bu_exit(EXIT_FAILURE, "unable to shoot default NIRT ray\n");
bu_vls_free(&shot);
get_output(&output, &captured);
if (bu_vls_strlen(&output))
bu_exit(EXIT_FAILURE,
"annotations were reported without useannot: %s\n",
bu_vls_cstr(&output));
if (nirt_exec(state, "useannot 1;s"))
bu_exit(EXIT_FAILURE, "unable to shoot annotation NIRT ray\n");
get_output(&output, &captured);
struct bu_vls expected = BU_VLS_INIT_ZERO;
bu_vls_sprintf(&expected,
"/front.annot|0|leader|%.1f|%.1f|1\n"
"/rear.annot|0|leader|%.1f|%.1f|0\n",
FRONT_ANNOTATION_X, FRONT_ANNOTATION_X,
REAR_ANNOTATION_X, REAR_ANNOTATION_X);
if (!BU_STR_EQUAL(bu_vls_cstr(&output), bu_vls_cstr(&expected)))
bu_exit(EXIT_FAILURE, "unexpected annotation report:\n%s",
bu_vls_cstr(&output));
bu_vls_free(&expected);
nirt_destroy(state);
BU_PUT(state, struct nirt_state);
bu_vls_free(&captured);
bu_vls_free(&output);
db_close(dbip);
bu_file_delete(path);
return EXIT_SUCCESS;
}
+6 -12
View File
@@ -419,7 +419,7 @@ protected:
ListNode* AllocListNode();
void FreeListNode(ListNode* a_listNode);
bool Overlap(Rect* a_rectA, Rect* a_rectB) const;
bool Intersect(const Rect* a_rect, const Ray* a_ray) const;
bool Intersect(Rect* a_rect, Ray* a_ray) const;
void ReInsert(Node* a_node, ListNode** a_listNode);
bool Search(Node* a_node, Rect* a_rect, int& a_foundCount, std::function<bool(const DataType&, void *)> callback, void *a_context) const;
void RemoveAllRec(Node* a_node);
@@ -433,15 +433,14 @@ protected:
size_t CheckNodes(Node *a_nodeA, Node *a_nodeB, std::set<DataType> *a_result, std::set<DataType> *b_result);
size_t CheckNodes(Node *a_nodeA, Node *a_nodeB, std::set<std::pair<DataType, DataType>> *result);
size_t IsectNode(const Node *a_node, const Ray *a_ray,
std::set<DataType> *result) const;
size_t IsectNode(Node *a_node, Ray *a_ray, std::set<DataType> *result);
Node* m_root = nullptr; ///< Root of tree
ElementTypeReal m_unitSphereVolume = kElementTypeRealZero; ///< Unit sphere constant for required number of dimensions
public:
// Find the leaves intersected by a ray, returning the data from the leaves in a set.
size_t Intersects(const Ray *a_ray, std::set<DataType> *result) const;
size_t Intersects(Ray *a_ray, std::set<DataType> *result);
void plot(const char *fname);
void plot2d(const char *fname);
@@ -1552,7 +1551,7 @@ bool RTREE_QUAL::Overlap(Rect* a_rectA, Rect* a_rectB) const
// Decide whether a ray intersect a Rect.
RTREE_TEMPLATE
bool RTREE_QUAL::Intersect(const Rect* a_rect, const Ray* a_ray) const
bool RTREE_QUAL::Intersect(Rect* a_rect, Ray* a_ray) const
{
RTreeAssert(a_rect);
if (kNumDimensions != 3) return false;
@@ -1686,8 +1685,7 @@ size_t RTREE_QUAL::Overlaps(RTree &other, std::set<std::pair<DataType, DataType>
RTREE_TEMPLATE
size_t RTREE_QUAL::IsectNode(const Node *a_node, const Ray *a_ray,
std::set<DataType> *result) const
size_t RTREE_QUAL::IsectNode(Node *a_node, Ray *a_ray, std::set<DataType> *result)
{
size_t ocnt = 0;
for (int index = 0; index < a_node->m_count; ++index) {
@@ -1705,8 +1703,7 @@ size_t RTREE_QUAL::IsectNode(const Node *a_node, const Ray *a_ray,
// Loosely based on the idea from the openNURBS code to search two R-trees for all pairs elements whose bounding boxes overlap.
RTREE_TEMPLATE
size_t RTREE_QUAL::Intersects(const Ray *a_ray,
std::set<DataType> *result) const
size_t RTREE_QUAL::Intersects(Ray *a_ray, std::set<DataType> *result)
{
if (!m_root) return 0;
size_t ovlp_cnt = IsectNode(m_root, a_ray, result);
@@ -1803,9 +1800,6 @@ void RTREE_QUAL::plot2d(const char *fname)
}
#undef BB_PLOT2D
#undef BB_PLOT
#undef TREE_LEAF_FACE_3D
#undef RTREE_TEMPLATE
#undef RTREE_QUAL
+1 -1
View File
@@ -234,7 +234,7 @@ set(
mime.cmake
parallel.h
parallel_cpp11thread.cpp
process_private.h
process.h
sha1.h
struetype.h
vfont.h
+1 -1
View File
@@ -49,7 +49,7 @@
#include "bu/str.h"
#include "bu/datetime.h"
#include "bu/vls.h"
#include "./process_private.h"
#include "./process.h"
#ifndef HAVE_KILL
# include <TlHelp32.h>
@@ -1,4 +1,4 @@
/* P R O C E S S _ P R I V A T E . H
/* P R O C E S S . H
* BRL-CAD
*
* Copyright (c) 2013-2026 United States Government as represented by
@@ -20,8 +20,8 @@
/* Private libbu process related definitions shared between multiple files */
#ifndef LIBBU_PROCESS_PRIVATE_H
#define LIBBU_PROCESS_PRIVATE_H
#ifndef LIBBU_PROCESS_H
#define LIBBU_PROCESS_H
/* Apparently the termination code on Windows is arbitrary and somewhat
* ill-defined - see https://bugs.python.org/issue31863 for more info. Try our
@@ -29,7 +29,7 @@
* abort identification. */
#define BU_MSVC_ABORT_EXIT 97101 /* ascii - a == 97 && e == 101 */
#endif /* LIBBU_PROCESS_PRIVATE_H */
#endif /* LIBBU_PROCESS_H */
/*
* Local Variables:
@@ -188,7 +188,8 @@ test_uncertain()
require(result.shells.empty() || (result.shells.front().orientation == ShellOrientation::Indeterminate &&
!result.shells.front().corrected), "invalid parameter-loop senses caused an incorrect face repair");
auto flat = box();
ON_Xform flatten = ON_Xform::DiagonalTransformation(1.0);
ON_Xform flatten;
flatten.DiagonalTransformation(1.0);
flatten[2][2] = 1.0e-13;
require(flat->Transform(flatten), "could not construct thin shell");
result = repair_brep_orientation(*flat);
+6 -8
View File
@@ -62,8 +62,9 @@ constexpr fastf_t DEFAULT_LEADER_MARGIN_SCALE = 2.0;
constexpr fastf_t DEFAULT_SCREEN_TEXT_HEIGHT_MM = 3.0;
/* The display-plane renderer uses this nominal density. --dpi makes the
* physical size explicit for displays whose effective density differs. */
constexpr fastf_t DEFAULT_SCREEN_DPI = RT_ANNOT_SCREEN_DPI;
constexpr fastf_t DISPLAY_PIXELS_PER_MM = RT_ANNOT_SCREEN_PIXELS_PER_MM;
constexpr fastf_t DEFAULT_SCREEN_DPI = 96.0;
constexpr fastf_t MILLIMETERS_PER_INCH = 25.4;
constexpr fastf_t DISPLAY_PIXELS_PER_MM = DEFAULT_SCREEN_DPI / MILLIMETERS_PER_INCH;
constexpr double AUTODIM_TEXT_WIDTH_SCALE = 0.65;
/* Layout penalties deliberately dominate the smaller rewards so avoiding
* collisions and crossings wins over merely moving a label farther out. */
@@ -472,12 +473,10 @@ parse_options(struct ged *gedp, int argc, const char **argv,
bu_vls_printf(gedp->ged_result_str, "Precision must be between 0 and 15");
return -1;
}
if (!std::isfinite(opts.text_height) || !std::isfinite(opts.line_width) ||
opts.text_height < 0.0 || opts.line_width < 0.0 ||
std::isinf(opts.offset) ||
if (opts.text_height < 0.0 || opts.line_width < 0.0 ||
(std::isfinite(opts.offset) && opts.offset < 0.0)) {
bu_vls_printf(gedp->ged_result_str,
"Text height and line width must be finite and non-negative; offset must be finite and non-negative when specified");
"Text height, line width, and offset must not be negative");
return -1;
}
if (!std::isfinite(opts.dpi) || opts.dpi <= 0.0) {
@@ -2905,8 +2904,7 @@ cmd_info(void *data, int argc, const char **argv)
if (rt_db_get_internal(&intern, dp, gedp->dbip, NULL) == ID_ANNOT) {
struct rt_annot_internal *annotation =
static_cast<struct rt_annot_internal *>(intern.idb_ptr);
bu_vls_printf(gedp->ged_result_str,
"\nspace: %s\nsegments: %zu",
bu_vls_printf(gedp->ged_result_str, "\nspace: %s\nsegments: %zu",
(annotation->flags & RT_ANNOT_MODEL_SPACE) ? "model" : "view",
annotation->ant.count);
rt_db_free_internal(&intern);
-2
View File
@@ -150,7 +150,6 @@ set(
prep.cpp
${LIBRT_PRIMEDIT_SOURCES}
primitives/annot/annot.c
primitives/annot/annot_render.cpp
primitives/arb8/arb8.c
primitives/arb8/arb8_brep.cpp
primitives/arb8/arb8_mirror.c
@@ -428,7 +427,6 @@ endif(BRLCAD_ENABLE_SPR)
set(LIBRT_PUBLIC_LIBS ${librt_deps} OPENNURBS::OPENNURBS)
set(
LIBRT_PRIVATE_LIBS
Threads::Threads
${OPENCL_LIBS}
mmesh::mmesh
REGEX::REGEX
+586 -47
View File
@@ -49,6 +49,7 @@
#include "nmg.h"
#include "rt/geom.h"
#include "rt/primitives/annot.h"
#include "rt/primitives/bot.h"
#include "raytrace.h"
#if defined(__GNUC__) && !defined(__clang__)
@@ -76,8 +77,46 @@
#define ANNOT_PRES_MAGIC 0x414e5032u /* "ANP2" */
#define ANNOT_PRES_VERSION 1u
/* Stored line widths are display-scale multipliers, not model distances.
* Primitive prep has no view pixel scale, so infer a stable model-space base
* width from the annotation content before applying that multiplier. */
#define ANNOT_TEXT_STROKE_RATIO (1.0 / 14.0)
#define ANNOT_GEOMETRY_STROKE_RATIO 0.01
#define ANNOT_MIN_WIDTH_TOL_FACTOR 4.0
struct annot_mesh {
point_t *vertices;
size_t vertex_count;
size_t vertex_capacity;
int *faces;
fastf_t *thickness;
size_t face_count;
size_t face_capacity;
};
C_DECL void rt_annot_ifree(struct rt_db_internal *ip);
/* Annotation prep uses BOT as a transient raytrace representation, as ARS
* does. These callbacks are internal to librt rather than public BOT APIs. */
C_DECL int rt_bot_prep(struct soltab *stp, struct rt_db_internal *ip,
struct rt_i *rtip);
C_DECL int rt_bot_shot(struct soltab *stp, struct xray *rp,
struct application *ap, struct seg *seghead);
C_DECL void rt_bot_print(const struct soltab *stp);
C_DECL void rt_bot_norm(struct hit *hitp, struct soltab *stp,
struct xray *rp);
C_DECL void rt_bot_curve(struct curvature *cvp, struct hit *hitp,
struct soltab *stp);
C_DECL void rt_bot_uv(struct application *ap, struct soltab *stp,
struct hit *hitp, struct uvcoord *uvp);
C_DECL void rt_bot_free(struct soltab *stp);
C_DECL void rt_bot_ifree(struct rt_db_internal *ip);
static int annot_to_bot(struct rt_bot_internal **result,
const struct rt_annot_internal *annot_ip,
const struct bg_tess_tol *ttol, const struct bn_tol *tol,
unsigned char bot_mode);
static uint32_t
annot_get_uint32(const unsigned char *cp)
{
@@ -434,9 +473,8 @@ rt_annot_validate(const struct rt_annot_internal *annot_ip,
annot_validation_message(messages, i, "fill has no compatible outline segments");
ret++;
} else {
size_t legacy_end = (size_t)fsg->legacy_start +
(size_t)fsg->legacy_count;
for (j = (size_t)fsg->legacy_start; j < legacy_end; ++j)
for (j = (size_t)fsg->legacy_start;
j < (size_t)(fsg->legacy_start + fsg->legacy_count); ++j)
if (!ant->segments[j] ||
*(uint32_t *)ant->segments[j] != CURVE_LSEG_MAGIC) {
annot_validation_message(messages, i,
@@ -503,25 +541,59 @@ rt_annot_validate(const struct rt_annot_internal *annot_ip,
C_DECL int
rt_annot_prep(struct soltab *stp, struct rt_db_internal *ip, struct rt_i *rtip)
{
struct bg_tess_tol default_ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol default_tol = BN_TOL_INIT_TOL;
const struct rt_annot_internal *annot_ip;
struct rt_bot_internal *bot = NULL;
struct rt_db_internal bot_intern;
const struct bg_tess_tol *ttol = &default_ttol;
const struct bn_tol *tol = &default_tol;
int ret;
if (!stp)
return -1;
RT_CK_SOLTAB(stp);
if (ip) RT_CK_DB_INTERNAL(ip);
if (rtip) RT_CK_RTI(rtip);
if (!ip)
return -1;
RT_CK_DB_INTERNAL(ip);
if (rtip) {
RT_CK_RTI(rtip);
ttol = &rtip->rti_ttol;
tol = &rtip->rti_tol;
}
if (ip && rt_annot_validate(
(const struct rt_annot_internal *)ip->idb_ptr, NULL))
annot_ip = (const struct rt_annot_internal *)ip->idb_ptr;
if (rt_annot_validate(annot_ip, NULL))
return -1;
/* View-space annotations need camera state, which primitive prep does not
* have. Leave them non-intersecting until a view-aware prep API exists. */
stp->st_specific = (void *)NULL;
return 0;
if (!(annot_ip->flags & RT_ANNOT_MODEL_SPACE))
return 0;
if (annot_to_bot(&bot, annot_ip, ttol, tol, RT_BOT_PLATE) || !bot)
return -1;
RT_DB_INTERNAL_INIT(&bot_intern);
bot_intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
bot_intern.idb_type = ID_BOT;
bot_intern.idb_meth = &OBJ[ID_BOT];
bot_intern.idb_ptr = bot;
ret = rt_bot_prep(stp, &bot_intern, rtip);
rt_bot_ifree(&bot_intern);
return ret;
}
C_DECL void
rt_annot_print(const struct soltab *stp)
{
if (stp) RT_CK_SOLTAB(stp);
if (!stp)
return;
RT_CK_SOLTAB(stp);
if (stp->st_specific)
rt_bot_print(stp);
}
@@ -543,25 +615,18 @@ rt_annot_shot(struct soltab *stp, struct xray *rp, struct application *ap, struc
RT_CK_RAY(rp);
RT_CK_APPLICATION(ap);
/* Annotation coverage is evaluated separately from solid partitions. */
return 0; /* MISS */
return stp->st_specific ? rt_bot_shot(stp, rp, ap, seghead) : 0;
}
/**
* Vectorized rt_annot_shot(): annotations do not create solid partitions, so
* every ray in the batch misses.
* Vectorized annotation shooting uses the scalar wrapper so view-space
* annotations without prepared geometry continue to miss safely.
*/
C_DECL void
rt_annot_vshot(struct soltab **stp, struct xray **UNUSED(rp), struct seg *segp, int n, struct application *ap)
rt_annot_vshot(struct soltab **stp, struct xray **rp, struct seg *segp, int n, struct application *ap)
{
int i;
if (ap) RT_CK_APPLICATION(ap);
for (i = 0; i < n; i++) {
if (stp[i] == 0) continue; /* skip this ray */
segp[i].seg_stp = (struct soltab *)0; /* always MISS */
}
rt_vshot_via_shot(rt_annot_shot, stp, rp, segp, n, ap);
}
@@ -578,6 +643,10 @@ rt_annot_norm(struct hit *hitp, struct soltab *stp, struct xray *rp)
if (stp) RT_CK_SOLTAB(stp);
RT_CK_RAY(rp);
if (stp && stp->st_specific) {
rt_bot_norm(hitp, stp, rp);
return;
}
VJOIN1(hitp->hit_point, rp->r_pt, hitp->hit_dist, rp->r_dir);
}
@@ -594,6 +663,10 @@ rt_annot_curve(struct curvature *cvp, struct hit *hitp, struct soltab *stp)
RT_CK_HIT(hitp);
if (stp) RT_CK_SOLTAB(stp);
if (stp && stp->st_specific) {
rt_bot_curve(cvp, hitp, stp);
return;
}
cvp->crv_c1 = cvp->crv_c2 = 0;
bn_vec_ortho(cvp->crv_pdir, hitp->hit_normal);
@@ -608,13 +681,19 @@ rt_annot_uv(struct application *ap, struct soltab *stp, struct hit *hitp, struct
if (hitp) RT_CK_HIT(hitp);
if (!uvp)
return;
if (stp && stp->st_specific)
rt_bot_uv(ap, stp, hitp, uvp);
}
C_DECL void
rt_annot_free(struct soltab *stp)
{
if (stp) RT_CK_SOLTAB(stp);
if (!stp)
return;
RT_CK_SOLTAB(stp);
if (stp->st_specific)
rt_bot_free(stp);
}
@@ -1526,29 +1605,6 @@ seg_to_vlist(struct bu_list *vlfree, struct bu_list *vhead, const struct bg_tess
}
int
rt_annot_segment_vlist(struct bu_list *vlfree, struct bu_list *vhead,
const struct bg_tess_tol *ttol,
const struct rt_annot_internal *annot_ip, size_t segment)
{
struct rt_annot_internal local;
point_t base = VINIT_ZERO;
if (!vlfree || !vhead || !ttol || !annot_ip ||
segment >= annot_ip->ant.count || !annot_ip->ant.segments ||
!annot_ip->ant.segments[segment])
return 1;
RT_ANNOT_CK_MAGIC(annot_ip);
local = *annot_ip;
local.flags = RT_ANNOT_SCREEN_SPACE;
VSETALL(local.V, 0.0);
return seg_to_vlist(vlfree, vhead, ttol, base, &local,
annot_ip->ant.segments[segment], annot_ip->styles ?
&annot_ip->styles[segment] : NULL);
}
static int
annot_is_fill_compatibility_outline(const struct rt_ant *ant, size_t seg_no)
{
@@ -1635,6 +1691,489 @@ ant_to_vlist(struct bu_list *vlfree, struct bu_list *vhead, const struct bg_tess
}
static void
annot_mesh_free(struct annot_mesh *mesh)
{
if (!mesh)
return;
if (mesh->vertices)
bu_free(mesh->vertices, "annotation mesh vertices");
if (mesh->faces)
bu_free(mesh->faces, "annotation mesh faces");
if (mesh->thickness)
bu_free(mesh->thickness, "annotation mesh thickness");
memset(mesh, 0, sizeof(*mesh));
}
static int
annot_mesh_reserve_vertices(struct annot_mesh *mesh, size_t additional)
{
size_t capacity;
size_t required;
if (additional > SIZE_MAX - mesh->vertex_count)
return 1;
required = mesh->vertex_count + additional;
if (required <= mesh->vertex_capacity)
return 0;
capacity = mesh->vertex_capacity ? mesh->vertex_capacity : 64;
while (capacity < required) {
if (capacity > SIZE_MAX / 2) {
capacity = required;
break;
}
capacity *= 2;
}
if (capacity > SIZE_MAX / sizeof(point_t))
return 1;
mesh->vertices = (point_t *)bu_realloc(mesh->vertices,
capacity * sizeof(point_t), "annotation mesh vertices");
mesh->vertex_capacity = capacity;
return 0;
}
static int
annot_mesh_reserve_faces(struct annot_mesh *mesh, size_t additional)
{
size_t capacity;
size_t required;
if (additional > SIZE_MAX - mesh->face_count)
return 1;
required = mesh->face_count + additional;
if (required <= mesh->face_capacity)
return 0;
capacity = mesh->face_capacity ? mesh->face_capacity : 96;
while (capacity < required) {
if (capacity > SIZE_MAX / 2) {
capacity = required;
break;
}
capacity *= 2;
}
if (capacity > SIZE_MAX / (3 * sizeof(int)) ||
capacity > SIZE_MAX / sizeof(fastf_t))
return 1;
mesh->faces = (int *)bu_realloc(mesh->faces,
capacity * 3 * sizeof(int), "annotation mesh faces");
mesh->thickness = (fastf_t *)bu_realloc(mesh->thickness,
capacity * sizeof(fastf_t), "annotation mesh thickness");
mesh->face_capacity = capacity;
return 0;
}
static int
annot_mesh_add_triangle(struct annot_mesh *mesh, const point_t a,
const point_t b, const point_t c, const vect_t normal, fastf_t thickness,
const struct bn_tol *tol)
{
point_t ordered[3];
vect_t ab, ac, bc, face_normal;
size_t base;
if (VINVALID(a) || VINVALID(b) || VINVALID(c)) {
bu_log("annotation mesh contains a non-finite triangle\n");
return 1;
}
VSUB2(ab, b, a);
VSUB2(ac, c, a);
VSUB2(bc, c, b);
VCROSS(face_normal, ab, ac);
if (INVALID(MAGSQ(ab)) || INVALID(MAGSQ(ac)) || INVALID(MAGSQ(bc)) ||
INVALID(MAGSQ(face_normal))) {
bu_log("annotation mesh triangle exceeds the numeric range\n");
return 1;
}
if (MAGNITUDE(face_normal) <= tol->dist_sq)
return 0;
VMOVE(ordered[0], a);
if (VDOT(face_normal, normal) >= 0.0) {
VMOVE(ordered[1], b);
VMOVE(ordered[2], c);
} else {
VMOVE(ordered[1], c);
VMOVE(ordered[2], b);
}
if (!mesh || mesh->vertex_count > (size_t)INT_MAX - 3 ||
annot_mesh_reserve_vertices(mesh, 3) ||
annot_mesh_reserve_faces(mesh, 1))
return 1;
base = mesh->vertex_count;
VMOVE(mesh->vertices[base], ordered[0]);
VMOVE(mesh->vertices[base + 1], ordered[1]);
VMOVE(mesh->vertices[base + 2], ordered[2]);
mesh->vertex_count += 3;
mesh->faces[3 * mesh->face_count] = (int)base;
mesh->faces[3 * mesh->face_count + 1] = (int)base + 1;
mesh->faces[3 * mesh->face_count + 2] = (int)base + 2;
mesh->thickness[mesh->face_count++] = thickness;
return 0;
}
static int
annot_mesh_add_stroke_span(struct annot_mesh *mesh, const point_t start,
const point_t end, const vect_t normal, fastf_t width,
const struct bn_tol *tol)
{
point_t corners[4];
point_t first, last;
vect_t direction, side;
fastf_t half_width;
fastf_t length;
VSUB2(direction, end, start);
length = MAGNITUDE(direction);
if (INVALID(length))
return 1;
if (length <= tol->dist)
return 0;
VSCALE(direction, direction, 1.0 / length);
VCROSS(side, normal, direction);
if (VINVALID(side))
return 1;
if (MAGNITUDE(side) <= SMALL_FASTF)
return 0;
VUNITIZE(side);
half_width = 0.5 * width;
VJOIN1(first, start, -half_width, direction);
VJOIN1(last, end, half_width, direction);
VJOIN1(corners[0], first, -half_width, side);
VJOIN1(corners[1], last, -half_width, side);
VJOIN1(corners[2], last, half_width, side);
VJOIN1(corners[3], first, half_width, side);
if (annot_mesh_add_triangle(mesh, corners[0], corners[1], corners[2],
normal, width, tol))
return 1;
return annot_mesh_add_triangle(mesh, corners[0], corners[2], corners[3],
normal, width, tol);
}
static void
annot_line_pattern(uint32_t pattern, const fastf_t **intervals,
size_t *interval_count)
{
/* Entries alternate mark and gap lengths in stroke-width units. */
static const fastf_t dashed[] = {8.0, 4.0};
static const fastf_t dotted[] = {1.0, 3.0};
static const fastf_t center[] = {8.0, 3.0, 2.0, 3.0};
static const fastf_t phantom[] = {8.0, 3.0, 2.0, 3.0, 2.0, 3.0};
*intervals = NULL;
*interval_count = 0;
switch (pattern) {
case RT_ANNOT_LINE_DASHED:
*intervals = dashed;
*interval_count = sizeof(dashed) / sizeof(dashed[0]);
break;
case RT_ANNOT_LINE_DOTTED:
*intervals = dotted;
*interval_count = sizeof(dotted) / sizeof(dotted[0]);
break;
case RT_ANNOT_LINE_CENTER:
*intervals = center;
*interval_count = sizeof(center) / sizeof(center[0]);
break;
case RT_ANNOT_LINE_PHANTOM:
*intervals = phantom;
*interval_count = sizeof(phantom) / sizeof(phantom[0]);
break;
default:
break;
}
}
static int
annot_mesh_add_stroke(struct annot_mesh *mesh, const point_t start,
const point_t end, const vect_t normal, fastf_t width, uint32_t pattern,
fastf_t *path_distance, const struct bn_tol *tol)
{
const fastf_t *intervals;
size_t interval_count;
vect_t delta;
fastf_t length;
fastf_t period = 0.0;
fastf_t position = 0.0;
fastf_t phase;
size_t interval = 0;
size_t i;
VSUB2(delta, end, start);
length = MAGNITUDE(delta);
if (INVALID(length))
return 1;
if (length <= tol->dist)
return 0;
annot_line_pattern(pattern, &intervals, &interval_count);
if (!interval_count) {
*path_distance += length;
return annot_mesh_add_stroke_span(mesh, start, end, normal, width, tol);
}
for (i = 0; i < interval_count; ++i)
period += intervals[i] * width;
phase = fmod(*path_distance, period);
while (phase >= intervals[interval] * width) {
phase -= intervals[interval] * width;
interval = (interval + 1) % interval_count;
}
while (position < length) {
fastf_t available = intervals[interval] * width - phase;
fastf_t run = available < length - position ? available : length - position;
if (!(interval & 1) && run > tol->dist) {
point_t run_start, run_end;
VJOIN1(run_start, start, position / length, delta);
VJOIN1(run_end, start, (position + run) / length, delta);
if (annot_mesh_add_stroke_span(mesh, run_start, run_end, normal,
width, tol))
return 1;
}
position += run;
phase = 0.0;
interval = (interval + 1) % interval_count;
}
*path_distance += length;
return 0;
}
static fastf_t
annot_default_width(const struct rt_annot_internal *annot_ip,
const struct bn_tol *tol)
{
fastf_t largest_text = 0.0;
fastf_t local_width = 0.0;
fastf_t plane_scale;
point2d_t minimum, maximum;
vect_t normal;
size_t i;
for (i = 0; i < annot_ip->ant.count; ++i) {
if (annot_ip->ant.segments[i] &&
*(uint32_t *)annot_ip->ant.segments[i] == ANN_TSEG_MAGIC) {
const struct txt_seg *text =
(const struct txt_seg *)annot_ip->ant.segments[i];
if (text->txt_size > largest_text)
largest_text = text->txt_size;
}
}
if (largest_text > 0.0) {
local_width = largest_text * ANNOT_TEXT_STROKE_RATIO;
} else if (annot_ip->vert_count) {
V2MOVE(minimum, annot_ip->verts[0]);
V2MOVE(maximum, annot_ip->verts[0]);
for (i = 1; i < annot_ip->vert_count; ++i) {
V_MIN(minimum[X], annot_ip->verts[i][X]);
V_MIN(minimum[Y], annot_ip->verts[i][Y]);
V_MAX(maximum[X], annot_ip->verts[i][X]);
V_MAX(maximum[Y], annot_ip->verts[i][Y]);
}
local_width = hypot(maximum[X] - minimum[X],
maximum[Y] - minimum[Y]) * ANNOT_GEOMETRY_STROKE_RATIO;
}
VCROSS(normal, annot_ip->u_vec, annot_ip->v_vec);
plane_scale = sqrt(MAGNITUDE(normal));
local_width *= plane_scale;
if (local_width < ANNOT_MIN_WIDTH_TOL_FACTOR * tol->dist)
local_width = ANNOT_MIN_WIDTH_TOL_FACTOR * tol->dist;
return local_width;
}
static int
annot_segment_mesh(struct annot_mesh *mesh,
const struct rt_annot_internal *annot_ip, size_t segment,
const struct bg_tess_tol *ttol, const struct bn_tol *tol,
const vect_t normal, fastf_t base_width)
{
struct bu_list vhead;
struct bu_list vlfree;
struct bv_vlist *vp;
const struct rt_annot_seg_style *style = annot_ip->styles ?
&annot_ip->styles[segment] : NULL;
uint32_t magic = *(uint32_t *)annot_ip->ant.segments[segment];
uint32_t pattern = style ? style->line_pattern : RT_ANNOT_LINE_CONTINUOUS;
fastf_t width = base_width;
point_t line_start = VINIT_ZERO;
point_t polygon[3];
fastf_t path_distance = 0.0;
size_t polygon_count = 0;
int have_line_start = 0;
int ret = 0;
if (style && (style->flags & RT_ANNOT_STYLE_WIDTH))
width *= style->line_width;
else if (style && magic == ANN_TSEG_MAGIC &&
(style->flags & RT_ANNOT_STYLE_BOLD))
width *= 2.0;
if (!isfinite(width) || width <= 0.0)
return 1;
BU_LIST_INIT(&vhead);
BU_LIST_INIT(&vlfree);
ret = seg_to_vlist(&vlfree, &vhead, ttol,
(fastf_t *)annot_ip->V, (struct rt_annot_internal *)annot_ip,
annot_ip->ant.segments[segment], style);
if (ret)
goto cleanup;
for (BU_LIST_FOR(vp, bv_vlist, &vhead)) {
size_t i;
for (i = 0; i < vp->nused; ++i) {
switch (vp->cmd[i]) {
case BV_VLIST_LINE_MOVE:
VMOVE(line_start, vp->pt[i]);
have_line_start = 1;
path_distance = 0.0;
break;
case BV_VLIST_LINE_DRAW:
if (have_line_start && annot_mesh_add_stroke(mesh,
line_start, vp->pt[i], normal, width, pattern,
&path_distance, tol)) {
ret = 1;
goto cleanup;
}
VMOVE(line_start, vp->pt[i]);
have_line_start = 1;
break;
case BV_VLIST_POLY_MOVE:
case BV_VLIST_TRI_MOVE:
VMOVE(polygon[0], vp->pt[i]);
polygon_count = 1;
break;
case BV_VLIST_POLY_DRAW:
case BV_VLIST_TRI_DRAW:
if (polygon_count < 3) {
VMOVE(polygon[polygon_count], vp->pt[i]);
polygon_count++;
}
break;
case BV_VLIST_POLY_END:
case BV_VLIST_TRI_END:
if (polygon_count == 3 && annot_mesh_add_triangle(mesh,
polygon[0], polygon[1], polygon[2], normal,
base_width, tol)) {
ret = 1;
goto cleanup;
}
polygon_count = 0;
break;
default:
break;
}
}
}
cleanup:
BV_FREE_VLIST(&vlfree, &vhead);
bv_vlist_cleanup(&vlfree);
return ret;
}
static int
annot_to_bot(struct rt_bot_internal **result,
const struct rt_annot_internal *annot_ip,
const struct bg_tess_tol *ttol, const struct bn_tol *tol,
unsigned char bot_mode)
{
struct annot_mesh mesh = {0};
struct rt_bot_internal *bot;
vect_t normal;
fastf_t base_width;
size_t i;
if (!result)
return 1;
*result = NULL;
if (!annot_ip || !ttol || !tol ||
(bot_mode != RT_BOT_PLATE && bot_mode != RT_BOT_SURFACE) ||
!(annot_ip->flags & RT_ANNOT_MODEL_SPACE))
return 1;
VCROSS(normal, annot_ip->u_vec, annot_ip->v_vec);
if (VINVALID(normal) || MAGNITUDE(normal) <= SMALL_FASTF)
return 1;
VUNITIZE(normal);
base_width = annot_default_width(annot_ip, tol);
if (!isfinite(base_width) || base_width <= 0.0)
return 1;
for (i = 0; i < annot_ip->ant.count; ++i) {
if (!annot_ip->ant.segments[i] ||
annot_is_fill_compatibility_outline(&annot_ip->ant, i))
continue;
if (annot_segment_mesh(&mesh, annot_ip, i, ttol, tol, normal,
base_width)) {
annot_mesh_free(&mesh);
return 1;
}
}
if (!mesh.vertex_count || !mesh.face_count) {
annot_mesh_free(&mesh);
return 1;
}
BU_ALLOC(bot, struct rt_bot_internal);
bot->magic = RT_BOT_INTERNAL_MAGIC;
bot->mode = bot_mode;
bot->orientation = RT_BOT_UNORIENTED;
bot->num_vertices = mesh.vertex_count;
bot->vertices = (fastf_t *)mesh.vertices;
bot->num_faces = mesh.face_count;
bot->faces = mesh.faces;
if (bot_mode == RT_BOT_PLATE)
bot->thickness = mesh.thickness;
else
bu_free(mesh.thickness, "annotation mesh thickness");
mesh.vertices = NULL;
mesh.faces = NULL;
mesh.thickness = NULL;
*result = bot;
return 0;
}
C_DECL int
rt_annot_tess(struct nmgregion **r, struct model *m,
struct rt_db_internal *ip, const struct bg_tess_tol *ttol,
const struct bn_tol *tol)
{
struct rt_bot_internal *bot = NULL;
struct rt_db_internal bot_intern;
const struct rt_annot_internal *annot_ip;
int ret;
if (!r || !m || !ip || !ttol || !tol)
return -1;
RT_CK_DB_INTERNAL(ip);
annot_ip = (const struct rt_annot_internal *)ip->idb_ptr;
if (rt_annot_validate(annot_ip, NULL) ||
annot_to_bot(&bot, annot_ip, ttol, tol, RT_BOT_SURFACE))
return -1;
RT_DB_INTERNAL_INIT(&bot_intern);
bot_intern.idb_major_type = DB5_MAJORTYPE_BRLCAD;
bot_intern.idb_type = ID_BOT;
bot_intern.idb_meth = &OBJ[ID_BOT];
bot_intern.idb_ptr = bot;
ret = rt_bot_tess(r, m, &bot_intern, ttol, tol);
rt_bot_ifree(&bot_intern);
return ret;
}
C_DECL int
rt_annot_plot(struct bu_list *vhead, struct rt_db_internal *ip, const struct bg_tess_tol *ttol, const struct bn_tol *UNUSED(tol), const struct bview *UNUSED(info))
{
@@ -3527,11 +4066,11 @@ rt_annot_adjust(struct bu_vls *logstr, struct rt_db_internal *intern, int argc,
fastf_t *new_vert;
vert_no = atol(argv[0] + 1);
if (vert_no < 0 || (size_t)vert_no >= annot_ip->vert_count) {
new_vert = annot_ip->verts[vert_no];
if (vert_no < 0 || (size_t)vert_no > annot_ip->vert_count) {
bu_vls_printf(logstr, "ERROR: Illegal vertex number\n");
return BRLCAD_ERROR;
}
new_vert = annot_ip->verts[vert_no];
array_len = 2;
if (_rt_tcl_list_to_fastf_array(argv[1], &new_vert, &array_len) != array_len) {
bu_vls_printf(logstr, "ERROR: Incorrect number of coordinates for vertex\n");
File diff suppressed because it is too large Load Diff
+202 -468
View File
@@ -45,28 +45,24 @@
* intersecting closed surface), and total_chord is the sum of solid
* segment lengths.
*
* Chord endpoints are independent pseudorandom points on the bounding
* sphere. Reinitializing the generator with a fixed seed makes sampling
* reproducible without changing the ordinary Monte Carlo convergence
* behavior on which the estimator's stability checks rely.
* References:
* Li et al. (2003), "Using low-discrepancy sequences and the Crofton
* formula to compute surface areas of geometric models",
* Computer-Aided Design 35, 771-782.
*
* Liu et al. (2010), "Surface area estimation of digitized 3D objects
* using quasi-Monte Carlo methods", Pattern Recognition 43, 3900-3909.
*/
#include "common.h"
#include <chrono>
#include <climits>
#include <condition_variable>
#include <cstdint>
#include <exception>
#include <mutex>
#include <random>
#include <stdlib.h>
#include <math.h>
#include <stdexcept>
#include <random>
#include <string.h>
#include <thread>
#include <time.h>
#include <vector>
#include "vmath.h"
#include "bu/log.h"
@@ -104,15 +100,8 @@
#define RT_CROFTON_STABILITY_MIN_WINDOWS 2u
/** Fixed seed for reproducible estimates in tests and cross-platform runs. */
#define RT_CROFTON_RNG_SEED UINT64_C(0x9e3779b97f4a7c15)
#define RT_CROFTON_RNG_SEED 0x9e3779b97f4a7c15ULL
/** Two sphere coordinates for each of two chord endpoints. */
#define RT_CROFTON_RANDOM_VALUES_PER_RAY 4u
/** Convert the high 53 random bits exactly to a double in [0, 1). */
static constexpr unsigned int CROFTON_RANDOM_SHIFT = 11u;
static constexpr double CROFTON_RANDOM_SCALE =
1.0 / 9007199254740992.0;
/* ------------------------------------------------------------------ */
/* Internal types */
@@ -121,7 +110,6 @@ static constexpr double CROFTON_RANDOM_SCALE =
struct crofton_ray {
point_t r_pt;
vect_t r_dir;
size_t id;
};
struct crofton_shared {
@@ -129,19 +117,14 @@ struct crofton_shared {
size_t total_crossings; /* in+out hit events */
double total_chord; /* solid segment length sum (mm) */
size_t total_rays; /* rays fired (hits + misses) */
size_t ray_offset; /* first identifier in this sample stream */
point_t *points;
size_t point_count;
size_t point_capacity;
struct rt_crofton_segment *segments;
size_t segment_count;
size_t segment_capacity;
bool collect_points;
bool collect_segments;
};
struct crofton_worker_data {
struct application ap; /* per-CPU application struct */
struct application *ap; /* per-CPU application struct */
struct crofton_ray *rays; /* shared ray array (read-only) */
size_t start;
size_t end;
@@ -152,10 +135,6 @@ struct crofton_worker_data {
point_t *local_points;
size_t local_point_count;
size_t local_point_capacity;
struct rt_crofton_segment *local_segments;
size_t local_segment_count;
size_t local_segment_capacity;
size_t current_ray;
};
@@ -164,88 +143,46 @@ struct crofton_worker_data {
/* ------------------------------------------------------------------ */
static int
crofton_hit(struct application *ap, struct partition *PartHeadp,
struct seg *UNUSED(segs))
crofton_hit(struct application *ap, struct partition *PartHeadp, struct seg *UNUSED(segs))
{
struct crofton_worker_data *worker =
(struct crofton_worker_data *)ap->a_uptr;
struct crofton_worker_data *wd = (struct crofton_worker_data *)ap->a_uptr;
size_t crossings = 0;
double chord = 0.0;
double chord = 0.0;
struct partition *partition;
for (partition = PartHeadp->pt_forw; partition != PartHeadp;
partition = partition->pt_forw) {
fastf_t thickness = partition->pt_outhit->hit_dist -
partition->pt_inhit->hit_dist;
crossings += 2;
chord += thickness;
if (worker->shared->collect_points) {
if (worker->local_point_count + 2 >
worker->local_point_capacity) {
const size_t initial_capacity = 1024;
size_t new_capacity = worker->local_point_capacity ?
2 * worker->local_point_capacity : initial_capacity;
while (new_capacity < worker->local_point_count + 2)
new_capacity *= 2;
worker->local_points = (point_t *)bu_realloc(
worker->local_points,
new_capacity * sizeof(point_t),
"Crofton worker hit points");
worker->local_point_capacity = new_capacity;
}
VJOIN1(worker->local_points[worker->local_point_count],
ap->a_ray.r_pt, partition->pt_inhit->hit_dist,
ap->a_ray.r_dir);
worker->local_point_count++;
VJOIN1(worker->local_points[worker->local_point_count],
ap->a_ray.r_pt, partition->pt_outhit->hit_dist,
ap->a_ray.r_dir);
worker->local_point_count++;
}
if (worker->shared->collect_segments) {
if (worker->local_segment_count + 1 >
worker->local_segment_capacity) {
const size_t initial_capacity = 512;
size_t new_capacity = worker->local_segment_capacity ?
2 * worker->local_segment_capacity : initial_capacity;
worker->local_segments =
(struct rt_crofton_segment *)bu_realloc(
worker->local_segments,
new_capacity * sizeof(struct rt_crofton_segment),
"Crofton worker segments");
worker->local_segment_capacity = new_capacity;
}
struct rt_crofton_segment *sample =
&worker->local_segments[worker->local_segment_count];
struct soltab *in_solid =
partition->pt_inseg->seg_stp;
struct soltab *out_solid =
partition->pt_outseg->seg_stp;
VJOIN1(sample->in_point, ap->a_ray.r_pt,
partition->pt_inhit->hit_dist, ap->a_ray.r_dir);
VJOIN1(sample->out_point, ap->a_ray.r_pt,
partition->pt_outhit->hit_dist, ap->a_ray.r_dir);
RT_HIT_NORMAL(sample->in_normal, partition->pt_inhit,
in_solid, &ap->a_ray, partition->pt_inflip);
RT_HIT_NORMAL(sample->out_normal, partition->pt_outhit,
out_solid, &ap->a_ray, partition->pt_outflip);
sample->thickness = thickness;
sample->ray_id = worker->rays[worker->current_ray].id;
if (!VNEAR_ZERO(sample->in_normal, VUNITIZE_TOL) &&
!VNEAR_ZERO(sample->out_normal, VUNITIZE_TOL))
worker->local_segment_count++;
}
struct partition *pp;
for (pp = PartHeadp->pt_forw; pp != PartHeadp; pp = pp->pt_forw) {
/* Each partition contributes an in-hit and an out-hit */
crossings += 2;
chord += pp->pt_outhit->hit_dist - pp->pt_inhit->hit_dist;
if (wd->shared->collect_points) {
if (wd->local_point_count + 2 > wd->local_point_capacity) {
const size_t initial_capacity = 1024;
size_t new_capacity = wd->local_point_capacity ?
2 * wd->local_point_capacity : initial_capacity;
while (new_capacity < wd->local_point_count + 2)
new_capacity *= 2;
wd->local_points = (point_t *)bu_realloc(wd->local_points,
new_capacity * sizeof(point_t), "Crofton worker hit points");
wd->local_point_capacity = new_capacity;
}
VJOIN1(wd->local_points[wd->local_point_count], ap->a_ray.r_pt,
pp->pt_inhit->hit_dist, ap->a_ray.r_dir);
wd->local_point_count++;
VJOIN1(wd->local_points[wd->local_point_count], ap->a_ray.r_pt,
pp->pt_outhit->hit_dist, ap->a_ray.r_dir);
wd->local_point_count++;
}
}
worker->local_crossings += crossings;
worker->local_chord += chord;
worker->local_rays++;
wd->local_crossings += crossings;
wd->local_chord += chord;
wd->local_rays += 1;
return 1;
}
static int
crofton_miss(struct application *ap)
{
@@ -256,273 +193,159 @@ crofton_miss(struct application *ap)
}
/* ------------------------------------------------------------------ */
/* Parallel worker */
/* ------------------------------------------------------------------ */
static void
crofton_worker(int id, void *data)
{
struct crofton_worker_data *wd = &((struct crofton_worker_data *)data)[id - 1];
struct application *ap = wd->ap;
struct crofton_ray *rays = wd->rays;
for (size_t i = wd->start; i < wd->end; i++) {
VMOVE(ap->a_ray.r_pt, rays[i].r_pt);
VMOVE(ap->a_ray.r_dir, rays[i].r_dir);
/* r_dir is already unit-length (set during ray generation) */
rt_shootray(ap);
}
}
/* ------------------------------------------------------------------ */
/* Point / ray generation */
/* ------------------------------------------------------------------ */
static double
crofton_random01(std::mt19937_64 &rng)
crofton_rand01(std::mt19937_64 &rng)
{
return static_cast<double>(rng() >> CROFTON_RANDOM_SHIFT) *
CROFTON_RANDOM_SCALE;
return std::generate_canonical<double, 53>(rng);
}
static void
random_point_on_sphere(double radius, const point_t center, double u,
double v, point_t point)
random_point_on_sphere(double radius, const point_t center, point_t out, std::mt19937_64 &rng)
{
double z = 1.0 - 2.0 * u;
double radial = sqrt(std::max(0.0, 1.0 - z * z));
double angle = 2.0 * M_PI * v;
point[X] = center[X] + radius * radial * cos(angle);
point[Y] = center[Y] + radius * radial * sin(angle);
point[Z] = center[Z] + radius * z;
double theta = 2.0 * M_PI * crofton_rand01(rng);
double phi = acos(2.0 * crofton_rand01(rng) - 1.0);
double sp = sin(phi);
out[X] = center[X] + radius * sp * cos(theta);
out[Y] = center[Y] + radius * sp * sin(theta);
out[Z] = center[Z] + radius * cos(phi);
}
/**
* Generate independent random chord endpoints. Pairing consecutive IID
* sphere points has the same distribution as shuffling a point array before
* pairing, but consumes exactly four random values per ray. That fixed
* stride lets callers continue the deterministic stream with ray_offset.
* Generate @p nrays chord rays from 2*nrays random points on the
* bounding sphere. Each ray connects one randomly chosen point to
* another.
*/
static void
generate_rays(struct crofton_ray *rays, size_t ray_count, size_t first_id,
double radius, const point_t center, std::mt19937_64 &rng)
generate_rays(struct crofton_ray *rays, size_t nrays,
double radius, const point_t center, std::mt19937_64 &rng)
{
for (size_t i = 0; i < ray_count; i++) {
point_t endpoint;
random_point_on_sphere(radius, center, crofton_random01(rng),
crofton_random01(rng), rays[i].r_pt);
random_point_on_sphere(radius, center, crofton_random01(rng),
crofton_random01(rng), endpoint);
VSUB2(rays[i].r_dir, endpoint, rays[i].r_pt);
VUNITIZE(rays[i].r_dir);
rays[i].id = first_id + i;
size_t npts = nrays * 2;
point_t *pts = (point_t *)bu_calloc(npts, sizeof(point_t), "crofton pts");
for (size_t i = 0; i < npts; i++)
random_point_on_sphere(radius, center, pts[i], rng);
/* Shuffle so pairing is random */
for (size_t i = npts - 1; i > 0; i--) {
size_t j = (size_t)(crofton_rand01(rng) * (i + 1));
if (j > i) j = i;
point_t tmp;
VMOVE(tmp, pts[i]);
VMOVE(pts[i], pts[j]);
VMOVE(pts[j], tmp);
}
for (size_t i = 0; i < nrays; i++) {
VMOVE(rays[i].r_pt, pts[i * 2]);
VSUB2(rays[i].r_dir, pts[i * 2 + 1], pts[i * 2]);
VUNITIZE(rays[i].r_dir);
}
bu_free(pts, "crofton pts");
}
/* ------------------------------------------------------------------ */
/* Reusable parallel workers */
/* One iteration: fire nrays, accumulate into shared */
/* ------------------------------------------------------------------ */
class CroftonWorkerPool
{
public:
CroftonWorkerPool(const struct application *ap_template,
struct resource *resources, size_t worker_count) :
count(worker_count), data(worker_count)
{
for (size_t i = 0; i < count; i++) {
data[i].ap = *ap_template;
data[i].ap.a_resource = &resources[i];
data[i].shared = NULL;
data[i].ap.a_uptr = &data[i];
}
try {
executor = std::thread(&CroftonWorkerPool::run_workers, this);
} catch (...) {
free_worker_points();
throw;
}
std::unique_lock<std::mutex> lock(mutex);
if (!workers_started.wait_for(lock, worker_start_timeout,
[this]() { return registered == count; })) {
stopping = true;
work_ready.notify_all();
lock.unlock();
executor.join();
free_worker_points();
throw std::runtime_error("worker startup timed out");
}
}
~CroftonWorkerPool()
{
stop_and_join();
free_worker_points();
}
CroftonWorkerPool(const CroftonWorkerPool &) = delete;
CroftonWorkerPool &operator=(const CroftonWorkerPool &) = delete;
void run(struct crofton_ray *rays, size_t nrays,
struct crofton_shared *shared)
{
const size_t rays_per_worker = nrays / count;
std::unique_lock<std::mutex> lock(mutex);
for (size_t i = 0; i < count; i++) {
struct crofton_worker_data &wd = data[i];
wd.rays = rays;
wd.start = i * rays_per_worker;
wd.end = (i == count - 1) ? nrays : (i + 1) * rays_per_worker;
wd.shared = shared;
wd.local_crossings = 0;
wd.local_chord = 0.0;
wd.local_rays = 0;
wd.local_point_count = 0;
wd.local_segment_count = 0;
}
completed = 0;
generation++;
work_ready.notify_all();
work_done.wait(lock, [this]() { return completed == count; });
}
const std::vector<struct crofton_worker_data> &worker_data() const
{
return data;
}
private:
/* Native thread creation should complete promptly. A bounded startup wait
* turns partial bu_parallel startup into a reported failure, not a hang. */
static constexpr std::chrono::seconds worker_start_timeout{5};
static void worker_entry(int UNUSED(id), void *context)
{
CroftonWorkerPool *pool = static_cast<CroftonWorkerPool *>(context);
size_t slot = 0;
{
std::lock_guard<std::mutex> lock(pool->mutex);
slot = pool->registered++;
if (pool->registered == pool->count)
pool->workers_started.notify_one();
}
pool->worker(slot);
}
void run_workers()
{
bu_parallel(worker_entry, count, this);
}
void worker(size_t slot)
{
size_t observed_generation = 0;
std::unique_lock<std::mutex> lock(mutex);
while (true) {
work_ready.wait(lock, [this, observed_generation]() {
return stopping || generation != observed_generation;
});
if (stopping)
return;
observed_generation = generation;
struct crofton_worker_data *wd = &data[slot];
lock.unlock();
for (size_t i = wd->start; i < wd->end; i++) {
wd->current_ray = i;
VMOVE(wd->ap.a_ray.r_pt, wd->rays[i].r_pt);
VMOVE(wd->ap.a_ray.r_dir, wd->rays[i].r_dir);
rt_shootray(&wd->ap);
}
lock.lock();
completed++;
if (completed == count)
work_done.notify_one();
}
}
void stop_and_join()
{
{
std::lock_guard<std::mutex> lock(mutex);
stopping = true;
work_ready.notify_all();
}
if (executor.joinable())
executor.join();
}
void free_worker_points()
{
for (struct crofton_worker_data &wd : data) {
if (wd.local_points)
bu_free(wd.local_points, "Crofton worker hit points");
if (wd.local_segments)
bu_free(wd.local_segments, "Crofton worker segments");
wd.local_points = NULL;
wd.local_segments = NULL;
}
}
size_t count;
std::vector<struct crofton_worker_data> data;
std::thread executor;
std::mutex mutex;
std::condition_variable workers_started;
std::condition_variable work_ready;
std::condition_variable work_done;
size_t registered = 0;
size_t generation = 0;
size_t completed = 0;
bool stopping = false;
};
static void
do_one_iteration(CroftonWorkerPool &pool, size_t ray_count, double radius,
const point_t center, struct crofton_shared *shared,
std::mt19937_64 &rng)
do_one_iteration(struct application *ap_template,
struct resource *resources,
size_t nrays,
double radius,
const point_t center,
struct crofton_shared *shared,
std::mt19937_64 &rng)
{
struct crofton_ray *rays = (struct crofton_ray *)bu_calloc(
ray_count, sizeof(struct crofton_ray), "Crofton rays");
generate_rays(rays, ray_count, shared->ray_offset + shared->total_rays,
radius, center, rng);
pool.run(rays, ray_count, shared);
nrays, sizeof(struct crofton_ray), "crofton rays");
for (const struct crofton_worker_data &worker : pool.worker_data()) {
shared->total_crossings += worker.local_crossings;
shared->total_chord += worker.local_chord;
shared->total_rays += worker.local_rays;
generate_rays(rays, nrays, radius, center, rng);
if (worker.local_point_count) {
const size_t needed =
shared->point_count + worker.local_point_count;
if (needed > shared->point_capacity) {
size_t new_capacity = shared->point_capacity ?
2 * shared->point_capacity : needed;
if (new_capacity < needed)
new_capacity = needed;
shared->points = (point_t *)bu_realloc(shared->points,
new_capacity * sizeof(point_t),
"Crofton hit points");
shared->point_capacity = new_capacity;
}
memcpy(&shared->points[shared->point_count],
worker.local_points,
worker.local_point_count * sizeof(point_t));
shared->point_count = needed;
}
size_t ncpus = bu_avail_cpus();
if (ncpus < 1) ncpus = 1;
if (ncpus > MAX_PSW) ncpus = MAX_PSW;
if (worker.local_segment_count) {
const size_t needed =
shared->segment_count + worker.local_segment_count;
if (needed > shared->segment_capacity) {
size_t new_capacity = shared->segment_capacity ?
2 * shared->segment_capacity : needed;
if (new_capacity < needed)
new_capacity = needed;
shared->segments =
(struct rt_crofton_segment *)bu_realloc(
shared->segments,
new_capacity * sizeof(struct rt_crofton_segment),
"Crofton segments");
shared->segment_capacity = new_capacity;
}
memcpy(&shared->segments[shared->segment_count],
worker.local_segments,
worker.local_segment_count *
sizeof(struct rt_crofton_segment));
shared->segment_count = needed;
}
struct crofton_worker_data *wdata = (struct crofton_worker_data *)bu_calloc(
ncpus, sizeof(struct crofton_worker_data), "crofton wdata");
size_t per_cpu = nrays / ncpus;
for (size_t i = 0; i < ncpus; i++) {
struct application *a = (struct application *)bu_calloc(
1, sizeof(struct application), "crofton app");
*a = *ap_template; /* struct copy */
a->a_resource = &resources[i];
struct crofton_worker_data *wd = &wdata[i];
wd->ap = a;
wd->rays = rays;
wd->start = i * per_cpu;
wd->end = (i == ncpus - 1) ? nrays : (i + 1) * per_cpu;
wd->shared = shared;
a->a_uptr = wd;
}
bu_free(rays, "Crofton rays");
bu_parallel(crofton_worker, (int)ncpus, (void *)wdata);
for (size_t i = 0; i < ncpus; i++) {
shared->total_crossings += wdata[i].local_crossings;
shared->total_chord += wdata[i].local_chord;
shared->total_rays += wdata[i].local_rays;
if (wdata[i].local_point_count) {
const size_t needed = shared->point_count + wdata[i].local_point_count;
if (needed > shared->point_capacity) {
size_t new_capacity = shared->point_capacity ?
2 * shared->point_capacity : needed;
if (new_capacity < needed)
new_capacity = needed;
shared->points = (point_t *)bu_realloc(shared->points,
new_capacity * sizeof(point_t), "Crofton hit points");
shared->point_capacity = new_capacity;
}
memcpy(&shared->points[shared->point_count], wdata[i].local_points,
wdata[i].local_point_count * sizeof(point_t));
shared->point_count = needed;
}
}
for (size_t i = 0; i < ncpus; i++) {
if (wdata[i].local_points)
bu_free(wdata[i].local_points, "Crofton worker hit points");
bu_free(wdata[i].ap, "crofton app");
}
bu_free(wdata, "crofton wdata");
bu_free(rays, "crofton rays");
}
/* ------------------------------------------------------------------ */
/* Public API: rt_crofton_shoot */
/* ------------------------------------------------------------------ */
@@ -552,8 +375,7 @@ do_one_iteration(CroftonWorkerPool &pool, size_t ray_count, double radius,
* sampling (>= 0) on success; -1 on bad arguments.
*/
static int
crofton_shoot_impl(struct rt_crofton_result *out_result,
point_t **out_points,
crofton_shoot_impl(point_t **out_points,
size_t *out_point_count,
double *out_surf_area,
double *out_volume,
@@ -562,22 +384,13 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
point_t out_obb[8],
struct rt_i *rtip,
const struct rt_crofton_params *params,
size_t ray_offset,
const fastf_t *bbox_min,
const fastf_t *bbox_max)
{
if (!rtip ||
(!out_result && !out_surf_area && !out_volume && !out_aabb_min &&
!out_obb && !out_points) ||
((out_points == NULL) != (out_point_count == NULL)) ||
((out_aabb_min == NULL) != (out_aabb_max == NULL)) ||
(out_result && out_result->segments) ||
ray_offset > UINT64_MAX / RT_CROFTON_RANDOM_VALUES_PER_RAY)
return -1;
if (out_result) {
struct rt_crofton_result empty = RT_CROFTON_RESULT_INIT;
*out_result = empty;
}
if (!rtip || (!out_surf_area && !out_volume && !out_aabb_min && !out_obb && !out_points) ||
((out_points == NULL) != (out_point_count == NULL)) ||
((out_aabb_min == NULL) != (out_aabb_max == NULL)))
return -1;
if (out_points) {
*out_points = NULL;
*out_point_count = 0;
@@ -660,14 +473,10 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
if (!use_default && max_rays > 0 && stability_mm <= 0.0 && time_ms <= 0.0)
batch = max_rays; /* single-iteration mode */
size_t ncpus = bu_avail_cpus();
if (ncpus < 1) ncpus = 1;
if (ncpus > MAX_PSW) ncpus = MAX_PSW;
/* ---- Initialize per-CPU resources ---- */
struct resource *resources = (struct resource *)bu_calloc(
ncpus, sizeof(struct resource), "crofton resources");
for (size_t i = 0; i < ncpus; i++)
MAX_PSW, sizeof(struct resource), "crofton resources");
for (int i = 0; i < MAX_PSW; i++)
rt_init_resource(&resources[i], i, rtip);
/* ---- Set up application template ---- */
@@ -685,32 +494,10 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
/* ---- Shared accumulator ---- */
struct crofton_shared shared;
memset(&shared, 0, sizeof(shared));
shared.ray_offset = ray_offset;
shared.collect_points =
out_points != NULL || out_aabb_min != NULL || out_obb != NULL;
shared.collect_segments = out_result != NULL;
const bool stabilize_surface =
out_surf_area != NULL || out_result != NULL ||
shared.collect_points;
CroftonWorkerPool *pool = NULL;
try {
pool = new CroftonWorkerPool(&ap, resources, ncpus);
} catch (const std::exception &e) {
bu_log("rt_crofton: unable to start worker pool: %s\n", e.what());
for (size_t i = 0; i < ncpus; i++) {
if (resources[i].re_magic == RESOURCE_MAGIC) {
rt_clean_resource_basic(rtip, &resources[i]);
BU_PTBL_SET(&rtip->rti_resources, i, NULL);
}
}
bu_free(resources, "crofton resources");
return -1;
}
shared.collect_points = out_points != NULL || out_aabb_min != NULL || out_obb != NULL;
const bool stabilize_surface = out_surf_area != NULL || shared.collect_points;
std::mt19937_64 rng(RT_CROFTON_RNG_SEED);
rng.discard(static_cast<uint64_t>(ray_offset) *
RT_CROFTON_RANDOM_VALUES_PER_RAY);
const double FOUR_PI = 4.0 * M_PI;
const double PI = M_PI;
@@ -734,7 +521,7 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
curr_rays = batch;
}
do_one_iteration(*pool, curr_rays, R, center, &shared, rng);
do_one_iteration(&ap, resources, curr_rays, R, center, &shared, rng);
iteration++;
if (shared.total_rays == 0) break;
@@ -775,12 +562,9 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
} else {
/* ---- Parametric loop: n_rays / stability_mm / time_ms ---- */
double prev_r_sa = -1.0, prev_r_v = -1.0;
double prev_sa = -1.0, prev_v = -1.0;
size_t total_fired = 0;
size_t stable_windows = 0;
int64_t t0 = (time_ms > 0.0) ? bu_gettime() : 0;
const bool use_relative_stability = out_result && time_ms > 0.0 &&
stability_mm <= 0.0;
for (;;) {
/* Time-budget check before firing */
@@ -797,7 +581,7 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
if (fire > remaining) fire = remaining;
}
do_one_iteration(*pool, fire, R, center, &shared, rng);
do_one_iteration(&ap, resources, fire, R, center, &shared, rng);
total_fired += fire;
if (shared.total_rays == 0) break;
@@ -810,39 +594,27 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
/ (double)shared.total_rays;
/* Stability check */
if ((stability_mm > 0.0 || use_relative_stability) &&
if (stability_mm > 0.0 &&
shared.total_rays >= RT_CROFTON_STABILITY_MIN_RAYS &&
shared.total_crossings >= RT_CROFTON_STABILITY_MIN_CROSSINGS) {
bool sa_ok = false;
bool v_ok = false;
if (use_relative_stability) {
sa_ok = !stabilize_surface || (prev_sa > 0.0 &&
fabs(curr_est_sa - prev_sa) / prev_sa * 100.0 <=
RT_CROFTON_DEFAULT_THRESHOLD);
v_ok = !out_volume || (prev_v > 0.0 &&
fabs(curr_est_v - prev_v) / prev_v * 100.0 <=
RT_CROFTON_DEFAULT_THRESHOLD);
prev_sa = curr_est_sa;
prev_v = curr_est_v;
} else {
double r_sa = (curr_est_sa > 0.0) ?
sqrt(curr_est_sa * INV_4PI) : 0.0;
double r_v = (curr_est_v > 0.0) ?
cbrt(curr_est_v * INV_4PI3) : 0.0;
sa_ok = !stabilize_surface || (prev_r_sa >= 0.0 &&
fabs(r_sa - prev_r_sa) < stability_mm);
v_ok = !out_volume || (prev_r_v >= 0.0 &&
fabs(r_v - prev_r_v) < stability_mm);
prev_r_sa = r_sa;
prev_r_v = r_v;
}
if (sa_ok && v_ok) {
stable_windows++;
if (stable_windows >= RT_CROFTON_STABILITY_MIN_WINDOWS)
break;
} else {
stable_windows = 0;
double r_sa = (curr_est_sa > 0.0) ? sqrt(curr_est_sa * INV_4PI) : 0.0;
double r_v = (curr_est_v > 0.0) ? cbrt(curr_est_v * INV_4PI3) : 0.0;
if (prev_r_sa >= 0.0) {
int sa_ok = (!stabilize_surface) ||
fabs(r_sa - prev_r_sa) < stability_mm;
int v_ok = (!out_volume) ||
fabs(r_v - prev_r_v) < stability_mm;
if (sa_ok && v_ok) {
stable_windows++;
if (stable_windows >= RT_CROFTON_STABILITY_MIN_WINDOWS)
break;
} else {
stable_windows = 0;
}
}
prev_r_sa = r_sa;
prev_r_v = r_v;
}
/* Time-budget check after firing */
@@ -852,16 +624,8 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
}
}
if (out_surf_area)
*out_surf_area = curr_est_sa;
if (out_volume)
*out_volume = curr_est_v;
if (out_result) {
out_result->surface_area = curr_est_sa;
out_result->volume = curr_est_v;
out_result->ray_count = shared.total_rays;
out_result->crossing_count = shared.total_crossings;
}
if (out_surf_area) *out_surf_area = curr_est_sa;
if (out_volume) *out_volume = curr_est_v;
if (out_aabb_min) {
for (size_t i = 0; i < shared.point_count; ++i)
VMINMAX(*out_aabb_min, *out_aabb_max, shared.points[i]);
@@ -877,19 +641,11 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
bounds_ret = -1;
}
if (out_points && !bounds_ret) {
*out_points = shared.points;
*out_point_count = shared.point_count;
*out_points = shared.points;
*out_point_count = shared.point_count;
} else if (shared.points) {
bu_free(shared.points, "Crofton hit points");
bu_free(shared.points, "Crofton hit points");
}
if (out_result && !bounds_ret) {
out_result->segments = shared.segments;
out_result->segment_count = shared.segment_count;
} else if (shared.segments) {
bu_free(shared.segments, "Crofton segments");
}
delete pool;
/* Clean each resource and NULL out its slot in rtip->rti_resources.
* This is necessary because crofton_from_ip calls rt_i_destroy(rtip)
@@ -901,7 +657,7 @@ crofton_shoot_impl(struct rt_crofton_result *out_result,
*
* By setting the slot to NULL we let rt_i_destroy's cleanup skip it,
* and then we can safely bu_free the resources array. */
for (size_t i = 0; i < ncpus; i++) {
for (int i = 0; i < MAX_PSW; i++) {
if (resources[i].re_magic == RESOURCE_MAGIC) {
rt_clean_resource_basic(rtip, &resources[i]);
BU_PTBL_SET(&rtip->rti_resources, i, NULL);
@@ -925,31 +681,9 @@ rt_crofton_shoot(double *out_surf_area, double *out_volume,
const struct rt_crofton_params *params,
const fastf_t *bbox_min, const fastf_t *bbox_max)
{
return crofton_shoot_impl(NULL, out_points, out_point_count,
out_surf_area, out_volume, out_aabb_min, out_aabb_max, out_obb,
rtip, params, 0, bbox_min, bbox_max);
}
int
rt_crofton_collect(struct rt_crofton_result *result, struct rt_i *rtip,
const struct rt_crofton_params *params, size_t ray_offset,
const fastf_t *bbox_min, const fastf_t *bbox_max)
{
if (!result)
return -1;
return crofton_shoot_impl(result, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, rtip, params, ray_offset, bbox_min, bbox_max);
}
void
rt_crofton_result_free(struct rt_crofton_result *result)
{
if (!result)
return;
if (result->segments)
bu_free(result->segments, "Crofton segments");
struct rt_crofton_result empty = RT_CROFTON_RESULT_INIT;
*result = empty;
return crofton_shoot_impl(out_points, out_point_count, out_surf_area,
out_volume, out_aabb_min, out_aabb_max, out_obb, rtip, params,
bbox_min, bbox_max);
}
@@ -1087,7 +821,7 @@ crofton_from_ip_n(const struct rt_db_internal *ip,
double sa = 0.0;
double vol = 0.0;
(void)rt_crofton_shoot(&sa, &vol, NULL, NULL, NULL, NULL, NULL,
rtip, params, NULL, NULL);
rtip, params, NULL, NULL);
if (out_sa) *out_sa = sa;
if (out_vol) *out_vol = vol;
+59 -9
View File
@@ -1543,7 +1543,8 @@ permute_cell(point_t A,
struct dsp_specific *dsp,
struct dsp_rpp *dsp_rpp)
{
point_t tmp;
int x, y;
#ifdef FULL_DSP_DEBUGGING
if (RT_G_DEBUG & RT_DEBUG_HF) {
@@ -1557,8 +1558,10 @@ permute_cell(point_t A,
switch (dsp->dsp_i.dsp_cuttype) {
case DSP_CUT_DIR_llUR:
return DSP_CUT_DIR_llUR;
break;
case DSP_CUT_DIR_ADAPT: {
point_t tmp;
size_t cx = (size_t)dsp_rpp->dsp_min[X];
size_t cy = (size_t)dsp_rpp->dsp_min[Y];
@@ -1567,8 +1570,28 @@ permute_cell(point_t A,
dsp_rpp->dsp_min[X],
dsp_rpp->dsp_min[Y]);
/* The rpp spans one grid cell; use its indices to select the
* lower-curvature diagonal.
/*
* We look at the points in the diagonal next cells to
* determine the curvature along each diagonal of this
* cell. This cell is divided into two triangles by
* cutting across the cell in the direction of least
* curvature.
*
* * * * *
* \ /
* \C D/
* * *--* *
* |\/|
* |/\|
* * *--* *
* /A B\
* / \
* * * * *
*/
/* leaf-cell precondition: the rpp spans exactly one grid unit, so
* (cx, cy) plus the cell counts fully describe the curvature
* stencil that rt_dsp_cell_cut() recomputes.
*/
BU_ASSERT(dsp_rpp->dsp_max[X] == dsp_rpp->dsp_min[X] + 1);
BU_ASSERT(dsp_rpp->dsp_max[Y] == dsp_rpp->dsp_min[Y] + 1);
@@ -1576,16 +1599,15 @@ permute_cell(point_t A,
if (rt_dsp_cell_cut(&dsp->dsp_i, cx, cy,
(size_t)dsp->xsiz, (size_t)dsp->ysiz)
== DSP_CUT_DIR_llUR) {
/* A-D cut is fine, no need to permute */
if (RT_G_DEBUG & RT_DEBUG_HF)
bu_log("A-D cut\n");
return DSP_CUT_DIR_llUR;
}
}
/* fall through */
case DSP_CUT_DIR_ULlr:
/* Both B-C modes need the same corner order. The top triangles
* must keep outward (+Z) normals for the entry/exit state machine.
*/
/* prefer the B-C cut */
VMOVE(tmp, A);
VMOVE(A, B);
VMOVE(B, D);
@@ -1593,7 +1615,35 @@ permute_cell(point_t A,
VMOVE(C, tmp);
if (RT_G_DEBUG & RT_DEBUG_HF)
bu_log("B-C cut\n");
return DSP_CUT_DIR_ULlr;
break;
}
case DSP_CUT_DIR_ULlr:
/* assign the values for the corner points
*
* D----C
* | |
* | |
* | |
* B----A
*/
x = dsp_rpp->dsp_min[X];
y = dsp_rpp->dsp_min[Y];
VSET(B, x, y, DSP(&dsp->dsp_i, x, y));
x = dsp_rpp->dsp_max[X];
VSET(A, x, y, DSP(&dsp->dsp_i, x, y));
y = dsp_rpp->dsp_max[Y];
VSET(C, x, y, DSP(&dsp->dsp_i, x, y));
x = dsp_rpp->dsp_min[X];
VSET(D, x, y, DSP(&dsp->dsp_i, x, y));
return DSP_CUT_DIR_ULlr;
break;
}
bu_log("%s:%d Unknown DSP cut direction: %d\n",
__FILE__, __LINE__, dsp->dsp_i.dsp_cuttype);
-108
View File
@@ -43,18 +43,6 @@ rel_err(double estimated, double exact)
}
static int
crofton_segments_equal(const struct rt_crofton_segment *left,
const struct rt_crofton_segment *right)
{
return left->ray_id == right->ray_id &&
NEAR_EQUAL(left->thickness, right->thickness, SMALL_FASTF) &&
VNEAR_EQUAL(left->in_point, right->in_point, SMALL_FASTF) &&
VNEAR_EQUAL(left->in_normal, right->in_normal, SMALL_FASTF) &&
VNEAR_EQUAL(left->out_point, right->out_point, SMALL_FASTF) &&
VNEAR_EQUAL(left->out_normal, right->out_normal, SMALL_FASTF);
}
static int
verify_crofton_estimates(void)
{
@@ -391,102 +379,6 @@ run_convergence_case(struct db_i *dbip,
&sample_points, &sample_count,
rtip, &p, NULL, NULL);
run_sec = (double)(bu_gettime() - t0) / 1000000.0;
if (i == 0) {
struct rt_crofton_result samples = RT_CROFTON_RESULT_INIT;
const size_t ray_offset = 4096u;
const size_t continuation_rays = 256u;
struct rt_crofton_params sample_params =
{ray_offset + continuation_rays, 0.0, 0.0};
int sample_ret = rt_crofton_collect(&samples, rtip,
&sample_params, 0, NULL, NULL);
int full_samples_valid = sample_ret > 0 && samples.segments &&
samples.segment_count * 2 == samples.crossing_count &&
samples.ray_count == sample_params.n_rays;
if (!full_samples_valid) {
printf(" %-24s invalid structured Crofton output\\n", label);
failures++;
} else {
for (size_t sample_index = 0;
sample_index < samples.segment_count; sample_index++) {
struct rt_crofton_segment *segment =
&samples.segments[sample_index];
if (segment->thickness <= 0.0 ||
!NEAR_EQUAL(DIST_PNT_PNT(segment->in_point,
segment->out_point), segment->thickness, RT_LEN_TOL) ||
!NEAR_EQUAL(MAGNITUDE(segment->in_normal), 1.0,
VUNITIZE_TOL) ||
!NEAR_EQUAL(MAGNITUDE(segment->out_normal), 1.0,
VUNITIZE_TOL) ||
segment->ray_id >= samples.ray_count) {
printf(" %-24s invalid structured segment\\n", label);
failures++;
break;
}
}
}
struct rt_crofton_result offset_samples =
RT_CROFTON_RESULT_INIT;
struct rt_crofton_params offset_params =
{continuation_rays, 0.0, 0.0};
sample_ret = rt_crofton_collect(&offset_samples, rtip,
&offset_params, ray_offset, NULL, NULL);
int offset_samples_valid = sample_ret > 0 &&
offset_samples.ray_count == offset_params.n_rays;
if (!offset_samples_valid) {
printf(" %-24s invalid offset Crofton output\\n", label);
failures++;
} else {
for (size_t sample_index = 0;
sample_index < offset_samples.segment_count;
sample_index++) {
size_t ray_id = offset_samples.segments[sample_index].ray_id;
if (ray_id < ray_offset ||
ray_id >= ray_offset + offset_params.n_rays) {
printf(" %-24s invalid offset ray identifier\\n",
label);
failures++;
break;
}
}
}
if (full_samples_valid && offset_samples_valid) {
size_t suffix_start = 0;
while (suffix_start < samples.segment_count &&
samples.segments[suffix_start].ray_id < ray_offset)
suffix_start++;
size_t suffix_count = samples.segment_count - suffix_start;
if (suffix_count != offset_samples.segment_count) {
printf(" %-24s random stream continuation count mismatch\\n",
label);
failures++;
} else {
for (size_t sample_index = 0;
sample_index < suffix_count; sample_index++) {
const struct rt_crofton_segment *full_segment =
&samples.segments[suffix_start + sample_index];
const struct rt_crofton_segment *offset_segment =
&offset_samples.segments[sample_index];
if (!crofton_segments_equal(full_segment, offset_segment)) {
printf(" %-24s random stream continuation mismatch\\n",
label);
failures++;
break;
}
}
}
}
rt_crofton_result_free(&samples);
if (samples.segments || samples.segment_count)
failures++;
rt_crofton_result_free(&offset_samples);
if (offset_samples.segments || offset_samples.segment_count)
failures++;
}
rt_i_destroy(rtip);
if (elapsed_total_sec)
+138 -318
View File
@@ -19,14 +19,13 @@
#include "bu/malloc.h"
#include "bu/str.h"
#include "bv/vlist.h"
#include "nmg.h"
#include "raytrace.h"
#include "rt/geom.h"
#include "rt/primitives/annot.h"
#include "rt/primitives/datum.h"
#include "wdb.h"
enum { LAYER_TEST_MAX_SEGMENTS = 2 };
static void
check_vector(const char *name, const fastf_t *actual, const fastf_t *expected)
@@ -47,46 +46,79 @@ lookup(struct db_i *dbip, const char *name)
}
static void
write_layer_annotation(struct rt_wdb *wdbp, const char *name,
fastf_t anchor_x, uint32_t space, size_t segment_count,
const unsigned char colors[][4])
{
struct rt_annot_internal annot = {0};
point2d_t vertices[2];
struct line_seg lines[LAYER_TEST_MAX_SEGMENTS] = {{0}};
void *segments[LAYER_TEST_MAX_SEGMENTS] = {NULL, NULL};
int reverse[LAYER_TEST_MAX_SEGMENTS] = {0, 0};
struct rt_annot_seg_style styles[LAYER_TEST_MAX_SEGMENTS] = {{0}};
size_t i;
struct annot_ray_result {
fastf_t in_dist;
fastf_t out_dist;
fastf_t color[3];
int color_valid;
int partitions;
};
if (!wdbp || !name || !colors || segment_count < 1 ||
segment_count > LAYER_TEST_MAX_SEGMENTS)
bu_exit(1, "invalid ordered-layer test annotation\n");
annot.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(annot.V, anchor_x, 0.0, 0.0);
annot.flags = space;
VSET(annot.u_vec, 0.0, 1.0, 0.0);
VSET(annot.v_vec, 0.0, 0.0, 1.0);
annot.vert_count = 2;
annot.verts = vertices;
V2SET(vertices[0], -1.0, 0.0);
V2SET(vertices[1], 1.0, 0.0);
annot.ant.count = segment_count;
annot.ant.reverse = reverse;
annot.ant.segments = segments;
annot.styles = styles;
for (i = 0; i < segment_count; ++i) {
lines[i].magic = CURVE_LSEG_MAGIC;
lines[i].start = 0;
lines[i].end = 1;
segments[i] = &lines[i];
styles[i].flags = RT_ANNOT_STYLE_COLOR;
styles[i].line_pattern = RT_ANNOT_LINE_CONTINUOUS;
memcpy(styles[i].color, colors[i], sizeof(styles[i].color));
static int
annot_ray_hit(struct application *ap, struct partition *part_head,
struct seg *UNUSED(finished_segs))
{
struct annot_ray_result *result = (struct annot_ray_result *)ap->a_uptr;
struct partition *part;
for (part = part_head->pt_forw; part != part_head;
part = part->pt_forw) {
if (!result->partitions) {
result->in_dist = part->pt_inhit->hit_dist;
result->out_dist = part->pt_outhit->hit_dist;
result->color_valid = part->pt_regionp->reg_mater.ma_color_valid;
VMOVE(result->color, part->pt_regionp->reg_mater.ma_color);
}
result->partitions++;
}
if (rt_annot_validate(&annot, NULL) || mk_annot(wdbp, name, &annot) < 0)
bu_exit(1, "unable to write ordered-layer test annotation\n");
return result->partitions > 0;
}
static int
annot_ray_miss(struct application *UNUSED(ap))
{
return 0;
}
static void
check_annot_ray(struct db_i *dbip, const char *object,
const fastf_t *expected_color)
{
struct rt_i *rtip = rt_i_create(dbip);
struct resource resource = RT_RESOURCE_INIT_ZERO;
struct application ap;
struct annot_ray_result result = {0};
const fastf_t expected_thickness = 0.2;
int shot_result;
if (!rtip || rt_gettree(rtip, object))
bu_exit(1, "unable to prepare annotation raytrace object %s\n", object);
rt_prep(rtip);
rt_init_resource(&resource, 0, rtip);
RT_APPLICATION_INIT(&ap);
ap.a_rt_i = rtip;
ap.a_resource = &resource;
ap.a_hit = annot_ray_hit;
ap.a_miss = annot_ray_miss;
ap.a_uptr = &result;
VSET(ap.a_ray.r_pt, 0.0, -2.0, 0.03);
VSET(ap.a_ray.r_dir, 1.0, 0.0, 0.0);
shot_result = rt_shootray(&ap);
if (shot_result != 1 || result.partitions != 1 ||
!result.color_valid ||
!NEAR_EQUAL(result.out_dist - result.in_dist,
expected_thickness, rtip->rti_tol.dist) ||
!VNEAR_EQUAL(result.color, expected_color, VUNITIZE_TOL)) {
bu_log("%s: shot=%d partitions=%d LOS=%g color-valid=%d color=%g/%g/%g\n",
object, shot_result, result.partitions,
result.out_dist - result.in_dist, result.color_valid,
V3ARGS(result.color));
bu_exit(1, "annotation did not produce the expected colored partition\n");
}
rt_i_destroy(rtip);
}
@@ -225,78 +257,48 @@ main(int argc, char **argv)
annot.styles[10].role = RT_ANNOT_ROLE_MASK;
annot.styles[10].flags = RT_ANNOT_STYLE_FILLED;
annot.styles[10].symbol = (char *)"owned fill with hole";
{
const size_t segment_count = annot.ant.count;
annot.ant.count = 1;
annot.styles[0].line_pattern = RT_ANNOT_LINE_CONTINUOUS;
annot.styles[0].line_width = 1.0;
if (rt_annot_validate(&annot, NULL) ||
mk_annot(wdbp, "thin.annot", &annot) < 0)
bu_exit(1, "unable to write default-width annotation\n");
annot.styles[0].line_width = 2.5;
if (rt_annot_validate(&annot, NULL) ||
mk_annot(wdbp, "wide.annot", &annot) < 0)
bu_exit(1, "unable to write wide annotation\n");
annot.ant.count = segment_count;
annot.styles[0].line_pattern = RT_ANNOT_LINE_DASHED;
}
if (rt_annot_validate(&annot, NULL) ||
mk_annot(wdbp, "leader.annot", &annot) < 0)
bu_exit(1, "unable to write enhanced annotation\n");
{
struct wmember members;
mat_t member_transform = MAT_INIT_IDN;
unsigned char inherited_color[3] = {90, 80, 70};
BU_LIST_INIT(&members.l);
MAT_DELTAS(member_transform, 100.0, 0.0, 0.0);
if (!mk_addmember("leader.annot", &members.l, member_transform,
WMOP_UNION) ||
mk_lcomb(wdbp, "annotation-group", &members, 0, NULL, NULL,
inherited_color, 0))
bu_exit(1, "unable to write annotation instance combination\n");
}
{
struct wmember members;
unsigned char inherited_color[3] = {21, 31, 41};
BU_LIST_INIT(&members.l);
if (!mk_addmember("annotation-group", &members.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "inherited-annotation-group", &members, 0,
NULL, NULL, inherited_color, 1))
bu_exit(1, "unable to write inherited annotation combination\n");
}
{
struct wmember members;
mat_t member_transform = MAT_INIT_IDN;
BU_LIST_INIT(&members.l);
MAT_SCALE_ALL(member_transform, 1000.0);
if (!mk_addmember("leader.annot", &members.l, member_transform,
WMOP_UNION) ||
mk_lcomb(wdbp, "scaled-annotation-group", &members, 0, NULL,
NULL, NULL, 0))
bu_exit(1, "unable to write scaled annotation instance combination\n");
}
{
point_t saved_anchor;
VMOVE(saved_anchor, annot.V);
VSETALL(annot.V, 0.0);
annot.flags = RT_ANNOT_SCREEN_SPACE;
if (mk_annot(wdbp, "screen.annot", &annot) < 0)
bu_exit(1, "unable to write screen annotation\n");
VMOVE(annot.V, saved_anchor);
annot.flags = RT_ANNOT_MODEL_SPACE;
}
{
const unsigned char far_color[][4] = {{0, 0, 255, 128}};
const unsigned char near_colors[][4] = {
{255, 0, 0, 128}, {0, 255, 0, 128}
};
const unsigned char screen_color[][4] = {{255, 255, 255, 128}};
write_layer_annotation(wdbp, "alpha-far.annot", 20.0,
RT_ANNOT_MODEL_SPACE, 1, far_color);
write_layer_annotation(wdbp, "alpha-near.annot", 10.0,
RT_ANNOT_MODEL_SPACE, 2, near_colors);
write_layer_annotation(wdbp, "alpha-screen.annot", 0.0,
RT_ANNOT_SCREEN_SPACE, 1, screen_color);
struct rt_annot_internal ray_annot = {0};
point2d_t ray_vertices[2];
struct line_seg ray_line = {0};
void *ray_segments[1];
int ray_reverse[1] = {0};
struct rt_annot_seg_style ray_style[1] = {{0}};
struct wmember member;
unsigned char region_color[3] = {90, 80, 70};
ray_annot.magic = RT_ANNOT_INTERNAL_MAGIC;
VSET(ray_annot.V, 10.0, 0.0, 0.0);
ray_annot.flags = RT_ANNOT_MODEL_SPACE;
VSET(ray_annot.u_vec, 0.0, 1.0, 0.0);
VSET(ray_annot.v_vec, 0.0, 0.0, 1.0);
ray_annot.vert_count = 2;
ray_annot.verts = ray_vertices;
V2SET(ray_vertices[0], -5.0, 0.0);
V2SET(ray_vertices[1], 5.0, 0.0);
ray_line.magic = CURVE_LSEG_MAGIC;
ray_line.start = 0;
ray_line.end = 1;
ray_segments[0] = &ray_line;
ray_annot.ant.count = 1;
ray_annot.ant.reverse = ray_reverse;
ray_annot.ant.segments = ray_segments;
ray_style[0].flags = RT_ANNOT_STYLE_WIDTH;
ray_style[0].line_pattern = RT_ANNOT_LINE_CONTINUOUS;
ray_style[0].line_width = 2.0;
ray_annot.styles = ray_style;
if (rt_annot_validate(&ray_annot, NULL) ||
mk_annot(wdbp, "ray.annot", &ray_annot) < 0)
bu_exit(1, "unable to write raytrace annotation\n");
BU_LIST_INIT(&member.l);
if (!mk_addmember("ray.annot", &member.l, NULL, WMOP_UNION) ||
mk_lcomb(wdbp, "ray.r", &member, 1, NULL, NULL,
region_color, 0))
bu_exit(1, "unable to write raytrace annotation region\n");
}
/* ANP2 is deliberately appended after the main-branch ANT2 body. A
@@ -396,7 +398,12 @@ main(int argc, char **argv)
struct bu_list vhead;
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
if (!(loaded->flags & RT_ANNOT_MODEL_SPACE) || !loaded->styles ||
if (!loaded || !loaded->styles || !loaded->ant.segments ||
loaded->ant.count != 11 || !loaded->ant.segments[10])
bu_exit(1,
"enhanced annotation structure did not round trip\n");
if (!(loaded->flags & RT_ANNOT_MODEL_SPACE) ||
loaded->styles[0].role != RT_ANNOT_ROLE_CENTERMARK ||
loaded->styles[0].line_pattern != RT_ANNOT_LINE_DASHED ||
!loaded->styles[0].font ||
@@ -415,7 +422,6 @@ main(int argc, char **argv)
!NEAR_EQUAL(loaded->styles[1].xy_scale, 0.25, SMALL_FASTF) ||
!NEAR_EQUAL(loaded->styles[1].yx_scale, 0.1, SMALL_FASTF) ||
!NEAR_EQUAL(loaded->styles[1].y_scale, 0.75, SMALL_FASTF) ||
loaded->ant.count != 11 ||
*(uint32_t *)loaded->ant.segments[10] != ANN_FSEG_MAGIC ||
loaded->styles[10].role != RT_ANNOT_ROLE_MASK ||
!loaded->styles[10].symbol ||
@@ -467,222 +473,36 @@ main(int argc, char **argv)
}
check_vector("annotation plot anchor", loaded->V, original_anchor);
BV_FREE_VLIST(&rt_vlfree, &vhead);
{
struct model *model = nmg_mm();
struct nmgregion *region = NULL;
if (rt_obj_tess(&region, model, &intern, &ttol, &tol) || !region)
bu_exit(1, "unable to tessellate model-space annotation\n");
nmg_km(model);
}
}
rt_db_free_internal(&intern);
{
const char *paths[] = {"leader.annot"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
struct xray ray;
struct rt_annot_hit hit = {0};
struct rt_annot_hit all_hit = {0};
point_t render_min, render_max, width_sample;
const fastf_t line_offset = 0.12;
const fastf_t inverse_line_length = 1.0 / sqrt(41.0);
const fastf_t offset_x = 2.0 - 5.0 * line_offset * inverse_line_length;
const fastf_t offset_y = 2.5 + 4.0 * line_offset * inverse_line_length;
VSET(ray.r_pt, 0.0, 24.0, 37.5);
VSET(ray.r_dir, 1.0, 0.0, 0.0);
if (!scene || rt_annot_scene_count(scene) != 1)
bu_exit(1, "annotation render scene did not collect its leaf\n");
if (!rt_annot_scene_bounds(scene, render_min, render_max) ||
!NEAR_EQUAL(render_min[X], 10.0, SMALL_FASTF) ||
!NEAR_EQUAL(render_max[X], 10.0, SMALL_FASTF) ||
render_min[Y] >= render_max[Y] ||
render_min[Z] >= render_max[Z])
bu_exit(1, "annotation render scene bounds are invalid\n");
if (!rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit) || hit.screen_space || !hit.visible ||
!NEAR_EQUAL(hit.distance, 10.0, SMALL_FASTF) ||
hit.color[0] != 12 || hit.color[1] != 34 || hit.color[2] != 56)
bu_exit(1, "model annotation coverage query failed\n");
if (rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0, 5.0, &hit))
bu_exit(1, "occluded annotation was returned as visible\n");
if (!rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
9.99975, &hit))
bu_exit(1, "annotation visibility ignored model tolerance\n");
if (rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
9.999, &hit))
bu_exit(1, "annotation visibility exceeded model tolerance\n");
if (rt_annot_scene_query_model(scene, &ray, 5.0, &all_hit, 1) != 1 ||
all_hit.visible)
bu_exit(1, "annotation diagnostic query lost occluded coverage\n");
VSET(ray.r_pt, 0.0, 12.0, 18.0);
if (!rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit) || hit.segment != 10)
bu_exit(1, "filled annotation coverage was not returned\n");
VSET(ray.r_pt, 0.0, 17.0, 25.5);
if (rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit))
bu_exit(1, "annotation fill hole unexpectedly reported coverage\n");
rt_annot_scene_destroy(scene);
VSET(width_sample, 0.0, 20.0 + 2.0 * offset_x,
30.0 + 3.0 * offset_y);
paths[0] = "wide.annot";
scene = rt_annot_scene_create(dbip, 1, paths, &ttol, &tol);
VMOVE(ray.r_pt, width_sample);
if (!scene || !rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit) || hit.segment != 0)
bu_exit(1, "annotation line width did not expand ray coverage\n");
rt_annot_scene_destroy(scene);
paths[0] = "thin.annot";
scene = rt_annot_scene_create(dbip, 1, paths, &ttol, &tol);
VMOVE(ray.r_pt, width_sample);
if (!scene || rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit))
bu_exit(1, "default line width unexpectedly covered wide sample\n");
rt_annot_scene_destroy(scene);
}
{
const char *paths[] = {"scaled-annotation-group"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
struct xray ray;
struct rt_annot_hit hit = {0};
VSET(ray.r_pt, 0.0, 0.028, 0.045);
VSET(ray.r_dir, 1.0, 0.0, 0.0);
if (!scene || rt_annot_scene_count(scene) != 1 ||
!rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit) ||
!NEAR_EQUAL(hit.distance, 0.01, SMALL_FASTF))
bu_exit(1, "scaled annotation instance coverage failed\n");
rt_annot_scene_destroy(scene);
}
{
const char *paths[] = {"annotation-group"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
struct xray ray;
struct rt_annot_hit hit = {0};
VSET(ray.r_pt, 0.0, 28.0, 45.0);
VSET(ray.r_dir, 1.0, 0.0, 0.0);
if (!scene || rt_annot_scene_count(scene) != 1 ||
!rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit) ||
!NEAR_EQUAL(hit.distance, 110.0, SMALL_FASTF) ||
hit.segment != 10 || hit.color[0] != 90 ||
hit.color[1] != 80 || hit.color[2] != 70)
bu_exit(1, "annotation instance transform was not applied\n");
VSET(ray.r_pt, 0.0, 24.0, 37.5);
if (!rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit) || hit.segment != 0 || hit.color[0] != 12 ||
hit.color[1] != 34 || hit.color[2] != 56)
bu_exit(1, "annotation segment color did not override inherited color\n");
rt_annot_scene_destroy(scene);
}
{
const char *paths[] = {"inherited-annotation-group"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
struct xray ray;
struct rt_annot_hit hit = {0};
VSET(ray.r_pt, 0.0, 28.0, 45.0);
VSET(ray.r_dir, 1.0, 0.0, 0.0);
if (!scene || !rt_annot_scene_query(scene, NULL, &ray, 0.0, 0.0,
INFINITY, &hit) || hit.segment != 10 || hit.color[0] != 21 ||
hit.color[1] != 31 || hit.color[2] != 41)
bu_exit(1, "annotation color did not follow tree inheritance\n");
rt_annot_scene_destroy(scene);
}
{
const char *paths[] = {"screen.annot"};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 1, paths,
&ttol, &tol);
struct rt_annot_view view = {MAT_INIT_IDN, 100, 100, 0.0};
struct xray ray;
struct rt_annot_hit hit = {0};
VSET(ray.r_pt, 0.0, 0.0, 10.0);
VSET(ray.r_dir, 0.0, 0.0, -1.0);
if (!scene || !rt_annot_scene_query(scene, &view, &ray, 50.0, 50.0,
0.0, &hit) || !hit.screen_space || !hit.visible)
bu_exit(1, "screen annotation coverage query failed\n");
rt_annot_scene_destroy(scene);
}
{
const char *paths[] = {
"alpha-far.annot", "alpha-near.annot", "alpha-screen.annot"
const fastf_t default_color[3] = {1.0, 1.0, 1.0};
const fastf_t annotation_color[3] = {
12.0 / 255.0, 34.0 / 255.0, 56.0 / 255.0
};
struct bg_tess_tol ttol = BG_TESS_TOL_INIT_TOL;
struct bn_tol tol = BN_TOL_INIT_TOL;
struct rt_annot_scene *scene = rt_annot_scene_create(dbip, 3, paths,
&ttol, &tol);
struct rt_annot_view view = {MAT_INIT_IDN, 100, 100, 0.0};
struct rt_annot_hit layers[4] = {{0}};
struct rt_annot_hit diagnostic_hits[2] = {{0}};
struct rt_annot_hit top = {0};
struct xray ray;
size_t layer_count;
fastf_t color[3] = {0.0, 0.0, 0.0};
const fastf_t alpha = 128.0 / 255.0;
const fastf_t remainder = 1.0 - alpha;
const fastf_t expected_color[3] = {
alpha + remainder * remainder * alpha,
alpha + remainder * alpha,
alpha + remainder * remainder * remainder * alpha
const fastf_t region_color[3] = {
90.0 / 255.0, 80.0 / 255.0, 70.0 / 255.0
};
VSET(ray.r_pt, 0.0, 0.0, 0.0);
VSET(ray.r_dir, 1.0, 0.0, 0.0);
layer_count = rt_annot_scene_query_layers(scene, &view, &ray,
50.0, 50.0, INFINITY, layers, 2);
if (!scene || layer_count != 4 ||
bu_strcmp(layers[0].path, "/alpha-far.annot") ||
bu_strcmp(layers[1].path, "/alpha-near.annot") ||
layers[1].segment != 0)
bu_exit(1, "annotation layer count or capacity handling failed: "
"count=%zu first=%s second=%s/%zu\n", layer_count,
layers[0].path ? layers[0].path : "(null)",
layers[1].path ? layers[1].path : "(null)", layers[1].segment);
if (rt_annot_scene_query_layers(scene, &view, &ray, 50.0, 50.0,
INFINITY, layers, 4) != 4 ||
!NEAR_EQUAL(layers[0].distance, 20.0, SMALL_FASTF) ||
!NEAR_EQUAL(layers[1].distance, 10.0, SMALL_FASTF) ||
layers[1].segment != 0 ||
layers[2].segment != 1 || !layers[3].screen_space)
bu_exit(1, "annotation layers are not back-to-front\n");
if (!rt_annot_scene_query(scene, &view, &ray, 50.0, 50.0,
INFINITY, &top) || !top.screen_space ||
bu_strcmp(top.path, "/alpha-screen.annot"))
bu_exit(1, "annotation top-layer compatibility query changed\n");
if (rt_annot_scene_query_layers(scene, &view, &ray, 50.0, 50.0,
15.0, NULL, 0) != 3)
bu_exit(1, "annotation layer visibility filtering failed\n");
if (rt_annot_scene_query_model(scene, &ray, INFINITY,
diagnostic_hits, 2) != 2 ||
bu_strcmp(diagnostic_hits[0].path, "/alpha-near.annot") ||
diagnostic_hits[0].segment != 1 ||
bu_strcmp(diagnostic_hits[1].path, "/alpha-far.annot"))
bu_exit(1, "annotation diagnostic hits changed layer semantics\n");
if (!rt_annot_scene_composite(scene, &view, &ray, 50.0, 50.0,
INFINITY, color, &top) ||
!VNEAR_EQUAL(color, expected_color, SMALL_FASTF) ||
!top.screen_space)
bu_exit(1, "annotation alpha compositing order failed\n");
MAT_DELTAS(view.model2view, 0.0, 0.0, -2.0);
view.width = 160;
view.height = 90;
view.perspective = 60.0;
if (rt_annot_scene_query_layers(scene, &view, &ray, 80.0, 45.0,
INFINITY, NULL, 0) != 4)
bu_exit(1, "perspective screen annotation coverage failed\n");
rt_annot_scene_destroy(scene);
/* m35.g has this broad table entry, which includes the synthetic ID 0
* assigned to bare solids. It must not replace annotation colors. */
db_mater_add(dbip, 0, 15000, 210, 146, 1, MATER_NO_ADDR);
check_annot_ray(dbip, "ray.annot", default_color);
if (db5_update_attribute("ray.annot", "rgb", "12/34/56", dbip))
bu_exit(1, "unable to set annotation raytrace color\n");
check_annot_ray(dbip, "ray.annot", annotation_color);
check_annot_ray(dbip, "ray.r", region_color);
}
dp = lookup(dbip, "leader.annot");
MAT_DELTAS(transform, -5.0, 6.0, 7.0);
RT_DB_INTERNAL_INIT(&intern);
if (rt_db_get_internal(&intern, dp, dbip, transform) != ID_ANNOT)
+21 -36
View File
@@ -414,20 +414,13 @@ flat_dsp_analytic(uint32_t xcnt, uint32_t ycnt, unsigned short h,
/* Build an in-memory DB, prep, and run comparison */
/* ------------------------------------------------------------------ */
enum dsp_ray_check_mode {
DSP_RAY_CHECK_NONE,
DSP_RAY_CHECK_FLAT,
DSP_RAY_CHECK_SMOOTH,
DSP_RAY_CHECK_BC_CUT
};
struct dsp_case {
const char *label;
uint32_t xcnt, ycnt;
const unsigned short *buf;
int cuttype;
int smooth;
enum dsp_ray_check_mode ray_checks;
int ray_checks;
/* stom diagonal (identity: dx=dy=dz=1): */
double dx, dy, dz;
/* analytic SA/vol (-1 if not available): */
@@ -557,7 +550,7 @@ check_smooth_normal(const struct dsp_case *tc, struct soltab *stp)
static int
run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
{
if (tc->ray_checks == DSP_RAY_CHECK_NONE)
if (!tc->ray_checks)
return 0;
int failures = 0;
@@ -569,14 +562,16 @@ run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
double x_extent = (tc->xcnt - 1) * tc->dx;
double y_extent = (tc->ycnt - 1) * tc->dy;
/* ray_checks == 1 is reserved for flat DSP cases, so buf[0] is the
* expected terrain height for the deterministic vertical/side rays.
*/
double top_z = (double)tc->buf[0] * tc->dz;
double expected_vertical_in = 10.0;
double expected_vertical_out = expected_vertical_in + top_z;
point_t o;
vect_t d;
if (tc->ray_checks == DSP_RAY_CHECK_FLAT) {
/* Flat cases use buf[0] as the terrain height for direct rays. */
double top_z = (double)tc->buf[0] * tc->dz;
double expected_vertical_in = 10.0;
double expected_vertical_out = expected_vertical_in + top_z;
if (tc->ray_checks == 1) {
VSET(o, 0.5 * x_extent, 0.5 * y_extent, top_z + 10.0);
VSET(d, 0.0, 0.0, -1.0);
failures += check_direct_ray("vertical down flat DSP", stp, rtip,
@@ -603,22 +598,12 @@ run_prepped_ray_checks(const struct dsp_case *tc, struct rt_i *rtip)
failures += check_direct_ray("vertical up flat DSP", stp, rtip,
o, d, 1, expected_vertical_in,
expected_vertical_out, 0);
} else if (tc->ray_checks == DSP_RAY_CHECK_SMOOTH) {
} else if (tc->ray_checks == 2) {
VSET(o, 0.5 * x_extent, 0.5 * y_extent, 10000.0);
VSET(d, 0.0, 0.0, -1.0);
failures += check_direct_ray("smooth-mode direct vertical shot", stp, rtip,
o, d, 1, -1.0, -1.0, 0);
failures += check_smooth_normal(tc, stp);
} else if (tc->ray_checks == DSP_RAY_CHECK_BC_CUT) {
/* In cell (3,3), the B-C triangle rises from 315 at y=3.25 by
* 68 per x. A horizontal ray at z=330 enters at x=3+15/68
* and exits through the outer wall at x=4. This also checks
* that the explicit B-C cut gives the top an outward normal.
*/
VSET(o, 2.5, 3.25, 330.0);
VSET(d, 1.0, 0.0, 0.0);
failures += check_direct_ray("explicit B-C top-to-side shot", stp, rtip,
o, d, 1, 0.5 + 15.0 / 68.0, 1.5, 1);
}
return failures;
@@ -797,7 +782,7 @@ test_synthetic(void)
struct dsp_case tc = {
"flat 5x5 h=100 (4x4 cells, identity stom)",
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_FLAT, 1.0, 1.0, 1.0, asa, avol
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 1, 1.0, 1.0, 1.0, asa, avol
};
failures += run_inmem_case(&tc);
}
@@ -813,7 +798,7 @@ test_synthetic(void)
struct dsp_case tc = {
"flat 10x10 h=200 (9x9 cells)",
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, asa, avol
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, asa, avol
};
failures += run_inmem_case(&tc);
}
@@ -829,7 +814,7 @@ test_synthetic(void)
struct dsp_case tc = {
"flat 10x10 h=50 (stom dx=5 dy=5 dz=2)",
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_FLAT, 5.0, 5.0, 2.0, asa, avol
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 1, 5.0, 5.0, 2.0, asa, avol
};
failures += run_inmem_case(&tc);
}
@@ -844,7 +829,7 @@ test_synthetic(void)
struct dsp_case tc = {
"ramp 9x9 (h=100+20x+10y)",
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
}
@@ -859,7 +844,7 @@ test_synthetic(void)
struct dsp_case tc = {
"asymmetric 5x5 explicit ULlr cut",
GW, GH, buf, DSP_CUT_DIR_ULlr, 0, DSP_RAY_CHECK_BC_CUT, 1.0, 1.0, 1.0, -1.0, -1.0
GW, GH, buf, DSP_CUT_DIR_ULlr, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
}
@@ -874,7 +859,7 @@ test_synthetic(void)
struct dsp_case tc = {
"asymmetric 5x5 adaptive cut",
GW, GH, buf, DSP_CUT_DIR_ADAPT, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
GW, GH, buf, DSP_CUT_DIR_ADAPT, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
}
@@ -889,7 +874,7 @@ test_synthetic(void)
struct dsp_case tc = {
"smooth=1 ramp normal check",
GW, GH, buf, DSP_CUT_DIR_llUR, 1, DSP_RAY_CHECK_SMOOTH, 1.0, 1.0, 1.0, -1.0, -1.0
GW, GH, buf, DSP_CUT_DIR_llUR, 1, 2, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
}
@@ -904,7 +889,7 @@ test_synthetic(void)
struct dsp_case tc = {
"smooth=2 ramp normal check",
GW, GH, buf, DSP_CUT_DIR_llUR, 2, DSP_RAY_CHECK_SMOOTH, 1.0, 1.0, 1.0, -1.0, -1.0
GW, GH, buf, DSP_CUT_DIR_llUR, 2, 2, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
}
@@ -923,7 +908,7 @@ test_synthetic(void)
struct dsp_case tc = {
"sinusoidal 16x16",
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
}
@@ -943,7 +928,7 @@ test_synthetic(void)
struct dsp_case tc = {
"complex 33x33 wave",
GW, GH, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
GW, GH, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
bu_free(buf, "33x33 buf");
@@ -986,7 +971,7 @@ test_synthetic(void)
struct dsp_case tc = {
"larger 129x129 mixed ramp/wave terrain",
grid_width, grid_height, buf, DSP_CUT_DIR_llUR, 0, DSP_RAY_CHECK_NONE, 1.0, 1.0, 1.0, -1.0, -1.0
grid_width, grid_height, buf, DSP_CUT_DIR_llUR, 0, 0, 1.0, 1.0, 1.0, -1.0, -1.0
};
failures += run_inmem_case(&tc);
bu_free(sin_x, "129x129 sin x");
+1
View File
@@ -889,3 +889,4 @@ done:
// c-file-style: "stroustrup"
// End:
// ex: shiftwidth=4 tabstop=8
-1
View File
@@ -56,7 +56,6 @@ add_target_deps(rtscale dm_plugins)
brlcad_addexec(rt "${LIBRTUIF_SOURCES};view.c" "${OPENCL_LIBS};${RT_STD_LIBS}" FOLDER Rt)
target_include_directories(rt BEFORE PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
set_property(TARGET rt APPEND PROPERTY COMPILE_DEFINITIONS "RT_ANNOT_OVERLAY")
add_target_deps(rt dm_plugins dm-txt)
brlcad_addexec(rtarea "${LIBRTUIF_SOURCES};viewarea.c" "${RT_STD_LIBS}" FOLDER Rt)
+10 -211
View File
@@ -80,15 +80,6 @@ extern void worker(int cpu, void *arg);
extern struct icv_image *bif;
unsigned char *pixmap = NULL; /**< Pixel Map for rerendering of black pixels */
#ifdef RT_ANNOT_OVERLAY
static struct rt_annot_scene *annotation_scene = NULL;
static struct rt_annot_view annotation_view = {MAT_INIT_IDN, 0, 0, 0.0};
static struct db_i *annotation_dbip = DBI_NULL;
static struct bu_ptbl annotation_paths = BU_PTBL_INIT_ZERO;
static struct bg_tess_tol annotation_ttol = BG_TESS_TOL_INIT_ZERO;
static struct bn_tol annotation_tol = BN_TOL_INIT_ZERO;
static int annotation_cache_valid = 0;
#endif
enum view_command_flag {
VIEW_COMMAND_EYE_SET = 1u << 0,
@@ -329,9 +320,6 @@ int cm_end(const int UNUSED(argc), const char **UNUSED(argv))
return -1;
}
#ifdef RT_ANNOT_OVERLAY
rtuif_prepare_annotations(rtip);
#endif
do_view_finalize(azimuth, elevation);
if (do_frame(curframe) < 0)
@@ -484,9 +472,6 @@ int cm_multiview(const int UNUSED(argc), const char **UNUSED(argv))
if (rtip && BU_LIST_IS_EMPTY(&rtip->HeadRegion) && !def_tree(rtip, NULL)) {
return -1;
}
#ifdef RT_ANNOT_OVERLAY
rtuif_prepare_annotations(rtip);
#endif
for (i = 0; i < (sizeof(a)/sizeof(a[0])); i++) {
do_ae((double)a[i], (double)e[i]);
(void)do_frame(curframe++);
@@ -507,9 +492,6 @@ int cm_anim(const int argc, const char **argv)
bu_log("cm_anim: %s %s failed\n", argv[1], argv[2]);
return -1; /* BAD */
}
#ifdef RT_ANNOT_OVERLAY
rtuif_clear_annotations();
#endif
return 0;
}
@@ -905,19 +887,11 @@ validate_raytrace(struct rt_i *rtip)
bu_log("ERROR: No raytracing instance.\n");
return 1;
}
if (rtip->stats.nsolids <= 0
#ifdef RT_ANNOT_OVERLAY
&& (!annotation_scene || !rt_annot_scene_count(annotation_scene))
#endif
) {
if (rtip->stats.nsolids <= 0) {
bu_log("ERROR: No primitives remaining.\n");
return 2;
}
if (rtip->stats.nregions <= 0
#ifdef RT_ANNOT_OVERLAY
&& (!annotation_scene || !rt_annot_scene_count(annotation_scene))
#endif
) {
if (rtip->stats.nregions <= 0) {
bu_log("ERROR: No regions remaining.\n");
return 3;
}
@@ -926,152 +900,6 @@ validate_raytrace(struct rt_i *rtip)
}
#ifdef RT_ANNOT_OVERLAY
static void
annotation_cache_clear(void)
{
size_t i;
rt_annot_scene_destroy(annotation_scene);
annotation_scene = NULL;
if (BU_PTBL_TEST(&annotation_paths)) {
for (i = 0; i < BU_PTBL_LEN(&annotation_paths); ++i)
bu_free((char *)BU_PTBL_GET(&annotation_paths, i),
"cached annotation path");
bu_ptbl_free(&annotation_paths);
}
annotation_dbip = DBI_NULL;
annotation_cache_valid = 0;
}
static int
annotation_scalar_equal(double a, double b)
{
return a <= b && a >= b;
}
static int
annotation_tolerances_match(const struct rt_i *rtip)
{
const struct bg_tess_tol *ttol = &rtip->rti_ttol;
const struct bn_tol *tol = &rtip->rti_tol;
return annotation_ttol.magic == ttol->magic &&
annotation_scalar_equal(annotation_ttol.abs, ttol->abs) &&
annotation_scalar_equal(annotation_ttol.rel, ttol->rel) &&
annotation_scalar_equal(annotation_ttol.norm, ttol->norm) &&
annotation_scalar_equal(annotation_ttol.absmax, ttol->absmax) &&
annotation_scalar_equal(annotation_ttol.absmin, ttol->absmin) &&
annotation_scalar_equal(annotation_ttol.relmax, ttol->relmax) &&
annotation_scalar_equal(annotation_ttol.relmin, ttol->relmin) &&
annotation_scalar_equal(annotation_ttol.rel_lmax, ttol->rel_lmax) &&
annotation_scalar_equal(annotation_ttol.rel_lmin, ttol->rel_lmin) &&
annotation_tol.magic == tol->magic &&
annotation_scalar_equal(annotation_tol.dist, tol->dist) &&
annotation_scalar_equal(annotation_tol.dist_sq, tol->dist_sq) &&
annotation_scalar_equal(annotation_tol.perp, tol->perp) &&
annotation_scalar_equal(annotation_tol.para, tol->para);
}
static int
annotation_cache_matches(const struct rt_i *rtip, int path_count,
const char * const *paths)
{
int i;
if (!annotation_cache_valid || annotation_dbip != rtip->rti_dbip ||
path_count < 0 ||
(size_t)path_count != BU_PTBL_LEN(&annotation_paths) ||
!annotation_tolerances_match(rtip))
return 0;
for (i = 0; i < path_count; ++i)
if (!BU_STR_EQUAL((const char *)BU_PTBL_GET(&annotation_paths, i),
paths[i]))
return 0;
return 1;
}
static void
annotation_cache_store(const struct rt_i *rtip, int path_count,
const char * const *paths)
{
int i;
bu_ptbl_init(&annotation_paths, (size_t)path_count,
"cached annotation paths");
for (i = 0; i < path_count; ++i)
bu_ptbl_ins(&annotation_paths, (long *)bu_strdup(paths[i]));
annotation_dbip = rtip->rti_dbip;
annotation_ttol = rtip->rti_ttol;
annotation_tol = rtip->rti_tol;
annotation_cache_valid = 1;
}
const struct rt_annot_scene *
rtuif_annotations(void)
{
return annotation_scene;
}
const struct rt_annot_view *
rtuif_annotation_view(void)
{
return &annotation_view;
}
void
rtuif_clear_annotations(void)
{
annotation_cache_clear();
MAT_IDN(annotation_view.model2view);
annotation_view.width = 0;
annotation_view.height = 0;
annotation_view.perspective = 0.0;
}
size_t
rtuif_prepare_annotations(struct rt_i *rtip)
{
const char **treev = NULL;
int treec = 0;
size_t count;
if (!rtip) {
rtuif_clear_annotations();
return 0;
}
if (cmd_objs && BU_PTBL_LEN(cmd_objs)) {
treec = (int)BU_PTBL_LEN(cmd_objs);
treev = (const char **)cmd_objs->buffer;
} else if (!rtuif_tree_list(rtip, &treec, &treev, NULL)) {
rtuif_clear_annotations();
return 0;
}
if (!annotation_cache_matches(rtip, treec, treev)) {
annotation_cache_clear();
annotation_scene = rt_annot_scene_create(rtip->rti_dbip, treec, treev,
&rtip->rti_ttol, &rtip->rti_tol);
annotation_cache_store(rtip, treec, treev);
}
count = rt_annot_scene_count(annotation_scene);
if (opencl_mode && count) {
bu_log("rt: annotations require primary-sample compositing; using the CPU renderer\n");
opencl_mode = 0;
}
return count;
}
#endif
/**
* Do all the actual work to run a frame.
*
@@ -1096,10 +924,6 @@ do_frame(int framenumber)
/* Compute model RPP, etc. */
do_prep(rtip);
#ifdef RT_ANNOT_OVERLAY
rtuif_prepare_annotations(rtip);
#endif
if (rt_verbosity & VERBOSE_VIEWDETAIL)
bu_log("Tree: %zu solids in %zu regions\n", rtip->stats.nsolids, rtip->stats.nregions);
@@ -1134,13 +958,6 @@ do_frame(int framenumber)
bu_vls_free(&msg);
}
#ifdef RT_ANNOT_OVERLAY
MAT_COPY(annotation_view.model2view, model2view);
annotation_view.width = width;
annotation_view.height = height;
annotation_view.perspective = rt_perspective;
#endif
/* az/el 0, 0 is when screen +Z is model +X */
VSET(work, 0, 0, 1);
MAT3X3VEC(temp, view2model, work);
@@ -1589,32 +1406,22 @@ do_ae_internal(double azim, double elev, int center_eye)
{
struct rt_i *rtip = APP.a_rt_i;
point_t view_min, view_max;
#ifdef RT_ANNOT_OVERLAY
point_t annotation_min, annotation_max;
int have_annotation_bounds;
#endif
if (rtip == NULL)
return;
if (rtip->mdl_min[X] >= INFINITY || rtip->mdl_max[X] <= -INFINITY) {
if (rtip->mdl_max[X] >= INFINITY) {
bu_log("do_ae: infinite model bounds? setting a unit minimum\n");
VSETALL(rtip->mdl_min, -1);
}
if (rtip->mdl_max[X] <= -INFINITY) {
/* Model not yet loaded (librt initial state: mdl_min=+INF, mdl_max=-INF).
* Reset both bounds to a unit box so that autoviewsize() does not
* propagate +INFINITY through mdl_min into cell_width (which causes a
* bu_bomb("bad cell size") crash when work is dispatched). */
#ifdef RT_ANNOT_OVERLAY
have_annotation_bounds = rt_annot_scene_bounds(annotation_scene,
annotation_min, annotation_max);
if (have_annotation_bounds) {
VMOVE(rtip->mdl_min, annotation_min);
VMOVE(rtip->mdl_max, annotation_max);
} else
#endif
{
bu_log("do_ae: infinite model bounds? setting unit bounds\n");
VSETALL(rtip->mdl_min, -1);
VSETALL(rtip->mdl_max, 1);
}
bu_log("do_ae: infinite model bounds? setting unit bounds\n");
VSETALL(rtip->mdl_min, -1);
VSETALL(rtip->mdl_max, 1);
}
/*
@@ -1636,14 +1443,6 @@ do_ae_internal(double azim, double elev, int center_eye)
*/
VMOVE(view_min, rtip->mdl_min);
VMOVE(view_max, rtip->mdl_max);
#ifdef RT_ANNOT_OVERLAY
have_annotation_bounds = rt_annot_scene_bounds(annotation_scene,
annotation_min, annotation_max);
if (have_annotation_bounds) {
VMINMAX(view_min, view_max, annotation_min);
VMINMAX(view_min, view_max, annotation_max);
}
#endif
if (autoview_argc > 0) {
point_t sub_min, sub_max;
if (rt_obj_bounds(NULL, rtip->rti_dbip, autoview_argc,
-9
View File
@@ -151,9 +151,6 @@ extern vect_t dx_model; /* view delta-X as model-space vect (width of pixel as
extern vect_t dx_unit; /* unit-len dir vector of pixel side-to-side */
extern vect_t dy_model; /* view delta-Y as model-space vect (height of pixel as vector) */
extern vect_t dy_unit; /* unit-len dir vector of pixel top-to-bottom */
#ifdef RT_ANNOT_OVERLAY
# define RTUIF_ANNOTATION_ONLY 2
#endif
/** 'jitter' variable values **/
#define JITTER_CELL 0x1 /* jitter position of ray in each cell */
#define JITTER_FRAME 0x2 /* jitter position of entire frame */
@@ -200,12 +197,6 @@ extern void do_ae(double azim, double elev);
extern void do_view_finalize(double azim, double elev);
extern int old_way(FILE *fp);
extern int do_frame(int framenumber);
#ifdef RT_ANNOT_OVERLAY
extern size_t rtuif_prepare_annotations(struct rt_i *rtip);
extern const struct rt_annot_scene *rtuif_annotations(void);
extern const struct rt_annot_view *rtuif_annotation_view(void);
extern void rtuif_clear_annotations(void);
#endif
#ifdef USE_OPENCL
enum {
-6
View File
@@ -620,9 +620,6 @@ int main(int argc, char *argv[])
}
}
#ifdef RT_ANNOT_OVERLAY
rtuif_prepare_annotations(APP.a_rt_i);
#endif
do_view_finalize(azimuth, elevation);
if (need_fb != 0 && !fbp) {
@@ -742,9 +739,6 @@ int main(int argc, char *argv[])
}
rt_cleanup:
#ifdef RT_ANNOT_OVERLAY
rtuif_clear_annotations();
#endif
/* Clean up objv memory, if necessary */
if (objs_free_argv) {
for (i = 0; i < objc; i++) {
-9
View File
@@ -267,14 +267,6 @@ view_pixel(struct application *ap)
fp->ff_frame = -1; /* Don't cache misses */
return;
}
#ifdef RT_ANNOT_OVERLAY
if (ap->a_flag == RTUIF_ANNOTATION_ONLY) {
/* Annotation coverage has no region or reusable model hit. */
fp->ff_dist = -INFINITY;
fp->ff_regp = NULL;
return;
}
#endif
/* XXX a_dist is negative and misleading when eye is in air */
fp->ff_dist = (float)ap->a_dist;
VJOIN1(fp->ff_hitpt, ap->a_ray.r_pt,
@@ -1238,7 +1230,6 @@ viewit(struct application *ap, struct partition *PartHeadp, struct seg *UNUSED(s
}
hitp = pp->pt_inhit;
ap->a_dist = hitp->hit_dist;
RT_HIT_NORMAL(normal, hitp, pp->pt_inseg->seg_stp, &(ap->a_ray), pp->pt_inflip);
/*
+2 -70
View File
@@ -151,46 +151,6 @@ jitter_start_pnt(vect_t point, struct application *a, int samplenum, int pat_num
}
#ifdef RT_ANNOT_OVERLAY
static int
annotation_overlay_active(void)
{
const struct rt_annot_scene *scene = rtuif_annotations();
return scene && rt_annot_scene_count(scene);
}
static void
annotate_primary_sample(struct application *ap, const point_t viewplane_point)
{
point_t view_point;
struct rt_annot_hit hit = {0};
fastf_t sample_x;
fastf_t sample_y;
fastf_t scene_distance;
const struct rt_annot_scene *scene = rtuif_annotations();
const struct rt_annot_view *view = rtuif_annotation_view();
if (!scene || !rt_annot_scene_count(scene))
return;
MAT4X3PNT(view_point, model2view, viewplane_point);
sample_x = (view_point[X] + 1.0) * (fastf_t)width * 0.5;
sample_y = view_point[Y] * (fastf_t)width * 0.5 +
(fastf_t)height * 0.5;
scene_distance = ap->a_user ? ap->a_dist : INFINITY;
if (rt_annot_scene_composite(scene, view, &ap->a_ray,
sample_x, sample_y, scene_distance, ap->a_color, &hit)) {
if (!ap->a_user) {
ap->a_user = 1;
ap->a_flag = RTUIF_ANNOTATION_ONLY;
ap->a_dist = hit.screen_space ? INFINITY : hit.distance;
}
}
}
#endif
void
do_pixel(int cpu, int pat_num, int pixelnum)
{
@@ -279,11 +239,7 @@ do_pixel(int cpu, int pat_num, int pixelnum)
if (fullfloat_mode) {
register struct floatpixel *fp;
fp = &curr_float_frame[a.a_y*width + a.a_x];
if (fp->ff_frame >= 0
#ifdef RT_ANNOT_OVERLAY
&& !annotation_overlay_active()
#endif
) {
if (fp->ff_frame >= 0) {
return; /* pixel was reprojected */
}
}
@@ -291,11 +247,7 @@ do_pixel(int cpu, int pat_num, int pixelnum)
/* Check the pixel map to determine if this image should be
* rendered or not.
*/
if (pixmap
#ifdef RT_ANNOT_OVERLAY
&& !annotation_overlay_active()
#endif
) {
if (pixmap) {
a.a_user= 1; /* Force Shot Hit */
if (pixmap[pindex + RED] + pixmap[pindex + GRN] + pixmap[pindex + BLU]) {
@@ -376,12 +328,7 @@ do_pixel(int cpu, int pat_num, int pixelnum)
a.a_level = 0; /* recursion level */
a.a_purpose = "main ray";
a.a_flag = 0;
a.a_dist = INFINITY;
(void)rt_shootray(&a);
#ifdef RT_ANNOT_OVERLAY
annotate_primary_sample(&a, point);
#endif
if (stereo) {
fastf_t right, left;
@@ -403,12 +350,7 @@ do_pixel(int cpu, int pat_num, int pixelnum)
}
a.a_level = 0; /* recursion level */
a.a_purpose = "left eye ray";
a.a_flag = 0;
a.a_dist = INFINITY;
(void)rt_shootray(&a);
#ifdef RT_ANNOT_OVERLAY
annotate_primary_sample(&a, point);
#endif
left = CRT_BLEND(a.a_color);
VSET(a.a_color, left, 0, right);
@@ -471,12 +413,7 @@ do_pixel(int cpu, int pat_num, int pixelnum)
a.a_level = 0; /* recursion level */
a.a_purpose = "main ray";
a.a_flag = 0;
a.a_dist = INFINITY;
(void)rt_shootray(&a);
#ifdef RT_ANNOT_OVERLAY
annotate_primary_sample(&a, point);
#endif
if (stereo) {
fastf_t right, left;
@@ -498,12 +435,7 @@ do_pixel(int cpu, int pat_num, int pixelnum)
}
a.a_level = 0; /* recursion level */
a.a_purpose = "left eye ray";
a.a_flag = 0;
a.a_dist = INFINITY;
(void)rt_shootray(&a);
#ifdef RT_ANNOT_OVERLAY
annotate_primary_sample(&a, point);
#endif
left = CRT_BLEND(a.a_color);
VSET(a.a_color, left, 0, right);