Liking cljdoc? Tell your friends :D

Surface navigation

A surface frame treats local Z as forward, X as sideways, and Y as depth along the outward normal. forward sweeps the current cross-section along the walked path. The API works in Clojure, ClojureScript/WASM, and the SCI adapter.

(require '[clj-manifold3d.core :as m]
         '[plexus.core :as p])

(def support (m/cylinder 30 20 20 96))
(def surface (p/surface support))

(def ribbon
  (p/extrude
    (p/frame :name :ribbon :cross-section (m/square 1.2 0.6 true))
    (p/on-surface :surface surface
                  :origin [20 0 8.37] :direction [0 1 0.15]
                  :depth 0.45 :step-size 0.5)
    (p/forward :length 65)
    (p/rotate :y 0.7)
    (p/forward :length 12)
    (p/translate :y 1)
    (p/forward :length 4)
    (p/save-transform :frame :ribbon :name :end)))

The original support is a navigation reference; it is not automatically included in the extrusion. Union the ribbon with it for a raised feature, or place a cutter at negative depth and subtract it for a groove.

For a colored GLB with branching paths, run:

clojure -M:dev src/examples/surface_navigation.clj
# writes target/surface-navigation.glb

Attaching and moving

p/surface copies a Manifold's triangle topology into immutable Clojure data. Prepare it once and share it between paths. It retains no native handles; the original Manifold may be disposed after preparation. on-surface also accepts a raw Manifold, preparing it during each build.

on-surface requires :surface, :origin, and :direction. Both vectors use world coordinates. The origin is snapped once to the nearest triangle, and the direction is projected into its tangent plane. At a seed edge or vertex, the direction chooses an outgoing face. Start near the intended sheet when surfaces are close together. An empty surface, zero direction, or direction with no outgoing tangent is an error.

OperationSurface behavior
forward :length d / forward :z dWalk forward and sweep a cross-section
forward :x dWalk sideways and sweep a cross-section normal to that movement
forward :y dExtrude along the normal, changing depth at the same surface point
translate :x x :z z :y yWalk one combined tangent displacement, then change depth; create no geometry
rotate :y angleTurn the forward direction about the outward normal, in radians
off-surfaceKeep the current position and orientation, resume ordinary spatial operations

All distances are in model units and may be negative. Tangential distance is measured on the reference surface, regardless of depth. Depth offsets use the interpolated frame normal in smooth regions and the face normal at sharp creases; they do not repeatedly project onto the nearest surface.

:to targets specific frames. Without it, attachment and detachment affect the active frames and the default frame. New frames inherit the default frame's surface cursor. Branches restore existing frames' cursors afterward, while retaining generated geometry and saved transforms. save-transform returns an ordinary world-space transform suitable for placing another model.

forward supports :gap, :center, :branch?, and :n-steps. :step-size (default 1) supplies the sampling interval when :n-steps is absent or nil. Navigation samples always include triangle crossings, even with :n-steps 1. For geometry, closely spaced stations in smooth regions are coalesced if their profile would fold; the resulting spacing cannot exceed twice the requested interval. Existing loft can interpolate between changing cross-sections along the retained sweep stations. Surface path samples returned by points use world coordinates, including when the frame was created under a nonidentity transform.

Use p/deferred when a rebuilt path should read new parameter values, as with ordinary Plexus operations. Use plexus.source/on-surface, forward, etc. for file/line/column diagnostics. These macros are also available inside SCI.

Geometry and limits

Navigation follows the triangulated surface, not an inferred analytic sphere or spline. The heading is parallel transported by unfolding adjacent triangles. Walking straight defines a local geodesic initial-value path; it is not a solver for the globally shortest route to a destination. Unlike a planar chart, it can continue around a cylinder and past the seed tangent plane's horizon.

The implementation follows the topology discipline in Manifold's native SurfaceUV mapping: property vertices are connected through explicit merge IDs and paired physical edges. It never welds nearby coordinates or repeatedly snaps to the closest triangle. Thus a walk cannot jump between nearby, disconnected sheets. Native UV mapping uses plane cuts; Plexus instead transports the heading across edges. See also Geometry Central's description of geodesic path tracing.

Each sampled cross-section is rigid. Frame normals use angle-weighted vertex normals interpolated across connected smooth faces, matching the native mapper while keeping creases over 60 degrees sharp. Headings are projected into these frame planes; navigation still unfolds the exact triangle geometry. This avoids normal jumps at every edge of a finely tessellated bowl or torus. The sweep bends along its centerline; it does not independently map every point across the profile's width onto the surface. Use a sufficiently fine reference mesh and a narrow profile for a surface-following ribbon. Wide profiles, sharp creases, large depth offsets, and paths crossing themselves can produce overlaps or self-intersections. Sweep construction checks the progression of every profile vertex between stations and rejects local folds/collapse with :reason :surface-sweep-fold. This includes unsafe sharp inward joins and excessive offsets. Depth and orientation remain discontinuous at sharp creases; this version does not build mitered or rounded joins there. The guard does not detect collisions between distant portions of a path, or guarantee clearance from other geometry. Changing the profile inside a surrounding loft may also introduce intersections that were absent from its individual forwards.

See concavity validation for the tested cases, the independent output-mesh checks, and runnable bowl/torus examples.

When a walk reaches a vertex with nonzero angle defect, continuation is ambiguous and raises :reason :vertex-singularity, with the point and face in ex-data. Change the origin or heading. Starting at a vertex is allowed because the supplied direction selects the initial face. Flat subdivision vertices can be crossed. Ending exactly on an edge retains the arrival face; its normal may differ from the normal of a path arriving from the other direction.

Refine genuinely smooth tangent meshes before preparing them; zero-filled tangent buffers from Boolean composition are accepted. Preparation and seed selection scan the mesh; ordinary movement visits only crossed triangles, except for vertex-fan checks. Individual movements are limited to 100,000 crossings/samples.

Surface mode rejects spatial left/right/up/down/curve, X/Z tilts, global translation, replacement transforms, insert :end-frame, and forward options :model, :twist, and :transform-step-fn. Turn with rotate :y and walk with forward, or call off-surface before those spatial operations. Regular insert can place a rigid model at the surface frame without changing the cursor.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
←Move to previous article
→Move to next article
Ctrl+/Jump to the search field
× close