Adding a New Primitive to BRL-CAD: Superellipsoid Walkthrough
On this page
This tutorial explains how a BRL-CAD primitive becomes a real database entity, using the superellipsoid as the concrete example throughout. The goal is not to copy the superellipsoid blindly, but to use its actual source paths, data layout, librt hooks, MGED hooks, libged helpers, OpenCL support, and tests as a map for adding another primitive.
The superellipsoid is a good example because it touches most primitive
extension points without being artificially ideal. It has CPU ray tracing,
wireframe drawing, import/export, BREP conversion, bounds, labels, keypoints,
measures, matrix transforms, perturbation, mirroring, OpenCL support, type-in
creation, make defaults, MGED and Archer editing, and regression tests. It
also has explicit placeholders for unfinished hooks such as rt_superell_tess,
rt_superell_curve, and rt_superell_uv, which is useful when deciding whether
a new primitive should register a hook, leave a hook NULL, or document a stub.
The example primitive
A superellipsoid is stored in BRL-CAD as struct rt_superell_internal in
include/rt/geom.h. Its fields are:
-
v: center point, labeledVin editors. -
a,b,c: semi-axis vectors, labeled by their endpoint keypointsA,B, andC. -
n: north-south curvature exponent. -
e: east-west curvature exponent.
The exact internal structure is:
struct rt_superell_internal {
uint32_t magic;
point_t v;
vect_t a;
vect_t b;
vect_t c;
double n;
double e;
};
When creating another primitive, start by writing the same kind of short
inventory before coding. Decide what data is intrinsic, what is derived, what
must survive database import/export, what should scale with model units, and
what should remain dimensionless. In the superellipsoid, v, a, b, and
c are length data and are unit-converted during I/O and editing; n and e
are dimensionless and are stored without unit conversion.
Implementation plan
Adding a primitive is a sequence of registrations. The superellipsoid wiring falls into these groups:
Area |
Superellipsoid example |
Identity and public data |
|
Core librt behavior |
|
Build and data installation |
|
Command creation and editing |
|
Analysis, utility, and testing |
|
Add the entity to headers
Header registration makes the primitive recognizable to database I/O, public
APIs, runtime type checks, and tools that switch on ID_*.
Reserve the type ID
The superellipsoid type ID is in include/rt/defines.h:
/* superellipsoid should be 31, but is not v5 compatible */ #define ID_SUPERELL 35 /**< @brief Superquadratic ellipsoid */
The same numeric value is the v5 BRL-CAD minor type in include/rt/db5.h:
#define DB5_MINORTYPE_BRLCAD_SUPERELL 35
For a new primitive, add a new ID_* value only after checking the existing
range and comments in defines.h. Do not renumber existing IDs. If the new
value extends the range, update ID_MAX_SOLID and ID_MAXIMUM consistently.
Database minor type compatibility depends on these values remaining stable.
Add the magic number and internal struct
Every internal primitive structure starts with a magic value. The
superellipsoid magic is in include/bu/magic.h:
#define RT_SUPERELL_INTERNAL_MAGIC 0xff93bb23
The structure and checker macro live in include/rt/geom.h:
struct rt_superell_internal {
uint32_t magic;
point_t v;
vect_t a;
vect_t b;
vect_t c;
double n;
double e;
};
#define RT_SUPERELL_CK_MAGIC(_p) \
BU_CKMAG(_p, RT_SUPERELL_INTERNAL_MAGIC, "rt_superell_internal")
Use the same pattern for a new primitive. Put persistent fields in the
internal struct and derived acceleration data somewhere else. The
superellipsoid keeps precomputed ray-tracing values in a private
struct superell_specific inside superell.c, not in
rt_superell_internal, because the database only needs v, a, b, c,
n, and e.
Decide whether v4 compatibility applies
The superellipsoid has old database support in include/rt/db4.h:
#define SUPERELL 32 #define s_superell_V s_values[0] #define s_superell_A s_values[3] #define s_superell_B s_values[6] #define s_superell_C s_values[9] #define s_superell_n s_values[12] #define s_superell_e s_values[13]
That mapping is why rt_superell_import4 and rt_superell_export4 exist. A
new primitive that never existed in v4 can normally leave ft_import4 and
ft_export4 NULL in OBJ[]. If it must read legacy data, add explicit v4
record constants and field aliases and test old-file round trips.
Add the primitive to librt
The core implementation lives under src/librt/primitives/superell/.
For another primitive, create a sibling directory named for the short primitive
label and keep the same file split when the features apply:
File |
Superellipsoid role |
|
Main internal parser, database I/O, ray prep, ray shot, normals, plotting, bounds, description, transform, labels, keypoint, volume, surface area, and perturbation. |
|
Primitive-specific edit modes and structured edit metadata. |
|
OpenNURBS BREP conversion used by BREP-aware tools. |
|
Legacy mirror support called from |
|
OpenCL version of the ray shot and normal code. |
Define the struct parser
rt_superell_parse in superell.c maps user-facing attribute names to fields:
{ "%f", 3, "V", bu_offsetofarray(struct rt_superell_internal, v, fastf_t, X), ... },
{ "%f", 3, "A", bu_offsetofarray(struct rt_superell_internal, a, fastf_t, X), ... },
{ "%f", 3, "B", bu_offsetofarray(struct rt_superell_internal, b, fastf_t, X), ... },
{ "%f", 3, "C", bu_offsetofarray(struct rt_superell_internal, c, fastf_t, X), ... },
{ "%g", 1, "n", bu_offsetof(struct rt_superell_internal, n), ... },
{ "%g", 1, "e", bu_offsetof(struct rt_superell_internal, e), ... },
This enables generic get, adjust, and form handling through the
rt_generic_get, rt_generic_adjust, and rt_generic_form entries in
OBJ[ID_SUPERELL]. For a new primitive, use a bu_structparse table when
simple named fields are enough. Use a custom ft_get or ft_adjust only when
generic struct parsing cannot express the data model.
Implement ray-tracing hooks
Superellipsoid CPU ray tracing uses three core hooks:
-
rt_superell_prepvalidates the internal data and precomputesstruct superell_specific. -
rt_superell_shotintersects a ray with the prepared primitive and appends one or morestruct segentries. -
rt_superell_normcomputes the hit point and outward normal.
rt_superell_prep checks that a, b, and c have non-zero length and are
mutually perpendicular. It stores unit axes, inverse squared magnitudes, the
center point, transformation matrices, a bounding sphere, and a bounding RPP.
Those values go in stp→st_specific and are released by
rt_superell_free.
For a new primitive, ft_prep should do all expensive invariant work once:
validate database parameters, compute transform matrices, build acceleration
data, set stp→st_center, stp→st_aradius, stp→st_bradius,
stp→st_min, and stp→st_max, and return non-zero for invalid geometry.
ft_shot should be as small as practical because it runs for every candidate
ray. ft_norm should consume either the hit distance or cached per-hit data
and return a unit normal.
Implement display hooks
The superellipsoid wireframe hook is rt_superell_plot. It builds three
16-point loops from V, A, B, and C with rt_superell_16pts and appends
them to a bv_vlist. This is why draw superell.s can show an editable
wireframe even though rt_superell_tess is currently a stub.
The superellipsoid also registers:
-
rt_superell_bbox, used by drawing and database utilities needing an axis-aligned bounding box without full ray prep. -
rt_superell_labels, which emitsV,A,B, andCpoint labels. -
rt_superell_keypoint, which returnsVby default or endpoints for explicitA,B, andCkey strings. -
rt_generic_scene_obj, which lets newer drawing code create scene objects through a generic wrapper.
For a new primitive, implement ft_plot early. It gives MGED and other tools a
visible object while more expensive features are still under development. Then
add ft_bbox, ft_labels, and ft_keypoint when selection, snapping, object
labels, and center-based operations need precise points.
Implement database import and export
The superellipsoid v5 external form is exactly fourteen network doubles:
V, A, B, C, n, and e. rt_superell_import5 decodes them with
bu_cv_ntohd, initializes an rt_superell_internal, and applies any modeling
matrix through rt_superell_mat. rt_superell_export5 scales the length fields
by local2mm, leaves n and e unscaled, and encodes the same fourteen
doubles with bu_cv_htond.
The v4 hooks, rt_superell_import4 and rt_superell_export4, use
union record, set s_type = SUPERELL, and place fields in s_values.
For a new primitive, write the v5 format first and make it boring. Keep a fixed
order, convert only dimensional fields, use network byte order helpers, and
make import initialize all rt_db_internal fields: idb_major_type,
idb_type, idb_meth, and idb_ptr.
Implement transforms and measurements
rt_superell_mat applies a matrix to v, a, b, and c, but intentionally
does not change n and e. Tests in src/librt/tests/edit/superell.cpp
document that global scale and rotation preserve those exponents.
rt_superell_volume uses an analytic formula when tgamma is available and a
Cauchy-Crofton fallback otherwise. rt_superell_surf_area uses a numerical
UV-space approximation and a special box-like case when both exponents are near
zero. rt_superell_perturb creates a copied primitive and offsets the three
axis lengths outward by a requested value.
For a new primitive, add measurement hooks when analyze, mass-property tools,
or model validation need them. If you cannot implement an analytic measure,
consider an existing fallback only when its assumptions fit the primitive.
Add BREP and mirror support when useful
rt_superell_brep starts from an ellipsoid BREP, converts it to a NURBS
surface, adjusts control point weights using n and e, and uses either one
surface or eight surfaces depending on exponent values. This hook is optional,
but without it BREP workflows and converters cannot represent the primitive
directly.
rt_superell_mirror is wired through the legacy dispatcher in
src/librt/primitives/mirror.c. It mirrors v, a, b, and c. The
current implementation resets n and e to 1.0, which is an important
historical detail: when adding a new primitive, decide deliberately how every
field transforms. If a field is invariant, preserve it explicitly.
Register the OBJ table entry
All core primitive behavior is advertised through OBJ[] in
src/librt/primitives/table.cpp. The superellipsoid entry is at
ID_SUPERELL and has the label superell:
RT_FUNCTAB_MAGIC, "ID_SUPERELL", "superell", 1, rt_superell_prep, rt_superell_shot, rt_superell_print, rt_superell_norm, ... rt_superell_import5, rt_superell_export5, rt_superell_import4, rt_superell_export4, ... rt_superell_parse, sizeof(struct rt_superell_internal), RT_SUPERELL_INTERNAL_MAGIC, ... rt_superell_bbox, rt_superell_volume, rt_superell_surf_area, ... rt_superell_labels, rt_superell_keypoint, rt_superell_mat, rt_superell_perturb, rt_generic_scene_obj
Also add RT_DECLARE_INTERFACE(superell) near the top of table.cpp; this
declares the functions named by the table.
The table index must match the ID_* value. If your new ID introduces a gap,
add placeholder entries so every later object keeps its existing index.
Understand every OBJ hook
The superellipsoid demonstrates how most rt_functab slots are used. NULL
means either the feature does not apply or the primitive has not implemented it.
|
Superellipsoid entry |
Why the hook exists |
|
|
Allows bounding-box pruning before expensive intersection work. |
|
|
Validates |
|
|
Intersects one ray with the prepared primitive and emits hit segments. |
|
|
Debug print of prepared data. |
|
|
Computes the surface normal at a ray hit. |
|
NULL |
Used by primitives with piecewise acceleration. Superellipsoid has one simple prepared object, so it does not use these. |
|
|
Texture-space coordinates. The superellipsoid hook is present but only logs a placeholder today. |
|
|
Surface curvature at a hit. Present as a placeholder for future shading or analysis. |
|
NULL |
Optional volume classification against boxes; superellipsoid relies on other paths. |
|
|
Releases |
|
|
Builds the three-loop wireframe used by MGED and display tools. |
|
NULL |
View-dependent plotting. Superellipsoid currently uses fixed wireframe loops. |
|
NULL |
Vectorized multi-ray shot path. Superellipsoid uses scalar shot plus optional OpenCL support. |
|
|
NMG tessellation. Superellipsoid registers a stub that returns |
|
NULL |
Legacy NURB tessellation hook, not used by superellipsoid. |
|
|
Converts the primitive to OpenNURBS BREP form. |
|
|
Reads and writes the v5 database body. |
|
|
Reads and writes old v4 records. |
|
|
Frees the |
|
|
Human-readable |
|
|
Generic export/import transform wrapper. In the superellipsoid v5 path,
|
|
|
Named-field get/adjust support for |
|
|
Lets generic code allocate/copy internal primitive storage. |
|
|
Lets generic code check the internal type. |
|
generic hooks |
Superellipsoid fields are simple enough for the generic parser. |
|
NULL |
Generic make hook is not used; libged |
|
|
Parameter-constraint hook. Superellipsoid currently returns success without populating constraints. |
|
|
Computes an axis-aligned bounding box from |
|
|
Mass-property support. |
|
|
Surface-area support. |
|
NULL |
A centroid hook could return |
|
NULL |
Optional tighter oriented bounds. |
|
NULL |
Sub-primitive selection editing. Superellipsoid exposes point keypoints, not selectable faces or edges. |
|
NULL |
Prepared-data cache serialization. Superellipsoid recomputes prep data. |
|
|
Returns |
|
|
Returns default keypoint |
|
|
Applies matrices to geometric fields. |
|
|
Produces an offset copy for tolerance and sampling workflows. |
|
|
Creates modern scene objects through the generic display wrapper. |
Add OpenCL support
The superellipsoid has a GPU ray path in addition to the CPU path:
-
src/librt/primitives/superell/superell.cdefinesstruct clt_superell_specificandclt_superell_packunderUSE_OPENCL. -
src/librt/primitives/superell/superell_shot.climplementssuperell_shotandsuperell_norm. -
src/librt/primitives/common.cldeclaresRT_DECLARE_INTERFACE(superell). -
src/librt/primitives/rt.cldefinesID_SUPERELLand dispatches tosuperell_shotandsuperell_norm. -
src/librt/primitives/primitive_util.caddssuperell_shot.cltomain_filesand mapsID_SUPERELLtoclt_superell_pack. -
src/librt/CMakeLists.txtlistsprimitives/superell/superell_shot.clinCL_FILESso it is installed as OpenCL data.
For a new primitive, CPU ray tracing can land before OpenCL support. If you do
add OpenCL, keep the packed GPU struct layout in lockstep with the data the
.cl code reads, and test both single-hit and multi-hit kernel builds.
Add librt edit hooks
Editing has a separate table, EDOBJ[], in
src/librt/primitives/edtable.cpp. Superellipsoid declares its edit interface
with EDIT_DECLARE_INTERFACE(superell), adds a forward declaration for
rt_edit_superell_edit_desc, and registers this entry:
RT_FUNCTAB_MAGIC, "ID_SUPERELL", "superell", NULL, /* label */ edit_keypoint, /* keypoint */ NULL, /* e_axes_pos */ rt_edit_superell_write_params, rt_edit_superell_read_params, rt_edit_superell_edit, rt_edit_superell_edit_xy, NULL, NULL, NULL, /* prim edit create/destroy/reset */ edit_menu_str, rt_edit_superell_set_edit_mode, rt_edit_superell_menu_item, rt_edit_superell_edit_desc, NULL /* edit_get_params */
edsuperell.c defines four primitive-specific edit commands:
-
ECMD_SUPERELL_SCALE_A -
ECMD_SUPERELL_SCALE_B -
ECMD_SUPERELL_SCALE_C -
ECMD_SUPERELL_SCALE_ABC
rt_edit_superell_set_edit_mode maps those commands to
RT_PARAMS_EDIT_SCALE, and rt_edit_superell_edit dispatches the command to
the correct scale function. The scale functions preserve the current axis
direction and change its magnitude. Set A,B,C first scales A, then rescales
B and C so all three magnitudes match A.
rt_edit_superell_edit_xy translates mouse movement into the same edit model:
scale modes call edit_sscale_xy, translation calls edit_stra_xy and
edit_abs_tra, and other modes fall back to generic editing.
Understand every EDOBJ hook
EDOBJ[] is smaller than OBJ[], but it is just as important for applications
that edit geometry interactively. The superellipsoid entry shows which hooks
need primitive-specific behavior and which can remain generic or NULL.
|
Superellipsoid entry |
Why the hook exists |
|
NULL |
Optional edit-time label override. Superellipsoid uses core
|
|
|
Generic edit keypoint helper. It ultimately uses primitive keypoint behavior
for |
|
NULL |
Optional primitive-specific edit-axis placement. Superellipsoid uses generic axis placement. |
|
|
Writes editable text for |
|
|
Reads the text form back, unit-converting only the point/vector fields. |
|
|
Executes the active edit command. Superellipsoid handles A/B/C scaling and falls back to generic edits for other modes. |
|
|
Maps mouse motion to scale and translate operations. |
|
NULL |
Used when an edit mode needs private per-session state. Superellipsoid scaling does not. |
|
|
Generic text for edit menus. |
|
|
Switches between |
|
|
Returns the interactive edit menu items. |
|
|
Returns structured metadata for generated UI controls and JSON tests. |
|
NULL |
Optional current-parameter query for a command. Superellipsoid does not expose a separate implementation. |
Provide structured edit metadata
The superellipsoid implements ft_edit_desc with a static
rt_edit_prim_desc. It tells clients that superell has four interactive
geometry commands, each with one scalar length parameter. For example, Set A
uses a parameter named a, labeled Semi-Axis A, with type
RT_EDIT_PARAM_SCALAR, minimum 1e-10, and units length.
This descriptor matters because newer clients can generate edit controls from
metadata instead of hard-coding every primitive. When adding a new primitive,
implement ft_edit_desc even if legacy MGED still needs hand-written menus.
Then add it to src/librt/tests/edit/edit_desc.cpp; the superellipsoid test
checks that JSON for ID_SUPERELL contains "superell" and command id
35113.
Support text editing
rt_edit_superell_write_params emits editable text:
Vertex: x y z A: x y z B: x y z C: x y z <n, e>: <n, e>
rt_edit_superell_read_params reads the same text, converts v, a, b, and
c from local units to base units, and leaves n and e dimensionless. A new
primitive should keep the write/read pair symmetrical and should not require
users to preserve labels exactly when the numeric order is unambiguous.
Add MGED and libged integration
Core librt registration makes the primitive loadable and traceable. User commands still need explicit wiring.
Add type-in creation
src/libged/typein/typein.c handles in superell.s superell …. The
superellipsoid path includes:
-
Prompt strings in
p_superell. -
A
superell_infunction that expects3*4 + 2values. -
A dispatch case for primitive name
superell.
superell_in allocates struct rt_superell_internal, sets the magic, copies
V, A, B, C, and stores n and e. It multiplies only the first twelve
numeric inputs by gedp→dbip→dbi_local2base.
src/libged/make/make.c handles make name superell. Its default
superellipsoid is centered at the current origin with:
A = (0.5 * scale, 0, 0) B = (0, 0.25 * scale, 0) C = (0, 0, 0.125 * scale) n = 1.0 e = 1.0
For a new primitive, add both paths if users should be able to create it
interactively. Use in for exact parameter entry and make for a reasonable
default object.
Decide whether to add a libwdb constructor
There is no dedicated public mk_superell helper in this tree. Superellipsoid
tests and examples create one by filling rt_superell_internal and calling
wdb_export or wdb_put_internal. If a new primitive needs a public creation
helper, add it to libwdb and public headers deliberately; otherwise document the
internal-object creation pattern.
Wire legacy MGED editing
MGED still has hard-coded primitive editing in addition to EDOBJ[].
Superellipsoid appears in these places:
File |
Superellipsoid role |
|
Declares |
|
Defines menu items |
|
Selects the superellipsoid menu, implements the legacy menu edit cases, returns keypoints, and draws edit labels. |
|
Writes and reads the text-edit form for |
This is duplicated with some of the newer librt edit behavior. When adding a
new primitive, check both systems. If MGED should edit the primitive today,
add the legacy cases. If a newer GUI consumes EDOBJ[], add the structured
edit descriptor and tests as well.
Wire Archer or other GUI scripts
Archer has a superellipsoid edit frame:
-
src/tclscripts/archer/SuperellEditFrame.tclsubclasses the ellipsoid edit workflow and addsnandefields. -
src/tclscripts/archer/Archer.tclbuilds, initializes, selects, and creates thesuperellView. -
src/tclscripts/archer/CMakeLists.txtinstallsSuperellEditFrame.tcl. -
src/tclscripts/archer/images/primitive_superell.pngis listed by the image CMake file for UI asset installation.
For a new primitive, add GUI code only where the application cannot use the
structured edit descriptor. If a custom frame is needed, follow the
superellipsoid pattern: parse existing object data, expose primitive-specific
fields, call put or edit commands to update the object, and list the Tcl file
and icon in CMake.
Add helper-library support
The superellipsoid appears in several libged helpers:
-
src/libged/analyze/analyze.cppdispatchesID_SUPERELLtoanalyze_superell. -
src/libged/analyze/superell.cppcomputes an ellipsoid-like analysis fromA,B, andC. This is useful forn = e = 1, but it is also a reminder to revisit analysis math when exponent fields change geometry. -
src/libged/edit/pscale.cdispatchesDB5_MINORTYPE_BRLCAD_SUPERELLto_ged_scale_superell. -
src/libged/edit/scale_superell.cimplements attribute scaling forA,B,C, andABC. -
src/libged/get_solid_kp.creturnsVas the default keypoint.
For another primitive, search for switch statements on ID_*,
DB5_MINORTYPE_BRLCAD_*, or primitive labels in libged and related tools.
Add cases only when the command can do something meaningful. A wrong helper is
worse than no helper because users will trust command output.
Add tests and regressions
Superellipsoid coverage is spread across targeted and broad tests:
Test |
What it covers |
|
Librt edit commands, mouse edit paths, generic scale/translate/rotate behavior,
and the fact that |
|
|
|
Generic edit command coverage with an in-memory superellipsoid made through
|
|
MGED primitive-edit menu behavior for |
|
The MGED-to-librt integration boundary: edit lifecycle and persistence across the practical CSG types, menu dispatch, numeric and mouse input, knob rotation, selection-driven edits, file-backed primitives, and accept/reject behavior. |
|
Documents that |
The MGED regression is intentionally complementary to the librt edit tests. It verifies that MGED reaches the requested librt state, persists the result, and produces a known geometric outcome where MGED has a stable observable: rigid translations preserve and offset bounds, parameter edits produce the requested value or endpoint, and rotations produce the requested vector. The lifecycle runner requires an operation-specific result validator, preventing a new test from checking only that serialization changed. The librt tests remain responsible for exhaustive numerical validation of the editing algorithms. Mouse input is meaningful for translate, scale, and pick states; interactive rotation is delivered through the knob path. Selection-heavy editors such as ARS, BoT, metaball, NMG, pipe, and sketch receive representative end-to-end workflows in MGED and detailed topology coverage in librt. ARB4 through ARB8 are the exception to representative menu sampling: every point, edge, move-face, and rotate-face entry is exercised and checked against its selected point, line, face plane, or fixed rotation vertex because their menus have type-specific operation mappings.
The same regression also exercises the GUI command boundary without requiring a window system. It runs the command sequence issued by the Tk matrix picker, invokes all eight matrix-edit menu callbacks, and sends the unconstrained and X/Y/Z-constrained translate, rotate, and scale drags installed by the default Tk bindings. Each case checks the exact stored combination matrix and confirms that the view center, size, and orientation did not change. A graphical primitive pick similarly verifies the resulting primitive bounds and unchanged view. This catches event-routing failures and stale MGED-to-librt edit state in ordinary headless regression runs.
Headless testing cannot verify rendered classic-faceplate hit regions, Tk
widget construction and bindings, or the separate Tk Primitive Editor form.
Those require a display-backed test (normally Xvfb) which generates events on
the real widgets. Such a test should verify the selected matrix path, invoke
each faceplate operation, and exercise Primitive Editor Apply, Reset, create,
and type-change actions with exact database assertions. Keep those widget
tests separate from csg_edit.mged: the latter remains the portable behavioral
test for the commands the widgets dispatch.
At minimum, a new primitive should have tests for database round trip, make
or in creation, ray hits and misses, normals, bounding boxes, text
description, transforms, and every edit mode it advertises. If a hook is a
documented stub, add a test or table entry that records the expected failure so
future developers do not mistake it for an accidental regression.
Step-by-step checklist for a new primitive
Use this checklist after designing the primitive data model.
-
Pick the short label and persistent parameters. Superellipsoid uses label
superelland storesV,A,B,C,n, ande. -
Reserve identity constants. Add
ID_MYPRIMininclude/rt/defines.h, matchingDB5_MINORTYPE_BRLCAD_MYPRIMininclude/rt/db5.h. Add a magic number ininclude/bu/magic.h. -
Add the internal structure. Add
struct rt_myprim_internalandRT_MYPRIM_CK_MAGICininclude/rt/geom.h. Keep derived acceleration data private to librt. -
Decide v4 support. Superellipsoid has
SUPERELLands_superell_*aliases ininclude/rt/db4.h; new v5-only primitives can leave v4 hooks NULL. -
Create the primitive source directory. Follow
src/librt/primitives/superell/: main.c, optional BREP, optional mirror, optional OpenCL shot file, and edit implementation. -
Implement database I/O. Write
rt_myprim_import5,rt_myprim_export5, andrt_myprim_ifree. Convert dimensional fields by unit scale and leave dimensionless fields unscaled, as superellipsoid does fornande. -
Implement prep, shot, normal, and free. Use prep to validate geometry and cache derived values in
stp→st_specific. Use free to release that cache. -
Implement display and bounds. Add
ft_plotandft_bboxearly. Add labels and keypoints for editor usability. -
Implement transforms and optional measures. Add
ft_mat,ft_volume,ft_surf_area, centroid if available, and perturb if offset copies are meaningful. -
Register in
src/librt/primitives/table.cpp. AddRT_DECLARE_INTERFACE(myprim)and anOBJ[ID_MYPRIM]entry at the exact ID index. Use NULL for unsupported hooks. -
Add librt edit support. Implement
edmyprim.c, add it toLIBRT_PRIMEDIT_SOURCES, registerEDOBJ[ID_MYPRIM], expose menu items, edit processing, text read/write, andft_edit_desc. -
Update the build. Add source files to
src/librt/CMakeLists.txt. If there is OpenCL code, list the.clfile in bothCL_FILESand the file lists used for distribution. -
Add creation commands. Wire exact
increation insrc/libged/typein/typein.cand defaultmakecreation insrc/libged/make/make.c. -
Add MGED legacy editing if users need it. Update
menu.h,menu.c,edsol.c, andtedit.cthe way superellipsoid does forA,B,C, andA,B,Cscaling. -
Add GUI-specific code only where needed. Superellipsoid has an Archer edit frame because it extends the ellipsoid editor with
nandefields. -
Add helper commands and analysis deliberately. Search for relevant
ID_*and minor-type switches. Add analysis, pscale, keypoint, converter, or icon support only when the command can be correct for the primitive. -
Add tests. Follow the superellipsoid edit tests, descriptor tests, libged edit tests, and MGED regression patterns. Document expected stubs.
-
Run focused verification. Build the changed targets, create a sample object with
inandmake, draw it, raytrace it, edit every advertised edit mode, save and reload the database, and check old-format support if you implemented v4 I/O.
A practical landing strategy
For a useful first landing, avoid registering features that are not true yet.
A new primitive normally needs a stable ID, internal struct, v5 import/export,
OBJ[] registration, prep, shot, norm, free, plot, bbox,
describe, creation through in or make, and focused tests. Add edit
support, BREP, OpenCL, measures, and GUI-specific integrations as soon as they
are needed and testable.
The superellipsoid shows that BRL-CAD can tolerate incremental feature depth: it is a first-class primitive even though tessellation, UVs, and curvature are not complete. The important rule is to make every hook honest, every registration intentional, and every user-visible path either working or explicitly documented.